Un tunnel low cost

Besoin de tunnel

La dernière mode chez les développeurs, c’est d’exposer publiquement son environnement local, généralement une application web en cours d’écriture. De mon côté, il m’a été demandé de travailler sur un système de paiement dont la configuration nécessitait que ma machine de développement soit accessible au service de paiement.

Détail d’une maison alsacienne. Cliquer pour voir l’image en plus grand

Il m’a été proposé d’utiliser les tunnels Cloudflare, ou bien d’installer ngrok. Mais ces deux solutions, ainsi que leurs multiples alternatives, me semblaient «/overkill/» pour mon simple besoin. J’ai donc préféré m’amuser à implémenter une solution plus modeste et sur mesure, que je vais présenter ci-après.

Solution maison

Je contrôle déjà un serveur web qui est accessible publiquement sur internet. C’est le serveur web qui héberge le blog sur lequel vous êtes certainement en train de lire cet article. Mon objectif va être d’utiliser ce serveur comme proxy web sur un nom de domaine dédié, qui transmettra toutes les requêtes vers mon serveur de développement, qui lui tournera sur mon portable dans des containers configurés avec docker-compose.

Configuration DNS

La première étape consiste donc à faire pointer le nom de domaine sous lequel on veut rendre accessible notre environnement de développement vers ce serveur.

Par convention je vais utiliser les noms suivants:

  • my-web-server.net: le nom de domaine de mon serveur web public;
  • dev1.devs.my-web-server.net: le nom de domaine sous lequel l’environnement de développement sera accessible.

L’enregistrement DNS à créer ressemblera à quelque chose comme: *.devs.my-web-server.net CNAME my-web-server.net

Configuration Caddy

Mon serveur web utilise Caddy. Ici je vais utiliser deux fonctionnalités de cet outil particulièrement pratiques:

  1. en déclarant un nom de domaine ouvert sur le port 443, Caddy récupère automatiquement le certificat Letsencrypt!
  2. Un reverse proxy se configure en une seule ligne.

La section de ma configuration Caddy dédiée au tunnel ressemble à ceci:

dev1.devs.my-web-server.net:80,
dev1.devs.my-web-server.net:443 {
    log {
        output file /var/log/caddy/dev1-devs.log
    }
    reverse_proxy {
        to 127.0.0.1:8888
   }
}

Configuration Docker avec le certificat

Lorsque mon application web sera accessible depuis l’extérieur, Caddy va utiliser le certificat généré par Letsencrypt pour sécuriser la connexion. Mais lorsque j’accède à cette même application en local depuis mon portable, c’est au serveur web qui tourne dans Docker de s’en occuper. Pour cela ce serveur a aussi besoin des fichiers du certificat.

Ces fichiers (la clé publique et la clé privée) se trouvent dans le sous répertoire dev1.devs.my-web-server.net du répertoire /var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/ Il faut les copier en local, puis les insérer dans le container qui fera tourner le serveur web.

Dans le Dockerfile du serveur web on trouvera donc quelque chose qui ressemble à:

COPY certs/dev1.devs.my-web-server.net.crt /usr/local/apache2/conf/dev1.devs.my-web-server.net.crt
COPY certs/dev1.devs.my-web-server.net.key /usr/local/apache2/conf/dev1.devs.my-web-server.net.key

Dans mon cas particulier l’application en local est servie par un serveur apache. La configuration de ce serveur ressemble à ceci:

<VirtualHost _default_:443>
    DocumentRoot "/var/www/my-app/"
    ServerName dev1.devs.my-web-server.net

    SSLEngine on
    <Directory "/var/www/my-app/">
        AllowOverride All
        Require all granted
    </Directory>

    SSLCertificateFile "/usr/local/apache2/conf/dev1.devs.my-web-server.net.crt"
    SSLCertificateKeyFile "/usr/local/apache2/conf/dev1.devs.my-web-server.net.key"
    Protocols h2 http/1.1
</VirtualHost>

Modification du fichier /etc/hosts

Pour pouvoir travailler localement, sans passer par le proxy, il faut associer l’adresse IP du container apache au nom de domaine dev1.devs.my-web-server.net. On récupère cette adresse avec la commande suivante:

docker inspect \
     --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
    $(docker compose ps -q apache)

