Changer son environnement fullstack en un clin d'oeil : partie 2 avec Maven
Tags: maven, fullstack, environnement, french
Published: 2023-02-12 23:16:44

Vous êtes développeur consultant et vous travaillez pour plusieurs clients ? Vous travaillez aussi sur des projets Open Source ?

Vous avez certainement été confronté au problème de changement de contexte lorsque vous devez passer d'un projet à un autre : changement de version de Java, de Node.js, de configuration Git ou de signature GnuPG...

J'ai commencé une série d'articles sur ce sujet avec [un premier article au sujet du changement de version de Java](https://dev.to/hcross/changer-son-environnement-fullstack-en-un-clin-doeil-partie-1-avec-java-3j50).

Je continue ici cette série et j'aimerais aborder la capacité de Maven à changer de contexte de développement.

## Ma façon préférée d'installer Maven

Pour installer Maven, vous pouvez le faire de multiples façons... Passer par le gestionnaire de paquets de votre distribution Linux, utiliser [brew](https://brew.sh) sur MacOS, utiliser [Chocolatey](https://chocolatey.ort) sous Windows, voire [télécharger directement le Zip du projet](https://maven.apache.org/download.cgi).

Me concernant, je préfère utiliser [l'outil dont je vous ai parlé dans mon premier article](https://dev.to/hcross/changer-son-environnement-fullstack-en-un-clin-doeil-partie-1-avec-java-3j50) : [SDKMan](https://sdkman.io/) !

Avec SDKMan, pour installer Maven, rien de plus simple, il suffit de taper la commande suivante dans votre terminal :

```sh
~❯ sdk install maven
Downloading: maven 3.9.0
In progress...
########################################################################## 100,0%
Installing: maven 3.9.0
Done installing!
Do you want maven 3.9.0 to be set as default? (Y/n): 
Setting maven 3.9.0 as default.
```

Durant l'installation, SDKMan vous demandera si vous désirez définir la version de Maven téléchargée comme étant le version à utiliser par défaut. Vous pourrez répondre par l'affirmative en appuyant simplement sur la touche 'Entrée'.

On pourra vérifier la bonne installation de Maven à l'aide de la commande suivante :

```sh
~❯ mvn -version
Apache Maven 3.9.0 (9b58d2bad23a66be161c4664ef21ce219c2c8584)
Maven home: /Users/hoanicross/.sdkman/candidates/maven/current
Java version: 19.0.2, vendor: Oracle Corporation, runtime: /Users/hoanicross/.sdkman/candidates/java/19.0.2-open
Default locale: fr_FR, platform encoding: UTF-8
OS name: "mac os x", version: "13.2", arch: "aarch64", family: "mac"
```

Comme vous pouvez le remarquer, j'utilise un Mac avec une puce ARM d'Apple 😊, en l'occurrence il s'agit d'un Macbook Air M2 !

## Comment Maven gère les environnements de développement

Maven intègre en son sein un moyen assez efficace de gérer les différents environnements de développement avec les **profils** !

Il peuvent s'activer de diverses manières : présence d'une variable d'environnement, détection du type de l'OS ou encore présence d'un fichier...

Cependant, en y regardant de plus près, on remarque que les profils ont surtout été créés pour gérer les différents environnements d'exécution (`dev`, `prod` ou `preprod` par exemple) mais pas vraiment les _environnements de développement_.

Typiquement, ce qu'on va chercher à changer dans le fichier de configuration de Maven pour passer d'un contexte de développement à un autre, c'est :
- l'éventuel proxy utilisé dans votre mission
- l'éventuel mirroir utilisé dans votre mission

Ces deux parties sont configurables sous la forme d'un élément respectivement `<proxy>` ou `<mirror>` que l'on retrouvera dans des éléments `<proxies>` ou `<mirrors>` juste sous l'élément racine `<settings>` du fichier `settings.xml` de Maven.

En l'occurrence, dans la mission où je travaille à l'heure où j'écris ces lignes, nous devons utiliser un référentiel interne mirroir de tous les référentiels Maven.

Dans le fichier `settings.xml`, cela se présente de la façon suivante :

```xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
          
    <!-- Miroir pour la gestion des dépendances-->
    <mirrors>
        <mirror>
            <id>nexus</id>
            <mirrorOf>*</mirrorOf>
            <url>https://nexus.domaine-interne-de-mon-client.com/repo/maven/</url>
        </mirror>
    </mirrors>
    
</settings>
```

Dès qu'une dépendance doit être téléchargée de quelque référentiel Maven que ce soit, c'est le serveur Nexus de mon client qui sera interrogé via l'URL `https://nexus.domaine-interne-de-mon-client.com/repo/maven/` qui n'est accessible **QUE** lorsque le VPN est activé...

Clairement, si je ne suis plus connecté au VPN du client et que je souhaite travailler sur un projet Java open source perso avec Maven, ça ne va plus marcher : les dépendances vont être téléchargées depuis le Nexus qui est indisponible (le VPN n'est pas monté) et la construction va échouer.

Pour continuer à travailler sur ce projet, il me faudrait éditer le fichier `~/.m2/settings.xml`, et mettre en commentaire le bloc `<mirrors>...</mirrors>` à chaque fois...

Et la même opération devra être réalisée lorsque je devrais à nouveau travailler sur un projet de mon client.

Une vraie purge ☹️

Il me fallait donc **absolument** trouver une solution pour que cette bascule se fasse automatiquement !

## Comment ChatGPT m'a mis sur la voie

Dans cette quête de pouvoir obtenir mon mécanisme de changement de contexte de développement, je me suis dit que [ChatGPT](https://chat.openai.com/) pouvait peut-être m'aider.

Voici la requête que je lui ai soumis :

> Hello, I would like to activate the ability of maven settings to setup a local repository to be the mirror of all existing repository, especially the public ones, only when I am connected to their network. For all other situation, I want this mirroring property to be deactivated. Do you have a solution for my problem ?

Ici, je lui ai demandé de pouvoir activer automatiquement la notion de référentiel local en tant que mirroir de toutes les autres, seulement lorsque je suis connecté au réseau du client.

Dans sa réponse, il fait justement référence à la notion de profil dont je vous ai parlé précédemment :

> Yes, you can configure your Maven settings to mirror remote repositories when you are connected to a specific network, and to not mirror them when you are not connected to that network. To do this, you will need to create two separate profiles in your settings.xml file: one for the scenario where you are connected to the network, and one for when you are not.
>
>In the profile for when you are connected to the network, you can specify a mirror that points to the local repository, and include a `<mirrorOf>*</mirrorOf>` element to indicate that all remote repositories should be mirrored to the local repository.
>
>In the profile for when you are not connected to the network, you can remove the mirror from the profile, and instead include the remote repositories that you want to use directly in the `<repositories>` element.
>
>You can use `<activeByDefault>` and `<activation>` elements to specify which profile is active depending on your network state.
>
>It is important to note that, if you want to use this feature, you need to have a local repository already configured, and you should consider the amount of space that the repository will need.

Ah mais c'est super ça 😃 ! Je vais donc pouvoir utiliser les profils pour activer ou non les mirroirs des référentiel Maven... Il fallait que j'en sache plus et j'ai donc demandé à ChatGPT :

> Can you provide me an example of a settings.xml maven file that implements those needs ?

Et voici le fichier exemple qu'il m'a fournit :

```xml
<settings>
    <mirrors>
        <mirror>
            <id>mirror-local-repo</id>
            <mirrorOf>*</mirrorOf>
            <url>file:///path/to/local/repo</url>
        </mirror>
    </mirrors>

    <profiles>
        <profile>
            <id>on-corporate-network</id>
            <activation>
                <property>
                    <name>onCorporateNetwork</name>
                    <value>true</value>
                </property>
            </activation>
            <repositories>
                <repository>
                    <id>central</id>
                    <url>http://central</url>
                    <releases>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                    </releases>
                </repository>
            </repositories>
        </profile>
        <profile>
            <id>off-corporate-network</id>
            <activation>
                <property>
                    <name>onCorporateNetwork</name>
                    <value>false</value>
                </property>
            </activation>
            <repositories>
                <repository>
                    <id>central</id>
                    <url>http://central</url>
                    <releases>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                    </releases>
                </repository>
                <repository>
                    <id>other-repo</id>
                    <url>http://other-repo</url>
                    <releases>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                    </releases>
                </repository>
            </repositories>
        </profile>
    </profiles>
</settings>
```

Euuuh 🧐 le bloc `<proxies>` n'est pas inclus où même associé à la partie concernant les différents profils et je m'empresse d'en faire mention à ChatGTP :

> However, in the example you provided, the mirror functionnality will be activated for both profiles since it is not part of the definition of the `<profile>` element. I really want the mirror to be deactivated when the `off-corporate-network` is active.

Et admirez ce que ChatGPT me propose comme fichier `settings.xml` :

```xml
<settings>
    <profiles>
        <profile>
            <id>on-corporate-network</id>
            <activation>
                <property>
                    <name>onCorporateNetwork</name>
                    <value>true</value>
                </property>
            </activation>
            <mirrors>
                <mirror>
                    <id>mirror-local-repo</id>
                    <mirrorOf>*</mirrorOf>
                    <url>file:///path/to/local/repo</url>
                </mirror>
            </mirrors>
            <repositories>
                <repository>
                    <id>central</id>
                    <url>http://central</url>
                    <releases>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                    </releases>
                </repository>
            </repositories>
        </profile>
        <profile>
            <id>off-corporate-network</id>
            <activation>
                <property>
                    <name>onCorporateNetwork</name>
                    <value>false</value>
                </property>
            </activation>
            <repositories>
                <repository>
                    <id>central</id>
                    <url>http://central</url>
                    <releases>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                    </releases>
                </repository>
                <repository>
                    <id>other-repo</id>
                    <url>http://other-repo</url>
                    <releases>
                        <enabled>true</enabled>
                        <updatePolicy>always</updatePolicy>
                    </releases>
                </repository>
            </repositories>
        </profile>
    </profiles>
</settings>
```

Eh bien voilà !  C'était tellement simple... Mais je voulais quand même en avoir le coeur net car la documentation de Maven ne mentionne **pas du tout cette possibilité**.

Voici ce que j'ai dit à ChatGPT :

> Reading the version 1.0.0 of the Maven settings XSD, the `<profile>` element can only contain direct child elements of type `<id>`, `<activation>`, `<repositories>`, `<pluginRepositories>` or `<properties>`. It cannot contain a `<mirrors>` child element.

Et vous savez quoi ? Il a admi son erreur et il est revenu sur sa première proposition qui ne permettait pas de conditionner l'activation des mirroirs selon les profils... On se marche sur la tête 🤨

C'est alors que je me suis dit que si le fonctionnement interne de Maven ne permettait pas d'activer ou non dynamiquement la notion de mirroir, il se pourrait qu'un outil externe puisse le faire à la place de Maven.

J'ai commencé à rédiger ma requête pour ChatGPT quand je me suis dit : "je peux le faire moi-même, sans l'aide de ChatGPT".

Ainsi, en me faisant tourner en bourrique, ChatGPT m'a ouvert sur une possibilité externe à Maven : jongler avec plusieurs fichiers de configuration !

## Avoir plusieurs fichiers de configuration

Par défaut, Maven va chercher sa configuration dans :
- le fichier `pom.xml` du projet courant
- le fichier `~/.m2/settings.xml`
- le fichier `conf/settings.xml` du répertoire d'installation de Maven

Cela ne laisse pas beaucoup de place pour insérer quelque chose...

Si on tape `mvn --help` dans un terminal, on peut lire cette option de maven :

```
 -s,--settings <arg>                    Alternate path for the user
                                        settings file
```

C'est exactement ce dont j'avais besoin !

Lorsque je travaille sur un projet d'un client, on va dire _lambda_, je veux utiliser le fichier de configuration `~/.m2/settings-lambda.xml`.

Pour les autres projets Open Source perso, le fichier `~/.m2/settings.xml` d'origine fera très bien l'affaire !

Pour un projet Open Source sur lequel je travail, je peux utiliser la commande `mvn` comme à l'accoutumée.

Pour un projet de mon client _lambda_, par contre, je vais donc devoir faire :

```sh
~❯ mvn -s ~/.m2/settings-lambda.xml clean install
```

Bon on va dire que c'est mieux que d'éditer le fichier `settings.xml` de Maven mais ça ne me convient toujours pas ! Moi ce que j'aimerais c'est de trouver un moyen de configurer ça automatiquement pour toute une arborescence !

## Un _wrapper_ magique pour `mvn`

Ma solution idéale voudrait que j'ai juste à utiliser la commande `mvn` comme j'ai l'habitude de le faire et que le fichier de configuration spécifique soit ajouté à la commande à l'aide de l'option `-s`, uniquement quand c'est nécessaire.

Dans mon cas, je veux que ça soit activé lorsque je suis dans mon répertoire parent de tous les projets de mon client _lambda_, à savoir `~/devel/lambda`.

### Retrouver le nom du fichier de configuration spécifique

Par convention, j'ai donc décidé de dire que lorsqu'un fichier nommé `.m2_settings` était trouvé dans le répertoire courant ou dans un de ses répertoires parents, son contenu sera lu et considédé comme le chemin d'un fichier de configuration spécifique à prendre en compte au lancement de la commande `mvn`.

En simple langage _shell_, j'en suis arrivé au code suivant pour obtenir le contenu de ce fameux fichier `.m2_settings` :

```sh
# Find the given file going up in the parent directories
# until found, return "" either
find_up() {
  pth=$(pwd)
  while [[ "$pth" != "" && ! -e "$pth/$1" ]]; do
    pth=${pth%/*}
  done
  echo "$pth"
} 
  
M2_SETTINGS_CONFIG_NAME=.m2_settings
  
check_mvn_settings() {
  MVN_SETTINGS=~/.m2/settings.xml
  fnd_dir=$(find_up $M2_SETTINGS_CONFIG_NAME)
  if [[ "" != "$fnd_dir" ]]; then
    MVN_SETTINGS=$(cat $fnd_dir/$M2_SETTINGS_CONFIG_NAME)
  fi
  echo "${MVN_SETTINGS/#\~/$HOME}"
}
```

La fonction `find_up()` trouve un fichier dont le nom est passé en paramètres dans le répertoire courant et ses répertoires parents jusqu'à ce qu'il soit trouvé, et dans le cas où ce fichier serait introuvable, une chaîne vide sera retournée.

La fonction `check_mvn_settings()` retournera :
- le nom du fichier de configuration sécifique trouvé dans le fichier `.m2_settings` s'il est trouvé
- `$HOME/.m2/settings.xml` sinon

Notez que l'expression `${MVN_SETTINGS/#\~/$HOME}` permet de remplacer toute occurence de `~` par `$HOME` dans la variable d'environnement `MVN_SETTINGS` avant de la retourner car sans cela ça générait des problèmes d'interprétation.

### Trouver le binaire `mvn` d'origine

Mon _wrapper_ sera intitulé `mvn` comme la commande d'origine. Je ne veux pas changer mes habitudes.

Le chemin vers ce dernier sera d'ailleurs prioritaire au chemin du binaire `mvn` d'origine pour qu'il puisse faire cette action _magique_ d'ajout automatique du fichier de configuration spécifique...

Seulement voilà : je ne voulais pas avoir à préciser dans ce dernier l'emplacement du binaire de Maven d'origine.

Je voulais que mon script le trouve tout seul.

Voici le bout de code _shell_ qui réalise cette opération :

```sh
get_mvn_bin() {
  directory_to_remove=$(cd $(dirname $0) && pwd)
  PATH=:$PATH:
  PATH=${PATH//:$directory_to_remove:/:}
  PATH=${PATH#:}; PATH=${PATH%:}
  echo $(whereis mvn | awk '{print$2}')
}
```

Je sais, c'est _magique_. Et en plus ça marche 😝

Pour résumer le fonctionnement de ce code, ce dernier :
- récupère le répertoire du _wrapper_
- retire ce répertoire de la variable d'environnement `PATH`
- appelle la commande `whereis` pour trouver le chemin de la commande `mvn` d'origine (le _wrapper_ n'étant plus trouvable ici)
- retourne ce chemin à l'appelant

### Le corps principal du _wrapper_

Il ne reste plus qu'a appeler toutes ces fonctions et gérer le cas où Maven n'est pas trouvé :

```sh
MVN_CMD=$(get_mvn_bin)
if [[ "" == "$MVN_CMD" ]]; then
  >&2 echo "mvn command not found in PATH variable !"
  exit -1
fi
MVN_SETTINGS=$(check_mvn_settings)
$MVN_CMD -s $MVN_SETTINGS $@
```

Et voilà !

### Le script complet

Voici le code du script complet :

```sh
#!/bin/sh

# Find the given file going up in the parent directories
# until found, return "" either
find_up() {
  pth=$(pwd)
  while [[ "$pth" != "" && ! -e "$pth/$1" ]]; do
    pth=${pth%/*}
  done
  echo "$pth"
} 
  
M2_SETTINGS_CONFIG_NAME=.m2_settings
  
check_mvn_settings() {
  MVN_SETTINGS=~/.m2/settings.xml
  fnd_dir=$(find_up $M2_SETTINGS_CONFIG_NAME)
  if [[ "" != "$fnd_dir" ]]; then
    MVN_SETTINGS=$(cat $fnd_dir/$M2_SETTINGS_CONFIG_NAME)
  fi
  echo "${MVN_SETTINGS/#\~/$HOME}"
}

get_mvn_bin() {
  directory_to_remove=$(cd $(dirname $0) && pwd)
  PATH=:$PATH:
  PATH=${PATH//:$directory_to_remove:/:}
  PATH=${PATH#:}; PATH=${PATH%:}
  echo $(whereis mvn | awk '{print$2}')
}

MVN_CMD=$(get_mvn_bin)
if [[ "" == "$MVN_CMD" ]]; then
  >&2 echo "mvn command not found in PATH variable !"
  exit -1
fi
MVN_SETTINGS=$(check_mvn_settings)
$MVN_CMD -s $MVN_SETTINGS $@
```

## Faire un _wrapper_ vraiment complet

Rediriger la commande `mvn` ne me semblait pas suffisant.

En effet, je voulais notamment que cette commande se retrouve dans un répertoire dont la structure est identique à celle d'une installation de Maven classique.

J'ai opté pour la convention suivante :
- le répertoire d'installation de mon wrapper sera `~/.wrapper/maven`
- tout sont contenu sera un lien symbolique du répertoire d'installation de la version courante de Maven dans le répertoire de SDKMan
- à l'exception du répertoire `bin` qui contiendra mon _wrapper_ magique et des liens symboliques vers les autres binaires

Voilà ce que ça donne :

```sh
~❯ ls -lah .wrapper/maven
total 0
drwxr-xr-x  9 hoanicross  staff   288B 26 jan 08:07 .
drwxr-xr-x  3 hoanicross  staff    96B 26 jan 08:04 ..
lrwxr-xr-x  1 hoanicross  staff    58B 26 jan 08:06 LICENSE -> /Users/hoanicross/.sdkman/candidates/maven/current/LICENSE
lrwxr-xr-x  1 hoanicross  staff    57B 26 jan 08:06 NOTICE -> /Users/hoanicross/.sdkman/candidates/maven/current/NOTICE
lrwxr-xr-x  1 hoanicross  staff    61B 26 jan 08:07 README.txt -> /Users/hoanicross/.sdkman/candidates/maven/current/README.txt
drwxr-xr-x  8 hoanicross  staff   256B 12 fév 23:33 bin
lrwxr-xr-x  1 hoanicross  staff    55B 26 jan 08:06 boot -> /Users/hoanicross/.sdkman/candidates/maven/current/boot
lrwxr-xr-x  1 hoanicross  staff    55B 26 jan 08:06 conf -> /Users/hoanicross/.sdkman/candidates/maven/current/conf
lrwxr-xr-x  1 hoanicross  staff    54B 26 jan 08:06 lib -> /Users/hoanicross/.sdkman/candidates/maven/current/lib

~❯ ls -lah .wrapper/maven/bin
total 8
drwxr-xr-x  8 hoanicross  staff   256B 12 fév 23:33 .
drwxr-xr-x  9 hoanicross  staff   288B 26 jan 08:07 ..
lrwxr-xr-x  1 hoanicross  staff    62B 26 jan 08:08 m2.conf -> /Users/hoanicross/.sdkman/candidates/maven/current/bin/m2.conf
-rwxr-xr-x@ 1 hoanicross  staff   876B  6 fév 11:14 mvn
lrwxr-xr-x  1 hoanicross  staff    62B 26 jan 08:07 mvn.cmd -> /Users/hoanicross/.sdkman/candidates/maven/current/bin/mvn.cmd
lrwxr-xr-x  1 hoanicross  staff    63B 26 jan 08:07 mvnDebug -> /Users/hoanicross/.sdkman/candidates/maven/current/bin/mvnDebug
lrwxr-xr-x  1 hoanicross  staff    67B 26 jan 08:08 mvnDebug.cmd -> /Users/hoanicross/.sdkman/candidates/maven/current/bin/mvnDebug.cmd
lrwxr-xr-x  1 hoanicross  staff    61B 26 jan 08:08 mvnyjp -> /Users/hoanicross/.sdkman/candidates/maven/current/bin/mvnyjp
```

Comme vous pouvez le voir, j'ai donc utilisé la commande `ln -s` à outrance pour arriver à mes fins !

## Rendre le _wrapper_ disponible dans la variable d'environnement `PATH`

Il ne reste plus maintenant qu'à rendre ce merveilleux _wrapper_ disponible dans la variable d'environnement `PATH` en priorité.

On pourra notamment créer une variable d'environnement `MAVEN_HOME` pointant vers le répertoire d'installation du _wrapper_ et  on ajoutera `$MAVEN_HOME/bin` au tout début de la variable d'environnement `PATH`.

Cela pourra notamment être rendu possible en ajoutant les lignes suivantes à la fin de votre script d'initialisation de votre interpréteur de commande (`~/.bashrc` si vous utilisez bash ou `~/.zshrc` si vous utilisez zsh) :

```sh
export MAVEN_HOME=~/.wrapper/maven
export PATH=$MAVEN_HOME/bin;$PATH
```

Relancez votre terminal et vous voilà paré pour changer d'environnement de développement à la volée avec Maven 🤤 en utilisant la commande `mvn` comme à l'accoutumée...

## Limite de ce procédé dans les IDE

Ce _wrapper_ est surtout magique en ligne de commande.

Dans votre IDE, cela ne marchera pas de façon transparente.

J'ai notamment pu constater que dans IntelliJ IDEA, étant donné que ce dernier utilise une installation de Maven embarquée par défaut, le changement de fichier de configuration ne se fait pas automatiquement.

Néanmoins, il reste possible de configurer le fichier de configuration à prendre en considération pour le projet en cours, ce qui nécessite une manipulation à faire une seule fois pour chaque projet. Pour moi cette contrainte est largement acceptable !

J'en profite aussi pour définir, dans le champ consacré à cet effet, le répertoire d'installation de Maven comme étant le répertoire d'installation de mon _wrapper_.

## Et la suite ?

Dans mon prochain article, je vous expliquerais comment j'ai réussi à jongler avec mes identités et signatures Git en fonction des projets sur lesquels je travaile !

Si vous voulez savoir quand je sortirais un nouvel article, suivez-moi sur [LinkedIn](https://www.linkedin.com/in/hoanicross/) ou Dev.to !

J'aimerais aussi terminer en vous expliquant rapidement qui je suis 😄

Je m'appelle Hoani CROSS. Je suis originaire de Polynésie Française.

Passionné d'informatique depuis toujours (j'ai commencé à développer en CP, il y a environ 40 ans), j'ai évolué dans beaucoup d'environnements et occupé de nombreux poste, jusqu'à celui de DSI...

Aujourd'hui je suis manager et backend architect chez [SFEIR](https://sfeir.com), une Néo-ESN française. Une des particularités de SFEIR, c'est que les managers sont également des exécutants en mission chez des clients. On comprends ce que font les employés qu'on manage et croyez-moi, ça fait une sacré différence ! Et enfin, ce qui nous relie tous chez SFEIR, c'est la passion pour l'informatique... Étant donné qu'elle me suit depuis 40 ans, autant vous dire que je suis tombé au bon endroit !..

D'ailleurs, si vous êtes passionné comme moi, il y certainement une mission de rêve qui vous attends chez SFEIR alors [n'hésitez pas à me contacter](https://www.linkedin.com/in/hoanicross/) si vous voulez changer d'horizon.

Enfin, je suis aussi le propriétaire du blog [Geek & Rentable](https://geeketrentable.fr), que je vous invite à aller découvrir.

Dans ce blog, je souhaite accompagner les développeurs/développeuses ou autre acteurs/actrices de la tech, passionné.e.s mais malheureusement incompris.es, de manière à ce qu'ils ou elles puissent évoluer vers de bien meilleurs conditions de travail.

À très bientôt.

_[Illustration par Ang3llor3 sur DeviantArt](https://www.deviantart.com/ang3llor3/art/a-juggler-321507830)_