Connexion ssh

La dernière étape consiste à créer le tunnel, avec SSH, entre notre machine locale et le serveur web:

ssh -R 8888:127.0.0.1:8351 my-web-server.net

Ici on a:

  • 8351 le port ouvert de notre serveur web dans votre docker-compose;
  • 8888 le port qui est dans la configuration reverse proxy de Caddy.

Et voilà!

Aller plus loin

Il ne semble pas trop difficile de bricoler une solution qui automatise un peu toutes ces étapes. D’ailleurs c’est pour cela que j’ai déclaré le domaine *.devs.my-web-server.net dans ma configuration dns, au lieu de dev1.my-web-server.net. Ainsi je peux ajouter plus facilement d’autres domaines sans modifier mon serveur DNS. Je n’aurais ensuite que les modifications de la configuration Caddy et de docker à programmer. Pour le moment, avec un seul environnement à rendre accessible, je n‘ai pas besoin d’automatiser plus.

Post Scriptum

Après avoir écrit cet article, je découvre un projet qui met en œuvre exactement le même procédé que moi, avec un début d’automatisation.

Nouvel outil de traduction hors ligne

Le besoin

Vivant et travaillant en Allemagne, utilisant les langues anglaise et allemande de manière professionnelle, le français et l’allemand dans la sphère privée, je suis un utilisateur pas très modéré des outils de traduction en ligne.

Des classeurs de toutes les couleurs

Aussi l’annonce d’un outil de traduction hors ligne basé sur des travaux universitaires par Mozilla m’a enchanté. La description technique de la conception de l’outil est intéressante à lire.

Malheureusement l’outil n’est distribué que sous la forme d’une extension Firefox, et le dépôt git et les explications pour obtenir une application indépendante est indigeste. «Hillariously difficult to use» pour rependre le titre d’un rapport un bogue, dont une réponse pointe vers Kotki, un projet dérivé qui promet de simplifier l’utilisation de l’outil.

L’outil

L’installation de Kotki se déroule en deux partie. Au niveau système, en tant que root:

apt install -y cmake ccache build-essential git pkg-config \
    rapidjson-dev pybind11-dev libyaml-cpp-dev python3-dev \
    python3-virtualenv libopenblas-dev libpcre2-dev \
    libprotobuf-dev protobuf-compiler libsqlite3-dev
pip3 install kotki -v

En tant qu’utilisateur, il faut ensuite télécharger les «modèles» permettant de traduire d’une langue à l’autre:

mkdir -p ~/.config/kotki/models/
cd /tmp/
# Voir https://github.com/kroketio/kotki/releases
wget https://github.com/kroketio/kotki/releases/download/0.6.0/kotki_models_0.3.3-tiny.zip
unzip kotki_models_0.3.3-tiny.zip -d ~/.config/kotki/models

Personnellement je télécharge la version tiny du fichier, parce que le fichier contient les modèles de traduction dont j’ai besoin. Si vous êtes intéressé par le russe ou l’ukrainien, la version complète sera nécessaire:

wget https://github.com/kroketio/kotki/releases/download/0.6.0/kotki_models_0.3.3-tiny.zip
unzip kotki_models_0.3.3.zip -d ~/.config/kotki/models

Utilisation

Ça marche plutôt pas mal:

kotki-cli -i "Longtemps je me suis couché de bonne heure."
# For a long time I went to bed early.

kotki-cli -m enfr -i "For a long time I went to bed early."
# Pendant longtemps, je suis allé me coucher tôt.

kotki-cli  -i \
"Als Gregor Samsa eines Morgens aus unruhigen Träumen erwachte, \
fand er sich in seinem Bett zu einem ungeheueren Ungeziefer \
verwandelt."
# One morning, when Gregor Samsa awoke from troubled dreams,
# he found himself in his bed turning into a monstrous vermin.

kotki-cli -m ende -i \
"One morning, when Gregor Samsa awoke from troubled dreams,\
 he found himself in his bed turning into a monstrous vermin."
# Eines Morgens, als Gregor Samsa aus unruhigen Träumen erwachte,
# fand er sich in seinem Bett wieder, das sich in ein
# ungesäuertes Ungeziefer verwandelte.

Mais l’allemand, n’est pas encore très au point.

Une autre critique: l’anglais est pivot. Il n’existe pas (encore?) de modèle permettant de traduire de l’allemand vers le français ou du français vers l’allemand.

Utiliser l’historique Bash

Apperçu rapide

Bash propose des raccourcis bien utiles pour relancer des commandes, ou des bouts de commandes, précédemment exécutées. Cette page va recenser ceux que j’ai trouvés les plus utiles jusqu’à présent.

Quand le présent rejoint le passé, allégorie. Cliquer pour voir l’image en plus grand

Voici quelques exemples d’usage:

# Relance la dernière commande en la faisant précéder par sudo
sudo !!
# Opération inverse: relancer la dernière commande sudo sans sudo
!*
# Pour modifier le dernier fichier visualisé avec less
!less:s/less/vi/

Tableau récapitulatif

Et voici un tableau à garder sous la main en cas de besoin

!! dernière commande
!rm dernière commande qui commence par rm
!?force? dernière commande qui contient force
   
!$ dernier argument de la dernière commande executée
!^ premier argument de la dernière commande executée
!* tous les arguments de la dernière commande executée (synonymes: !!:1* !!:1-$ !!:*)
   
!scp$ dernier argument de la dernière commande qui commence par scp
!scp^ premier argument de la dernière commence qui commence par scp
   
:p affichage au lieu d’exécuter
:h dirname (head)
:t filename (tail)
:r enlève l’extension (peut être utilisé deux fois pour des fichiers tar.gz)
:e retourne l’extension seulement
   
^search^replace remplace search par replace dans la dernière commande
!rm:s/search/replace/ remplace search par replace dans la dernière commande commençant par rm
!rm:sg/search/replace/ remplace partout …
   
sudo !! relance la commande précédente avec sudo
chmod +x !$ un exemple parmi mille de l’utilisation du racourci !$
!scp^:h le chemin du fichier copié par scp (si le fichier était le premier argument à scp ie il n’y avait pas d’options)
   
!grunt:2-$:p affichage tous les arguments sauf le premier de la dernière commande commençant par grunt
   

Re-écrire un historique git

Contexte

Ça y est. Après des mois de travail appliqué, le projet est achevé: toutes les fonctionalités ont été implementées, la couverture de tests est satisfaisante, les scripts de déploiement vers les différents environnements fonctionnent.

Bref le client est satisfait et l’heure est venue de transmettre le projet. Cet fois-ci le client souhaite être autonome et continuer seul à exploiter et faire évoluer le logiciel. C’est son choix, c’est son droit, pas de problème.

Une dernière vérification sur le dépôt dévoile une petit souci: le script de déploiement, lors de la phase de construction du paquet, utilise un fichier auth.json utilisé par composer afin d’accéder à des composants php sous licence. Et ce fichier est enregistré dans le dépôt. Donc si le projet est livré en l’état au client, celui-ci continuera à avoir accès aux dépôts privés.

Porte ouverte sur un espace délabré. Cliquer pour voir l’image en plus grand

Le problème

Après consultation du département des ventes, nous apprenons que nous devons distinguer plusieurs cas:

  • il y a des composants pour lesquels le client a acquis les droits sur les mises à jour futures;
  • pour d’autres logiciels, seule la version disponible au moment de la livraison devra rester accessible;

Pour les premiers composants, les identifiants peuvent rester dans le fichier auth.json sans problème. Lorsque les futurs développeurs du projet lanceront la commande composer update les dernières versions seront alors téléchargées. En revanche pour les autres composants (un seul en fait) il va falloir faire autrement.

La méthode va être la suivante:

  1. retirer les clefs d’accès au dépôt du composant en question du fichier auth.json
  2. insérer «/en dur/» dans le dépôt git les sources du composant, et mettre à jour les fichiers composer.json et composer.lock en conséquent.

Nous allons nous simplifier la tâche: la deuxième partie (copie du composant dans le dépôt) sera mise en œuvre dans un nouveau commit. C’est un peu de la triche parce que cela signifie que le projet livré ne tournera pas ou ne pourra pas être construit avec les commits précédents le dernier. Néanmoins:

  1. c’est contractuellement Ok;
  2. cela fera à peine un peu plus de travail pour les nouveaux développeurs lorsqu’ils voudront utiliser un git bisect.

Quant à la première partie du nettoyage de notre dépôt git, nous allons devoir utiliser un outil diabolique: git filter-branch.

La solution

Cet outil est tellement diabolique que la documentation officielle vous suggère d’utiliser une alternative plus sûre comme filter-repo. Après avoir attentivement lu la documentation des deux outils, j’ai préféré utiliser le plus dangereux des deux, filter-branch. Pas par amour du risque, mais par besoin de simplicité.

Les deux outils permettent de re-écrire l’historique d’un dépôt git. Modifier l’historique d’un dépôt git n’est pas anodin. Une fois le serveur de référence mis à jour avec le nouvel historique, tous les développeurs devront re-cloner leur dépôt avant de pouvoir pousser à nouveau leurs modifications. Bien sûre, dans le cas décrit ici, cela n’est pas problématique, puisqu’il n’y aura que des nouveaux développeurs.

Alors que filter-repo permet essentiellement de modifier l’historique d’un dépôt git en supprimant, ajoutant ou modifiant des fichiers dans chaque commit, filter-branch permet d’exécuter une commande avant chaque commit. Dans notre cas, nous allons écraser le fichier auth.json avec la version contenant les clefs permises.

git filter-branch --tree-filter 'cp /tmp/auth.json src/auth.json' HEAD

Et voilà!

À noter:

  1. tous vos commits ont maintenant des identifiants différents;
  2. le fichier src/auth.json a été automatiquement ajouté au premier commit, même si cela n’était pas le cas avant.

Mon projet comportait quelques centaines de commits. La commande n’a pas mis plus de quelques minutes pour s’exécuter sur un ordinateur type desktop.

Encore une fois, la commande git filter-branch est à utiliser avec parcimonie, et uniquement en dernier recours.

Astuces de printemps

Astuces de printemps / aka nouvelle fournée de Petits Tips

D’après le calendrier c‘est le printemps. Il est temps de publier une nouvelle liste de petits trucs. Dorénavant j’appellerai ces articles «p'titstips».

Parterre de fleurs

Supprimer le canal alpha des images

À cause d’un bug dans une librairie utilisée dans un projet, toutes les images avec un fond transparent apparaissaient avec un fond noir. En attendant une version corrigée de la librairie, il a été décidé de mettre un fond blanc aux images avec transparence. Voici la commande qui réalise cette transformation:

convert -resize 200 -background white -alpha remove pic-with-apha.png pic-with-white-bg.png

Docker

Lorsque l’hébergeur de votre site vous a mis à disposition une base de donnée dans un container, mais que le container n’est pas démarré automatiquement lorsque le serveur redémarre, il faut changer la configuration du container comme ci-dessous.

docker update --restart always mysql-server-container

Redirection de port à l’intérieur d’un container

Imaginons une application web dans un container, dont le port externe au container 8092 est redirigé vers le port interne 80. Notre application est configurée pour être appelée sur l’adresse 127.0.0.1:8092. Mais lorsque l’application s’appelle elle-même depuis l’intérieur du container, le port 8092 n’est pas atteignable. Il faut donc créer une redirection en utilisant socat par exemple:

socat tcp-l:8092,fork,reuseaddr tcp:127.0.0.1:80

Convert mp4 into gif

Comme souvent une image veut mieux qu’un long discours, et en description de bug, une vidéo vaut mieux qu’une image. Vokoscreen est donc votre ami pour les déclarations de bug, cet outil vous permettant d’enregistrer ce que vous faites à l’écran. Et lorsque vous avez enregistré votre bug mais que le formulaire du service client pour ouvrir un ticket n’accepte que le format gif, alors ffmpeg vient à la rescousse.

ffmpeg \
  -i vokoscreenNG-2021-04-18_12-25-57.mp4 \
  -r 15 \
  -vf "split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
  bug-live-test-credit-card.gif

Background dynamique

Vous n’arrivez pas à choisir votre fond d’écran? Ne choisissez pas, et changer le fond d’écran toutes les cinq minutes.

while true ; do  for f in `ls *|sort -R` ; do feh --bg-max $f ; sleep 300  ; done; done

(n’a pas été testé avec autre chose qu’Awesome)