Planet Asrall

July 07, 2019

Luc Didry

Mise à jour du firm­ware d’un Rasp­berry Pi 4

J’aime bien mon Rasp­berry Pi 4, mais il a une fâcheuse tendance à chauf­fer. Heureu­se­ment, il y a une mise à jour du firm­ware qui fait gagner quelques degrés et augmente les perfor­mances 🙂

Comme c’est désor­mais la règle pour mes tutos, j’ai publié comment faire sur mon wiki : https://wiki.fiat-tux.fr/admin:systeme:maj-firm­ware-rasp­ber­ry­pi4.

Crédits : Photo par Laura MacNeil sur Unsplash

by Luc at July 07, 2019 07:17 AM

July 04, 2019

Luc Didry

Fédé­rez votre Word­press !

TL; DR: vous pouvez suivre et commen­ter ce blog via @luc@­fiat-tux.fr via la fédé­ra­tion Acti­vi­tyPub, appe­lée aussi Fedi­verse.

Le (ou la, c’est pas vrai­ment bien défini) fédi­verse, qu’est-ce que c’est ? C’est l’en­semble des services web qui causent entre eux via le voca­bu­laire Acti­vi­tyPub. Cela permet de faire commu­niquer deux serveurs de micro-blog­ging comme Masto­don et Pleroma ensemble. Mais encore mieux, cela permet de commen­ter une vidéo PeerTube depuis Masto­don.

Il existe déjà des moteurs de blog fédé­rés comme Plume et j’es­père bien­tôt Diffu mais quit­ter le Word­press sur lequel j’ai mes archives, mes habi­tudes, mes exten­sions confi­gu­rées aux petits oignons… c’est diffi­cile, surtout quand il n’y a rien pour impor­ter un export Word­press.

Qu’à cela ne tienne ! Il existe des exten­sions Word­press pour fédé­rer votre blog : Acti­vi­tyPub et Ptero­type. Ne me deman­dez pas la diffé­rence entre les deux, je vous laisse lire ce qu’en dit le déve­lop­peur de l’ex­ten­sion Acti­vi­tyPub.

J’ai donc installé Acti­vi­tyPub et ça fonc­tionne nickel sans rien faire. La preuve ? J’ai installé l’ex­ten­sion avant de publier mon précé­dent article, je me suis abonné à @luc@­fiat-tux.fr avec mon compte Frama­piaf et j’ai pu voir appa­raître l’ar­ticle dans ma time­line. J’ai commenté depuis Frama­piaf et ça fonc­tionne comme par magie 😁

À part le fait que l’image d’en-tête de l’ar­ticle a été dupliquée, c’est juste parfait, je vous encou­rage à essayer cette exten­sion ! 🙂


EDIT : Appa­rem­ment, le fait de confi­gu­rer le plugin pour ne publier qu’un extrait de l’ar­ticle fout en l’air la mise en page. Quant aux adresses comme @luc@­fiat-tux.fr, elles ne semblent pas être trans­for­mées en lien vers le profil fédéré. C’est dommage.

Crédits : Photo par Alina Grub­nyak sur Unsplash

by Luc at July 04, 2019 08:15 PM

Nouveau thème pour le blog !

Ça fait un bout de temps que je n’ai pas touché au thème de ce blog (depuis 2013, 6 ans quand même !) et je me suis dit que je chan­ge­rais bien.

J’avais deux besoins :

  • un thème sombre (parce que j’aime bien les thèmes sombres, telle­ment que j’uti­lise l’ex­ten­sion Dark Reader sur mon navi­ga­teur, qui permet de donner un thème sombre à quasi­ment n’im­porte quelle page web avec un résul­tat bluf­fant) ;
  • un thème léger : à l’heure des pages web obèses (coucou lemonde.fr et tes 2Mio, dont ±1,7Mio de javas­cript et de trackers (sans décon­ner, faites le test avec uMatrix ou en désac­ti­vant le javas­cript)).

Je suis vite tombé sur le thème Gene­ra­teP­ress qui est vrai­ment sympa : il y a pas mal d’op­tion de person­na­li­sa­tion et la possi­bi­lité d’ajou­ter des règles CSS person­na­li­sées. Donc j’ai basé mon nouveau thème dessus.

J’ai souhaité en faire un thème enfant pour chan­ger le footer qui contient la licence du blog (CC-0 🙂) mais je n’ar­ri­vais pas à voir mon thème enfant dans les thèmes instal­lés donc tant pis, j’ai modi­fié le thème direc­te­ment, à la barbare. Faudra juste que je remette mes modifs à chaque mise à jour.

J’ai aussi installé l’ex­ten­sion Autop­ti­mize qui permet de conca­té­ner et compres­ser fichiers CSS et javas­cript.

J’ai pu consta­ter, sur la page d’ac­cueil, un passage d’un poids — hors images — de près de 700Kio à envi­ron 200Kio, ce qui nous fait un gain de près de 70% 😁


Tiens, tant qu’à parler de blog, je vous encou­rage à aller voir la campagne de finan­ce­ment du logi­ciel Diffu sur Ulule : https://fr.ulule.com/diffu/. Diffu est un moteur de blog qui sera fédéré (donc ça causera avec Plume, Masto­don, tous­sa…) qui sera instal­lable sur des héber­ge­ments mutua­li­sés donc acces­sible au plus grand nombre. Il y a un mockup sur https://mockup.diffu.social/.

Crédits : Photo par Taelynn Chris­to­pher sur Unsplash

by Luc at July 04, 2019 06:50 PM

June 21, 2019

Luc Didry

Migra­tion d’une version majeure de Post­greSQL à une autre (pour Debian Buster)

Aver­tis­se­ment Ceci est une réac­tua­li­sa­tion d’un [précé­dent article][1]. J’en avais marre de devoir chan­ger les numé­ros de version dans mes copier/coller (quoi, vous ne croyez quand même pas que je me souviens de ces commandes par cœur ?), donc je réac­tua­lise l’ar­ticle, ce sera plus confor­table pour tout le monde.

Avec la sortie de Buster, c’est Post­greSQL 11 qui est proposé. Mais comment faire pour migrer les bases de données du clus­ter 9.6 (qui ne rece­vra plus trop d’up­dates) vers le 11 ? Ce n’est pas très compliqué.

Aver­tis­se­ment Cet article est main­te­nant dispo­nible sur mon wiki à l’adresse https://wiki.fiat-tux.fr/admin:bdd:pgsql:migra­tion-version-majeure.
Le wiki contient des infor­ma­tions impor­tantes en plus et sera mis à jour. Pour cette raison, j’ai supprimé cet article pour que personne ne fasse d’er­reur. Rendez-vous donc sur le wiki 😉

by Luc at June 21, 2019 03:09 PM

May 18, 2019

Luc Didry

Une page wiki pour Rspamd

Rspamd est un super anti­spam qui ne fait pas que ça. Je l’uti­lise sur mon serveur perso et chez Frama­soft.

Par contre la doc est assez complexe, et tout n’y est pas, ou pas forcé­ment de façon simple.

J’ai donc commencé à écrire un article sur Rspamd dans mon wiki : https://wiki.fiat-tux.fr/admin:mail:rspamd.

J’ai préféré écrire dans mon wiki plutôt qu’ici parce que c’est plus simple pour retrou­ver la page, ainsi que pour la modi­fier 🙂 (je n’avais pas arrêté de chan­ger des trucs dans mon article sur bitwar­den, c’était assez pénible)

Crédits : Photo par Samuel Zeller sur Unsplash

by Luc at May 18, 2019 09:01 AM

April 23, 2019

Romain Dessort

Carte des pistes de ski des Laurentides

Après 4 hiver à skier et cartographier les pistes de ski nordique des Laurentides, je me devais de produire une carte de tout ça. C'est maintenant chose faite avec ces 2 cartes des Laurentides :

Ce sont les premières cartes à couvrir tout le secteur des Laurentides, et à être plus complètes que les cartes locales des différents organismes, qui ne montrent pas certaines des pistes historiques.

Pour générer ces cartes, j'ai utilisé QGIS, puissant outil libre de SIG mais qui reste assez complexe à maitriser pour quelqu'un comme moi qui manque clairement de connaissance dans ce domaine.

Voici les sources du projet QGIS.

J'explique ci-après les étapes en gros que j'ai suivi pour construire la carte dans QGIS.

Fond de carte

Pour les données topographiques, j'adore le rendu de OpenTopoMap pour leur précision et leur qualité graphique. J'ai donc connecté leur serveur WMS dans QGIS pour avoir leurs tuiles en fond de carte.

Récupération des données relatives aux pistes de ski nordique

Le but ici est de récupérer les données brutes de OpenStreetMap relatives aux pistes de ski nordique afin d'avoir des données vectorielles sur lesquelles travailler.

QGIS, via le plugin QuickOSM, peut interroger directement l'API Overpass pour récupérer ce qui nous intéresse. On va donc récupérer plusieurs choses à l'aide de requêtes Overpass API :

  • tous les sentiers. Ils sont déjà rendus sur les tuiles OpenTopoMap mais ça permet de les mettre un peu plus en valeur :

    way["highway"="path"]({{bbox}});
    out geom;
    
  • les relations ayant pour tag piste:type=nordic :

    relation["piste:type"="nordic"]({{bbox}});
    out geom;
    
  • les chemins (dans le vocabulaire de OpenStreetMap) ayant pour tag piste:type=nordic :

    way["piste:type"="nordic"]({{bbox}});
    out geom;
    
  • et enfin les chemins qui ont pour tag piste:type=nordic mais n'étant dans aucune relation ayant pour tag piste:type=nordic. C'est une astuce qui va être importante pour la suite. Plus compliqué (merci les nombreux exemples de requêtes Overpass API) :

    // On récupère les chemins avec piste:type=nordic et on stocke ça dans
    // le set .all
    way({{bbox}})["piste:type"="nordic"]->.all;
    
    
    // On récupère les relations pour lequelles au moins un de leur chemin
    // membre est présent dans le set .all
    rel["piste:type"="nordic"](bw.all);
    // et pour ces relations, on récupère tout leurs chemins membres dans
    // .members
    way(r)->.members;
    
    
    // Il ne reste plus qu'à faire la différence des 2 sets
    ( .all; - .members; );
    
    
    out geom;
    

QuickOSM stocke le résultat de chaque requête dans des calques séparés. On voit déjà que des mêmes données se retrouvent dans plusieurs calques (la 3ème requête inclue forcement les données de la 4ème et beaucoup de chemins avec piste:type=nordic ont aussi le tag highway=path. Idéalement j'aurai rajouté des exclusions dans les requêtes, mais je suis allé au plus simple, pourquoi s'embêter avec des requêtes Overpass quand il suffit de changer l'ordre d'affichage des calques.

J'ai donc dans l'ordre (du plus bas au plus haut) :

  • le calque de fond de carte OpenTopoMap
  • le calque avec les chemins avec highway=path
  • le calque avec les relations avec piste:type=nordic
  • le calque avec les chemins avec piste:type=nordic qui ne font parti d'aucune relation (mais dont le tracé est masqué, voir la suite)
  • le calque avec les chemins avec piste:type=nordic

Maintenant il s'agit d'ajouter des styles à tout ça

Pour reprendre ce qui se fait de plus commun au Québec, les pistes de ski de fond tracées seront en trait plein et avec le code de couleur représentant leur niveau de difficulté (vert/bleu/noir). Les pistes de ski nordique (communément appelé « hors-piste ») seront en rouge et en tirets. Les autres sentiers seront noir en tirets avec une épaisseur légèrement moindre que les autres (ils sont moins importants). Les relations quant a elles ont une couleur rouge pale. Ça ne se verra pas dans la plupart des cas car par dessus il y a la couleur de la piste de ski (appliquée sur les calques des chemins, sauf dans le cas où la piste que représente la relation traverse un lac ou suit une route : dans ce cas il n'y a pas de chemin avec piste:type=nordic. Ça permet de bien différencier là où il y a une vraie piste des parties qui ne sont pas forcement skiables (routes) ou qu'il n'y a pas de tracé définit (lacs).

Pour les 2 premiers calques (de l'ordre évoqué précédemment), c'est simple on joue avec les options de style de QGIS. Pour le 3ème, on ne veut pas les afficher donc on les mets en transparence totale ou largeur de 0 px (c'est toujours à cause de la fameuse astuce, j'y reviens, j'y reviens !).

Et enfin pour le 4ème calque, pour définir le style en fonction du type de piste et du niveau de difficulté (pour les pistes tracées), on va dire à QGIS de gérer le style suivant 5 règles, que voici :

"piste:grooming" =  'backcountry'
("piste:grooming" like '%classic%' or "piste:grooming" like '%skating%') and "piste:difficulty" = 'easy'
("piste:grooming" like '%classic%' or "piste:grooming" like '%skating%') and "piste:difficulty" = 'intermediate'
("piste:grooming" like '%classic%' or "piste:grooming" like '%skating%') and ("piste:difficulty" = 'advanced' or "piste:difficulty" = 'expert')
"piste:grooming" = 'scooter'

Puisqu'on a accès à tous les attributs OSM dans les données vectorielles importées, c'est relativement simple.

Nom et référence des pistes

Ensuite vient la partie où il faut placer le nom et/ou la référence (en fonction de qu'est ce qui est présent dans les attributs OpenStreetMap) sur les pistes. C'est là que j'ai bloqué pendant longtemps car, au niveau de OpenStreetMap, c'est un peu le bordel : une piste peut être créée par une relation qui assemble plusieurs chemins. Dans ce cas le nom et la référence (numéro ou lettre identifiant la piste en pratique) de la piste sont portés par les attributs name et ref de la relation. Une piste peut aussi ne pas avoir de relation, et être constituée d'un ou plusieurs chemin indépendants. Dans ce cas le nom et la référence peuvent être portés par les attributs name et ref des chemins où, si ces attributs existent déjà et qu'ils ne reflètent pas les bonnes valeurs, par les attributs piste:name et piste:ref (c'est le cas par exemple si la piste de ski emprunte un sentier de vélo ou de marche qui est nommé différemment).

Mon problème sur lequel j'ai luté pendant un moment était pour savoir si un chemin faisait parti d'une relation ou nom via le langage de requête de QGIS. En effet, une fois les données OSM importées, on perd leur structuration. La solution qu'on m'avait proposée sur StackOverflow consistait à utiliser des fonctions géométriques pour voir si le chemin donné se superposait à la relation donnée, mais je ne pouvais pas faire ça simplement pour chacune des relations à tester. D'où l'idée que j'ai eu de récupérer ce fameux calque supplémentaire : les chemins qui ne font partie d'aucune relation. Ainsi au niveau de QGIS c'est bien plus simple : j'affiche les noms pour le calque des relations ainsi que pour le calque des chemins n'ayant pas de relations. Le calque qui contient tous les chemins, lui, je ne lui fait afficher aucun nom.

Pour chacun des 2 calques, Il reste quand même à savoir quels attributs afficher parmi name, ref, piste:name et piste:ref mais ça se fait simplement avec le langage de QGIS.

Pour les relations :

case
    when name <> '' and ref <> '' then
        name || ' (' || ref || ')' 

    when ref <> '' then
        ref

    when name <> '' then
        name

end

Pour les chemins :

case
    when ("name" <> '' or "piste:name" <> '') and ("ref" <> '' or "piste:ref" <> '') then
        if("piste:name" <> '', "piste:name", "name") || ' (' || if("piste:ref" <> '', "piste:ref", "ref") || ')' 

    when "ref" <> '' or "piste:ref" <> ''then
        if("piste:ref" <> '', "piste:ref", "ref")

    when "name" <> '' or "piste:name" <> '' then
        if("piste:name" <> '', "piste:name","name")

end

On a maintenant les noms et/ou les référence des pistes qui s'affichent, et sans conflit. J'ai du jouer beaucoup sur les options d'affichage de QGIS car par défaut si la ligne est trop courbe dans tous les sens ou qu'il y a déjà trop d'information à un endroit, le moteur de QGIS n'affiche pas le nom. Pour plus de lisibilité, mais du coup je me retrouvais avec beaucoup de piste qui n'étaient pas identifiées.

Composition des cartes

Enfin c'est le moment de générer l'aperçu de la carte, via le module de composition de QGIS. Je définis la zone géographique que je veux imprimer, fixe une échelle, y ajoute des objets de texte pour le titre, les sources de données utilisées, une barre d'échelle, etc… QGIS permet de générer automatiquement une légende en fonction des règles de style qu'on a définit précédemment, ce qui est très pratique !

J'ai décidé de générer 2 cartes (secteur nord et secteur sud) pour économiser de la place sur le papier, car il n'y a pas beaucoup de pistes entre les 2.

J'aurai aimé pouvoir imprimer cette carte sur du grand papier pour pouvoir la donner à des collègues de ski. Pour avoir une échelle de 1:50 000, ça m'aurait pris environ une surface de papier de 1 m × 1 m. Mais après quelques devis, les prix sont vraiment chers et je ne suis pas non plus 100 % satisfait du rendu graphique (ni certain du résultat une fois imprimé !) donc j'ai plutôt décidé de découper ça en feuille A4 avec posterazor.

Améliorations

Il y en a beaucoup !

Déjà construire cette carte a permis de détecter pas mal de petites erreurs dans OpenStreetMap sur les données utilisées, je n'ai clairement pas tout corrigé, au delà du fait qu'il y a toujours des pistes incomplètes à aller explorer.

Je ne suis pas non plus très satisfait du placement automatique des noms des pistes par QGIS, bien que j'ai pas mal bidouillé ses réglages. La solution serait probablement de placer le nom des pistes à la main pour chacune d'elles, pour ainsi les mettre à l'endroit qui me semble le plus naturel.

Mais surtout je ne peux malheureusement pas considérer mes cartes générées comme de réelles cartes topographiques car j'ai du faire plusieurs approximations :

  • la projection utilisée est la pseudo-mercator, projection utilisée par OpenTopoMap sur lequel je me base et typiquement utilisé pour le rendu des cartes sur le web. Cette projection fait que l'échelle varie en fonction de la latitude et donc pas adaptée pour une carte topographique. Je devrais plutôt utilisé la projection UTM (zone 18 N pour la région qui m'intéresse ici), qui n'introduit pas de distorsion d'échelle et est utilisé comme projection officielle au Canada. Cependant ça aurait voulu dire regénérer toutes les tuiles avec le style OpenTopoMap avec la bonne projection, donc beaucoup de travail
  • la deuxième approximation est beaucoup plus classique et est au niveau de la sortie en PDF st du fait que je découpe le gros fichier en plusieurs A4, je n'arrive pas à garantir que l'échelle est correctement conservée entre le moment où je produits le PDF dans QGIS et où j'imprime les feuilles A4.

Mis à part ces problématiques, j'espère que cette carte pourra servir aux autres skieurs pour découvrir des nouvelles pistes dans les Laurentides !

April 23, 2019 09:22 PM

April 21, 2019

Romain Dessort

Using molecule with GitLab CI

This article follows this one where I talk about verifying and testing your Ansible roles with molecule.

As I wrote in this first article, I also configured GitLab CI to automatically run molecule tests after each push to the repository. A good documentation to start with is the official GitLab CI/CD documentation.

Here is our .gitlab-ci.yml (latest version) used on our ansible repository hosting all our roles:

---
image: quay.io/ansible/molecule:latest
services:
  - docker:dind

stages:
  - tests

before_script:
  - docker -v
  - python -V
  - ansible --version
  - molecule --version

molecule-role-common:
  stage: tests
  tags:
    - docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    PY_COLORS: 1
  script:
    - cd roles/common/
    - molecule test
  only:
    changes:
      - roles/common/**/*

molecule-role-monitoring-tools:
  stage: tests
  tags:
    - docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    PY_COLORS: 1
  script:
    - cd roles/monitoring-tools/
    - molecule test
  only:
    changes:
      - roles/monitoring-tools/**/*
[‌]

Our repository is hosted on gitlab.com and even if we can connect our own GitLab Runners to gitlab.com instance, we decided to use the provided gitlab.com's shared runners to get started quickly.

Those use Docker to run the jobs. So we instantiate the official molecule image (from Red Hat's registry):

image: quay.io/ansible/molecule:latest

And since molecule also needs to spawn Docker containers, we need to start another container: docker:dind (Docker in Docker). This is done with:

services:
  - docker:dind

And by exporting the DOCKER_HOST variable to the jobs, so that molecule will be able to connect to the Docker daemon to manage its containers:

variables:
  DOCKER_HOST: "tcp://docker:2375"

Then we define one stage, called tests:

stages:
  - tests

Each jobs related to this stage will have the stage: tests key in their definition.

The before_script block is here for debugging purpose (if the molecule tests succeed locally bt fail on the runners, it may be because the tests are run with a different version of molecule).

Last, we define as many jobs as we have roles to test with molecule. Here, tags is important since it is used by GitLab CI to select the runner the job will be executed on. We need a runner with Docker, thus:

tags:
  - docker

With script keyword we specify each command the runner should run. Pretty self-explanatory:

script:
  - cd roles/monitoring-tools/
  - molecule test

And only adds kind of a condition to the job. We don't want to run all jobs if, for instance, someone updates the README file. The job should run only if a commit introduces a change to a file in the role itself. So we restrict the execution to a change under the role's hierarchy only with:

only:
  changes:
    - roles/monitoring-tools/**/*

For instance, this pipeline, triggered by the commit afe5929c, has spawned only one job, the molecule-role-firewall test, since only the firewall role was modified. Whereas this commit (which impacted multiple roles) triggered 7 jobs (and yes, one has failed).

If a job fails, the commiter will receive a notification by e-mail with the commit and job ID. We can check the job's output on GitLab, to see why it has failed.

As an ending note, to test your .gitlab-ci.yml without pushing, you can install the gitlab-runner package and run it on your repository (you still need to commit your changes since `gitlab-runner will checkout the repository):

$ gitlab-runner exec docker <your job name>

April 21, 2019 01:40 AM

April 20, 2019

Romain Dessort

Containerize ZNC with systemd-nspawn

On my previous server, I had an IRC client (weechat) running in a screen session. I recently migrated this setup to a ZNC bouncer running on my server + weechat running on my laptop, mainly to avoid network lags through SSH and to have a better desktop integration (notifications, etc.).

My server hosts my e-mails, important and confidential documents (among other things) and I always thought that running an always connected IRC client on the same machine was a bad idea.

So in the same time I got rid of my weechat+screen to migrate to ZNC, I had a look at systemd-nspawn, also known as systemd containers. It's actually pretty much like LXC containers, but managed with the systemd logic in mind.

Here is a quick tutorial of how I containerized ZNC into a systemd container on a Debian stretch system:

  1. First, install systemd-container and debootstrap packages:

    # apt install systemd-container debootstrap
    
  2. Create directories used by systemd-container (which aren't create on install):

    # mkdir /var/lib/machines/ /etc/systemd/nspawn/
    

    /var/lib/machines/ will host the containers' hierarchy and containers unit files will be stored into /etc/systemd/nspawn/.

  3. Install a Debian system + znc package using debootstrap into /var/lib/machines/znc/:

    # cd /var/lib/machines/
    # debootstrap --include znc stretch znc
    
  4. To make the container start on boot, you have to enable the corresponding systemd-nspawn instance and the machines target (which will start all enabled instances of systemd-nspawn unit:

    # systemctl enable systemd-nspawn@znc machines.target
    
  5. At this stage, your container is ready. You can start your container (run its init process) with:

    # systemd-nspawn -D /var/lib/machines/znc/
    

    Or execute a command in the container:

    # systemd-nspawn -D /var/lib/machines/znc/ hostname
    

    systemd-nspawn comes with the machinectl command which allows you to easily manage your containers:

    # machinectl [list|list-images|start|stop|status|…]
    

    See its man page for all supported sub-commands and options.

  6. Now, let's configure some few extra stuff to run ZNC. Create a dedicated user in the container:

    # systemd-nspawn -D /var/lib/machines/znc/ useradd -u 1002 -g 1002 -m znc
    
  7. And customize the container's options by putting a config file into /etc/systemd/nspawn/:

    [Exec]
    # Don't start the init process inside the container, instead execute
    # `znc`
    Boot=off
    Parameters=/usr/bin/znc --foreground
    
    
    # Drop all default capabilities: the container will run with no
    # capabilities, ZNC doesn't need any
    DropCapability=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_FSETID CAP_IPC_OWNER CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETGID CAP_SETFCAP CAP_SETPCAP CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_PTRACE CAP_SYS_TTY_CONFIG CAP_SYS_RESOURCE CAP_SYS_BOOT CAP_AUDIT_WRITE CAP_AUDIT_CONTROL
    
    
    # Start `znc` as znc
    User=znc
    
    
    # Those 2 options aren't supported by the version of systemd shiped
    # with Debian stretch. Ephemeral=on makes the container discard on
    # shutdown any modification made during its runtime (Docker style) and
    # NoNewPrivileges ensures that the code executed inside the container
    # won't be able to gain greater privileges (with setuid bit for instance)
    #Ephemeral=on
    #NoNewPrivileges=on
    
    
    # Use private user namespace (equivalent to LXC's unprivilegied mode)
    PrivateUsers=on
    
    
    [Files] 
    # Mount my .znc configuration directory onto the container (with write
    # access) and the znc.pem containing the private key, certificates
    # chain and DH param (read-only)
    Bind=/home/romain/.znc/:/home/znc/.znc/
    BindReadOnly=/var/lib/acme/live/irc.univers-libre.net/combined:/home/znc/.znc/znc.pem
    
    
    [Network]
    # Use the same network stack as the host
    VirtualEthernet=no
    

    This file will automatically be read by systemd-nspawn before starting the container. All these options can be specified as commandline parameters to systemd-nspawn as well.

Resources:

April 20, 2019 04:03 AM

April 16, 2019

Romain Dessort

Ancien réseau de ski à Saint-Donat

Bien qu'assez éloigné des autres pistes nordiques de la vallée de la Rivière du Nord, on peut retrouver aussi des vielles pistes abandonnées aussi loin que Saint-Donat. Le massif de montagne entre le lac Ouareau, Saint-Donat et l'Interval hébergeait visiblement beaucoup de pistes de ski. La plus connue et celle qui a le mieux survécue à l'épreuve du temps est la Kaaikop qui monte au Mont Kaaikop depuis la 329 en passant par l'auberge de l'Interval. Un petit réseau de pistes toujours entretenues existe aussi autour de cette auberge mais je ne le connais pas vraiment.

Mais plus au nord-est, j'avais découvert en y allant l'été plusieurs sentiers bien envahis mais assez larges pour être des pistes de ski (lui, lui et lui notamment. Sur certains, il restait même quelques panneaux avec le numéro de la piste, des « attention pente raide » et des « sens unique », preuve que ça devait même être un réseau damé et tracé. Certaines balises portaient le nom La Réserve, station de ski alpin des environs, c'est donc possible qu'à une époque ce centre avait un réseau de ski nordique qu'il a ensuite complètement abandonné au profil du ski alpin…

Aujourd'hui, seules quelques pistes sont encore entretenues officiellement par le club de plein air de Saint-Donat qu'on peut voir sur leur carte.

J'y suis retourné la semaine passée, après la dernière neige de la saison avec 2 autres collègues de ski, dans le but de rejoindre le Mont Kaaikop depuis Le Grand R via une des pistes abandonnées. Je la connaissais en partie pour l'avoir faite l'été, mais je l'avais perdue à un moment donnée. En début d'hiver, j'avais repéré une intersection sur un autre sentier où c'était très probable qu'elle arrive là, je voulais donc confirmer ça. Finalement on a manqué une intersection et pas pris le sentier que je voulais… mais on en a découvert un autre ! À peu près parallèle à celui que je voulais retrouvé. J'ai aussi remarqué le départ d'un autre sentier (encore un) qui partait plein est.

Bref il reste beaucoup à explorer dans ce secteur encore, j'ai rajouté plus de fixme que complété des sentiers dans OpenStreetMap…

Par contre j'ai pu mettre la main sur un vieux plan des piste de Saint-Donat datant de 1981, édité à l'époque par le club de ski de fond des Panaches (qui n'existe plus). Le secteur dont je parle ici est celui du Lac Bouillon. Mais on voit qu'il y avait des pistes pas mal partout autour de Saint-Donat.

Certains des pistes que j'ai trouvé apparaissent effectivement sur le plan, mais d'autres non, probablement plus récentes.

April 16, 2019 06:26 PM

April 12, 2019

Romain Dessort

Yoghourt maison

Des fois il y a des trucs tellement simple à faire qu'on peut se demander pourquoi le monde ne les font plus eux-mêmes. Les yoghourts par exemple.

Un couple qui m'avait hébergé en Colombie-Britannique faisait son propre yaourt et m'avait montré comment faire. Du coup maintenant j'ai pris l'habitude de faire le mien (et d'en remanger aussi, parce que ça faisait longtemps que je n'en achetais plus).

Les avantages que je vois à faire son propre yaourt :

  • moins de déchet, surtout s'il est possible de trouver du lait en bouteille consignée (même si au Canada c'est moins pire, on ne trouve pas des pots de yoghourt individuels, seulement des pots de 0,5 ou 1 L)
  • plus économique (1 L de lait est généralement moins cher que 1 L de yoghourt)
  • le plaisir de faire son propre yoghourt
  • permet de conserver du lait plus longtemps si votre bouteille est due pour bientôt

La marche à suivre est assez simple, en fait il n'y a quasiment rien à faire. Je peux résumer ça à :

  • vider une bouteille de lait dans une casserole et le faire bouillir
  • le laisser tiédir et mélanger avec le fond de votre yoghourt précédent
  • transvaser dans des pots et les conserver au chaud le plus longtemps possible, puis mettre au frigo

Mais ça mérite quand même quelques explications de plus :

Au départ il faut trouver des bactéries lactiques actives, soit en pharmacie soit en supermarché, sous forme de « Yogourt Starter ». D'après des tests, utiliser des yaourts existants du commerce ne marche pas, même des yaourt dit contenant des ferments actifs, un bon test pour valider (ou pas) ce qui est écrit sur les pots de yaourt…

Ensuite il faut du lait (entier tant qu'à faire, mais allégé marche aussi).

Commencez par faire bouillir le lait pour le stériliser (en le surveillant car ça monte vite, prévoyez une grande casserole) et laissez le refroidir tranquillement jusqu'à ce qu'il soit tiède (trempez un doigt dedans, ça doit être chaud mais pas brulant).

Stérilisation du lait

À ce moment là ajoutez les bactéries actives et mélangez bien puis transvaser dans des pots type Mason (ou n'importe quels pots de récup en fait).

Le but maintenant est de garder les pots tièdes (normalement autour de 40 °C mais on est pas à 5 °C près) pendant 4 ou 5h minimum, le temps que les bactéries travaillent. C'est à ce moment là qu'il faut faire preuve d'un peu de créativité. Personnellement je les mets chacun systématiquement dans un gros bas de laine pour mieux garder leur chaleur. Ensuite, l'hiver je les pose sur un radiateur puis je les recouvre d'une polaire. Sinon ils vont dans le four encore chaud, derrière une vitre au soleil, sous la couverture et je dors avec (oui oui !)… Vous pouvez aussi récupérer des petites résistances chauffantes et les mettre dans un vêtement, puis les pots dedans. Bref plein de possibilités.

Yogourt prêt à fermenter dans un bas de laine

Je les laisse comme ça une nuit puis je les mets au frigo ensuite pour stopper le processus (plus les bactéries travailleront, plus le yaourt sera ferme mais acide).

Sinon il y a bien sûr des… trucs qui s'appellent des yaourtières (qui devraient être efficaces), spécialement conçus pour encombrer vos placards de cuisine, puis vos (nos) poubelles quand, tôt ou tard, ils ne marcheront plus.

Le yaourt dans les pots se conservent un ou deux mois au frigo sans problème. Un fois un pot ouvert, je peux le conserver plusieurs semaines. Probablement plus en fait, mais ça m'ait jamais arrivé d'avoir un yoghourt qui a viré mauvais.

Yogourt maison Miam !

Et enfin, pour refaire une autre batch, au lieu des bactéries sèches du départ, il suffit de prendre le fond du pot qui vous reste et le mélanger au nouveau lait tiédi (genre l'équivalent de 3 ou 4 cuillères à soupe, au pif). Au bout d'un moment, si le yoghourt a du mal à prendre, c'est que votre culture de bactéries s'épuise et il suffit de repartir avec de nouvelles.

April 12, 2019 09:40 PM

April 05, 2019

Romain Dessort

Automatically generate a diagram of your infrastructure from Ansible inventory

Another post about Ansible. This time I will talk about generating a representation of your infrastructure, for documentation purpose, using your Ansible inventory and gathered facts.

I had to update the infrastructure diagram of the Services FACiLes project, it was deprecated and, inevitably, will still be in a few months because we keep adding new containers and services. That's a common issue in tech documentation.

Moreover, manually editing a graph isn't a thing I usually like to do, especially when elements, relations and additional text can more easily be declared in a text file using a definition language. I found for instance this tool (not tested) which allows you to describe what you want to graph using a YAML file and then use graphviz to draw the resulting diagram. But, wait, I already have this kind of structured data, it's in my Ansible inventory file.

I then thought about writing an equivalent tool to draw a diagram representing Ansible hosts and groups declared in the inventory and their facts and variables. Of course, someone else already had this idea, and this is called ansible-inventory-grapher. This python tool produces graphviz code that you can directly pipe to graphviz to generate a png or svg file.

As an example, here is how the graph of our infrastructure looks like:

infrastructure graph generated from Ansible inventory

ansible-inventory-grapher doesn't show value of variables but you can use this fork( merge request pending) to have them on your graph. You should also enable fact caching in your ansible.cfg and gather your server's facts before running ansible-inventory-grapher. Else you will only have variables declared in host_vars/ and group_vars/.

The default jinja2 template used to generate the code suitable for graphviz prints all variables and facts, but I was only interested by some of them (those passed to the --visible-vars actually) so I slighty modified the template as follow:

--- doc/ansible-inventory-graph.default.j2
+++ doc/ansible-inventory-graph.j2
@@ -8,7 +8,7 @@
   <tr><td><b>
   <font face="Times New Roman, Bold" point-size="16">{{ node.name}}</font>
   </b></td></tr>
-{% if node.vars and showvars %}<hr/><tr><td><font face="Times New Roman, Bold" point-size="14">{% for var in node.vars|sort %}{{var}}{% if var in visible_vars %} = {{node.vars[var]}}{% endif %}<br/>{%endfor %}</font></td></tr>{% endif %}
+{% if node.vars and showvars %}<hr/><tr><td><font face="Times New Roman, Bold" point-size="14">{% for var in node.vars|sort %}{% if var in visible_vars %}{{var}} = {% if node.vars[var] is iterable and node.vars[var] is not string %}{{ node.vars[var]|map(attribute='name')|join(', ') }}{% else %}{{node.vars[var]}}{% endif %}<br/>{% endif %}{% else %}<br />{%endfor %}</font></td></tr>{% endif %}
 </table>
 >]
 {% else %}
@@ -17,7 +17,7 @@
   <tr><td><b>
   <font face="Times New Roman, Bold" point-size="16">{{ node.name}}</font>
   </b></td></tr>
-{% if node.vars and showvars %}<hr/><tr><td><font face="Times New Roman, Bold" point-size="14">{% for var in node.vars|sort %}{{var}}{% if var in visible_vars %} = {{node.vars[var]}}{% endif %}<br/>{%endfor %}</font></td></tr>{% endif %}
+{% if node.vars and showvars %}<hr/><tr><td><font face="Times New Roman, Bold" point-size="14">{% for var in node.vars|sort %}{% if var in visible_vars %}{{var}} = {% if node.vars[var] is iterable %}{{ node.vars[var]|map(attribute='name')|join(', ') }}{% else %}{{node.vars[var]}}{% endif %}<br/>{% endif %}{% else %}<br />{% endfor %}</font></td></tr>{% endif %}
 </table>
 >]
 {% endif %}{% endfor %}
@@ -26,3 +26,5 @@
   {{ edge.source|labelescape }} -> {{ edge.target|labelescape }};
 {% endfor %}
 }

Finally, I wrote a simple Makefile to easily update the graph when inventory is updated:

# Clear out default implicit rules and variables.
MAKEFLAGS += --no-builtin-rules --no-builtin-variables

.PHONY: inventory-graph

inventory-graph: doc/ansible-inventory-graph.png

doc/ansible-inventory-graph.png: inventory/hosts $(wildcard inventory/group_vars/*.yml) $(wildcard inventory/host_vars/*.yml)
    ansible -i inventory/ -m setup all >/dev/null
    ~/src/ansible-inventory-grapher/bin/ansible-inventory-grapher -i inventory/ -t doc/ansible-inventory-graph.j2 -a "rankdir=LR;" \
    --visible-vars knot_zones \
    --visible-vars backup_server \
    --visible-vars munin_master \
    --visible-vars icinga2_master \
    --visible-vars icinga2_parent \
    --visible-vars backup_hosts \
    --visible-vars icinga2_monitored_sites \
    --visible-vars db_type \
    --visible-vars hosted_by \
    --visible-vars libvirt_domains \
    --visible-vars lxc_containers \
    --visible-vars ansible_architecture \
    --visible-vars ansible_default_ipv4.address \
    --visible-vars ansible_default_ipv6.address \
    --visible-vars ansible_distribution \
    --visible-vars ansible_distribution_version \
    --visible-vars ansible_memtotal_mb \
    --visible-vars ansible_processor_vcpus \
    --visible-vars ansible_virtualization_role \
    --visible-vars ansible_virtualization_type \
    all | dot -T png >$@

That way, when we add a new machine or make any change in our infra, all we need to do is make inventory-graph and commit the resulting graph. The image file is linked in our documentation in our private wiki, thus we are ensured that it is always up to date!

April 05, 2019 09:57 PM

March 31, 2019

Romain Dessort

Ansible QA and testing with molecule

Winter is already over in the Laurentians, and for a few weeks now ski conditions are so horribles that it's not fun anymore and I'm sick of applying and cleaning klister on my skis. We will soon have rain and mud and flooding everywhere. April is definitely the worst month in Quebec despite sunny and longer days. But the good thing is that I suddenly have a huge amount of time left for other things. Like system administration and coding.

Thus I picked up a task from my todo list that I always wanted to do: trying molecule over our Ansible roles and playbook at FACiL. The infrastructure running behind the Services FACiLes project is setup and maintained 100% with Ansible. All our roles and playbooks are public and hosted on Gitlab.

molecule is a Python tool developed by the same team as Ansible and aims running different kind of tests over your ansible roles: linting, role execution on multiple targets and scenarios, ensure idempotence over multiple executions and running unit tests on the target.

I am not going to write a full tutorial on molecule, a lot of them already exist on Internet, though not always up to date since molecule is still a young project and in heavy development. The official documentation is a bit rough to get started and to understand the scope of molecule. A good tutorial I found for being the most up to date and accurate.

Instead, in this post I'm going to explain how I use it to test our roles, with some tips I didn't find in the official documentation. I also played with Gitlab CI so molecule tests are run on each git push, but it will be for another post.

molecule's files

We start by creating a molecule directory in our role:

$ molecule init scenario --verifier-name goss --driver-name docker --role-name <role>

It will create a directory named molecule/ in your role's directory. You can have multiple scenarios for your role, stored under molecule/ directory. By default, molecule init will create the scenario default:

$ tree molecule/
molecule/
└── default
    ├── Dockerfile.j2
    ├── INSTALL.rst
    ├── molecule.yml
    ├── playbook.yml
    ├── tests
    │   └── test_default.yml
    └── verify.yml

Each file above are just here to help you to get started and can be edited as you want. Options we passed to the molecule init command are just here to alter these files. I usually prefer copying the molecule/ directory from an existing role (which is totally acceptable) since I have done some extra customization on it.

molecule.yml

molecule.yml is the only molecule configuration file. Here is the molecule.yml we use in our roles :

---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: molecule-monitoring-tools
    image: geerlingguy/docker-debian9-ansible:latest
    command: ${MOLECULE_DOCKER_COMMAND:-""}
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true
    pre_build_image: true
provisioner:
  name: ansible
  lint:
    name: ansible-lint
  options:
    skip-tags: molecule-converge-notest
  log: true
scenario:
  name: default

molecule is designed to be very extensible. For instance, it uses the galaxy dependency resolver (dependencies are defined in the meta/main.yml file of your role) and ansible as a provisioner (this is the only one supported for now but we can imagine molecule will be able to use other configuration management tools).

We use yamllint as the linter for all YAML file of the role (including molecule's own files!) and ansible-lint for ansible files.

For some reasons, we don't want to run some Ansible tasks when testing the role. molecule let us pass any ansible-playbook option with the dict provisioner:options. Here we excluded tasks which have the molecule-converge-notest tag on them.

And finally we tell molecule to run the role in a Docker container. molecule supports a large range of drivers, including LXC containers, Vagrant VMs and common public cloud providers. Even if we use LXC containers on production, I choose to use the Docker driver with molecule because, first it's more easy to setup on a personal machine, and second because this is the only supported option if we want to run molecule tests on Gitlab's shared runners (using Docker in Docker), I will talk about it later in a second post.

Most of our roles don't work inside a minimal Docker container, so we use geerlingguy/docker-debian9-ansible Docker image. The image provides a more complete Debian system (with systemd and common tools). It is maintained by Jeff Geerling, who wrote a lot of Ansible roles and uses it to test his own roles.

By default, molecule spawns a container named instance. It's a bad idea since I ended up with conflicts when I tried to launch tests on multiple roles at the same time, each of them was executed in the same container. Thus I edited platforms:name key so that it bears the role's name.

Dockerfile.j2

This file is only here if you want to build your Docker container on each test. You can use any Docker image (defined in platforms:image) and this Dockerfile will install requirements such as python, sudo, bash and ca-certificates package to run Ansible.

Since the image we use already contains those dependencies, we instruct molecule not to build another Docker image (platforms:pre_build_image). this way, we also speed up tests.

INSTALL.rst

INSTALL.rst is an informal file containing instructions to others contributors and requirements to run molecule tests. For instance, we need to have the Docker engine and docker-py installed on our machine.

playbook.yml

After molecule.yml, this is the second most important file. Once molecule has spawned the container, it will run this playbook.

Here is its content:

---
- name: Converge
  hosts: all
  roles:
    - role: <role>

Pretty simple, it applies onto the spun up container. But if your role requires some variables to be set, or dependencies that aren't declared in your role's meta/main.yml file, you can add them here. For instance:

---
- name: Converge
  hosts: all
  roles:
    - role: <other role>
    - role: <role>
  vars:
    foo: "bar"

verify.yml and tests/test_default.yml

Finally, those two files are for unit tests. verify.yml installs Goss (the verifier tool) in the container and run tests it finds in the tests/ directory. Although it's a very interesting subject, I didn't dig too much into unit tests for now.

Running molecule

First, to enable bash completion:

$ eval "$(_MOLECULE_COMPLETE=source molecule)"

Running molecule without argument will show you the list of available sub-commands. I'm not going to explain all of them, and I don't even sure what all these sub-commands do, I still learning about molecule.

Run linting tools on your role:

$ molecule lint

Test your role into a freshly created container (by executing playbook.yml on it):

$ molecule converge

molecule converge will automatically lint your code prior to run it.

At this time you can either login to the container to manually check what has been done:

$ molecule login

Or replay molecule converge if you obtained errors and have fixed them in your role.

molecule converge allows us to pass almost any ansible-playbook option. For instance, you may want to debug tasks with a specific tag:

$ molecule converge -- -vvv --tags <tag>

To destroy the running container so you can start with a new one:

$ molecule destroy

If you just want to start a container but don't execute anything in it (for instance if you want to known the state of a file in a brand new install):

$ molecule prepare

To run idempotence check (molecule will simply run the playbook.yml a second time and check that no tasks report a changed state):

$ molecule idempotence

All these previous commands are useful while you are developing a role. Once you have done or did only minors edits (you are pretty confident that you didn't break anything), it's handy to run all the test suite at once:

$ molecule test

It will lint your code, start a new container, apply playbook.yml, verify idempotence, run unit tests and finally destroy the container.

March 31, 2019 06:30 PM

March 29, 2019

Romain Dessort

[Ansible] sudo password from your password manager or agent

Make Ansible retrieve the sudo password from my password manager. For a long time, I was wondering how to solve this issue. Ansible is my main tool to manage my own servers. All the infrastructure of the Services FACiLes project I contribute to (a future CHATONS is built with Ansible automation in mind, and even at my previous work, Ansible is used on a daily basis.

A thing that really annoyed me was that each time I run a playbook, I have to give Ansible my sudo password. Besides that, Ansible prompts for the password at an early stage of execution, even before trying to include all files or running a syntax check on them. Thus it isn't uncommon that Ansible fails immediately after because of a mistake I made in a playbook, and I have fix it, rerun the command, reenter my password… and so on. Which is very frustrated.

I know some workarounds exist to avoid dealing with the interactive sudo password prompt: allowing Ansible to be executed without password on the servers (almost equivalent to allowing any command to be executed without password…, which is not an acceptable answer), storing your password into ansible_sudo_pass variable in a cleartext file somewhere (yes you can encrypt it with ansible-vault but then you have to give the vault password each time you run your playbook), trying to mess with except

The simple and elegant way of doing it for me would be that Ansible gets the password from an agent, like gpg-agent, or execute an arbitrary command to get it. After a lot of research and experimentation, I found a pretty nice and clever solution on Stackoverflow (far at the bottom, it should be upvoted more than that IMHO). The idea is to combine --extra-var ansible option (to which a file descriptor can be passed, while prefixing it with @) and <() bash operator to convert a command's stdout to a file virtual file descriptor. We end up with the following command:

$ ansible-playbook -e@<(echo "ansible_sudo_pass: $(pass Sysadmin/xxxx)") playbook.yml

This way my sudo password is retrieved from my pass's password manager (which uses gpg-agent) and I'm not bothered anymore with Ansible's interactive prompt \o/.

The only drawback is that I didn't find a way to set this into the ansible.cfg or somewhere else, except writing a bash alias or a small wrapper.

March 29, 2019 09:47 PM

March 26, 2019

Pierre Boesch

Debian : Installer la dernière version de Firefox

Debian propose dans ses paquets la version Firefox ESR qui peut être adapté à certains cas d'usage mais pour d'autres, il est frustrant de ne pas pouvoir bénéficier des dernières features. Sur wheezy, et pendant un temps sur jessie, il était possible d'utiliser un dépôt de l'équipe Debian Mozilla pour installer la dernière version de Firefox et Icedove. Finalement, il y a une méthode simple qui consiste à récupérer et extraire une archive contenant le binaire Firefox. C'est d'ailleurs la méthode décrite dans la documentation officielle (chapitre Installing outside of a package manager).

Idée générale

L'idée générale de l'opération se compose de 3 points :

  • télécharger l'archive de la dernière version ,
  • extraire dans un dossier (par exemple /opt),
  • exécuter le binaire firefox.

Pas plus compliqué que sur Windows ;-). En plus de ça, les mises à jours se font automatiquement.

Dernière information, les profils dans ($HOME/.mozilla/firefox) seront évidemment utilisés.

Que vous utilisiez la méthode manuelle ou automatique, comme expliqué juste au-dessus, l'opération n'est à faire qu'une seule fois : lorsqu'une mise à jour est disponible elle sera téléchargée automatiquement, il ne restera qu'un restart du navigateur à effectuer.

Installation pas à pas

Je reprends rapidement ce que décris la documentation et finirai par un playbook ansible qui automatiser ça.

Avec la cli

Mozilla a la bonne idée de tagger une archive latest qui pointe vers la dernière release stable de Firefox.

wget "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=fr" -O /tmp/firefox.tar.bz2
tar xjf /tmp/firefox.tar.bz2
sudo mv /tmp/firefox /opt
pkill firefox
/opt/firefox/firefox

Si la dernière commande ouvre Firefox, c'est gagné. Sinon peut-être qu'il manque des librairies ?

Tout ça c'est bien mais devoir ouvrir un terminal pour lancer Firefox ça devient vite chiant. Avec un window manager type i3 le problème est vite réglé mais sur un DE Gnome-like ?

Application Firefox

Chaque application graphique dispose d'un fichier .desktop dans /usr/share/applications. Sur Debian, comme Firefox ESR est pré-installé, le boulot est à moitié mâché.

On duplique le fichier pour notre propre version de Firefox :

sudo cp /usr/share/applications/firefox-esr.desktop /usr/share/applications/firefox.desktop

Les paramètres à modifier dans ce nouveau fichier sont : Name, Exec et Icon.

- Name=Firefox ESR
+ Name=Firefox

- Exec=/usr/lib/firefox-esr/firefox-esr %u
+ Exec=/opt/firefox/firefox %u

- Icon=firefox-esr
+ Icon=/opt/firefox/browser/chrome/icons/default/default128.png

L'application "Firefox" doit être à présent accessible par le DE : typiquement dans le menu "Recherche". Il est donc possible de définir cette nouvelle application dans "Preferred Applications" pour le Web.

Automatisation

Parce que j'utilise ansible pour configurer la base de mon poste de travail, je me suis crée une task automatisant les étapes ci-dessus. Les tasks font parties de mon rôle "base" mais celui-ci n'étant pas (encore ?) publique, j'ai mis à disposition sur mon github, un playbook reprenant l'idée générale. Le playbook se trouve ici : https://github.com/pboesch/ansible-firefox-deb.

Je pars du principe que ansible est installé :

git clone https://github.com/pboesch/ansible-firefox-deb
cd ansible-firefox-deb
ansible-playbook playbooks/localhost.yml -K

Un extrait :

- name: Install latest stable Firefox and latest beta Thunderbird
  hosts: localhost
  become: True

  tasks:
    - name: download and extract latest firefox
      unarchive:
        src: "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=fr"
        dest: /opt
        remote_src: yes

La playbook installera aussi la dernière version bêta de Thunderbird.

by Pierre Boesch at March 26, 2019 11:00 PM

March 14, 2019

Romain Dessort

Les pistes de Saint-Hippolyte

Qui aurait penser qu'il y avait des anciennes pistes de ski nordique vers Saint-Hippolyte ? C'est un coin semble-t-il assez peu connu à part par les locaux qui fréquentent ces pistes là. En effet aucune ressource sur Internet n'en parle et aucune carte de ce secteur qu'on peut se procurer (à ma connaissance). Mais il y avait bel et bien un dense réseau tout autour de Prévost (Shawbridge), puisque c'est là où habitait Herman S. Johannsen. Le secteur de Prévost, Piedmont et Saint-Hippolyte est protégé par la Réserve Naturelle du Parc des Falaises contre (notamment) le développement immobilier, ce qui est une très bonne chose. Mais c'est aussi un secteur littéralement envahi par les raquetteurs, ce qui ne donne jamais trop envie d'aller l'explorer à ski. Pourtant au delà d'une certaine distance des points d'accès, les sentiers deviennent moins tapés par les raquettes et on peut retrouver de la belle neige.

Je m'y suis rendu pour la première fois avec des membres du CMC qui connaissaient un peu le coin, puis à quelques jours près, Barclay s'y était aussi aventuré (en deux fois). J'y suis donc retourné seul avec pour mission de cartographier le plus de pistes possible.

J'ai ainsi pu ajouter un bon bout de la Fd (Flight Delight), la plupart de la 6X, qui continuait même plus au nord en contournant le lac Desjardins, la partie centrale de la Johannsen (que j'aimerai vraiment pouvoir relier au complet, j'ai en tête de partir de Mont-Rolland et d'aller jusqu'à Prévost en la suivant), et enrichie un peu la WN, Wizzard, la 8 et quelques autres.

Cela n'a pas été une tâche facile, surtout dans le secteur de Prévost, non pas que les pistes ne sont plus visibles, mais au contraire, certaines pistes ont été reroutées, donc on peut tomber sur les anciens tracés avec quelques vieilles balises rouillées qui ont bravées l'épreuve du temps, les nouveaux sentiers qui reprennent les mêmes noms mais qui ne correspondent plus du tout au tracé original et les sentiers informels créés par les randonneurs eux-mêmes, sans compter que plusieurs pistes fusionnent ensemble sur une certaine distance avant de se reséparer. Bref, quoi cartographier dans OpenStreetMap n'a pas été chose facile et je ne suis pas certain que chaque sentier y est correctement nommé, mais c'est déjà beaucoup mieux qu'avant.

J'ai aussi suivi la MOC (McGill Outing Club) aussi loin que possible avant d'arriver dans un terrain privé. Elle n'a pas beaucoup d'intérêt (peu de défis et pas très jolie, elle à l'air d'un vieux chemin de coupe) et est pas mal envahie aussi, mais elle sert d'artère centrale aux autres pistes du secteur. Et j'ai trouvé une piste mystère (elle aussi pas mal envahie) qui remonte de la MOC vers le Mont Olympia, juste balisée avec des disques de plastiques jaune.

C'est dans le secteur de Saint-Hippolyte que j'ai trouvé une précieuse aide dans ma tâche : 2 cartes différentes, présentes à 2 intersections :

Carte du Parc des Falaises, secteur Saint-Hippolyte Carte de la Réserve Ogilvy, Saint-Hippolyte

La première n'est pas très intéressante car elle ne montre que quelques sentiers, l'autre est déjà plus complète et m'a bien servie pour m'aider à y voir plus clair dans ce réseau, même si elle n'est pas tout à fait complète non plus. Ces 2 cartes ne semblent pas être trouvable sur Internet, donc à conserver précieusement !

J'en profite aussi pour laisser un lien vers une carte éditée par Plein-Air-Saint-Hippolyte, organisme qui ne semble plus exister depuis une dizaine d'année, qui montre encore d'autres sentiers de ski, cette fois au nord de Saint-Hippolyte (merci Archive.org !). Je ne suis pas du tout allé explorer ce coin là par contre.

Un autre article de Barclay tout récent à propos de ce secteur, après qu'il a vu les nouveaux sentiers cartographiés dans OpenStreetMap (j'ai l'impression qu'on joue au chat et à la souris à chaque fois !).

March 14, 2019 10:20 PM

March 13, 2019

Romain Dessort

Le nord de la Western

La Western est une autre ancienne piste de ski dans les Laurentides qui, comme son nom de laisse penser, se trouve dans la partie ouest de la région, à l'ouest de la vallée de la rivière du Nord et l'ancienne voie du CP Railway. La partie probablement la plus connue se trouve dans le réseau de Morin-Heights et apparait sur la carte de leur réseau pour une dizaine de kilomètres, partant des pistes de fond tracées jusqu'au lac Théodore au Nord. C'est la seule partie présente sur une carte. Mais la Western va en fait bien au delà !

Au sud, je l'ai trouvée aussi bas qu'à Sainte-Anne-des-Lacs, au niveau de la Loken, où j'ai été surpris d'y trouver une vieille balise de la Western. Je suppose qu'elle monte plein nord vers Morin-Heights, mais au vu du développement résidentiel de Saint-Sauveur qui se trouve entre les 2, je doute malheureusement qu'il en reste grand chose et, si c'est encore possible de passer, ça risque d'être beaucoup sur la route. Je ne suis pas allé explorer ce secteur.

Section du lac Théodore à la Fleur-de-Lys

Par contre beaucoup plus au nord, je l'avais encore croisée l'année passée en remontant la Fleur-de-Lys de Sainte-Adèle à Sainte-Agathe-des-Monts, peu avant que la Fleur-de-Lys connecte la Canadienne. Dernièrement je suis donc retourné à cette intersection pour suivre la Western le plus loin possible. D'abord vers le sud, jusqu'au lac Théodore. À l'endroit de l'intersection avec la Fleur-de-Lys, la Western qui part vers le sud n'est pas forcement évidente à retrouver, il faut en fait suivre un chemin privé vers le sud sur une cinquantaine de mètres. Ensuite de ça le tracé est très bien visible et balisé jusqu'au Lac Théodore, aucune difficulté particulière à le suivre. Il y a peu de dénivelé mais j'ai trouvé le sentier très joli, notamment le long du lac Normand, à travers une belle forêt de cèdre.

Balises de la Western Balises de la Western

On arrive au Lac Théodore en marchant/skiant un bout sur l'avenue des Pivouanes, qui débouche sur une mini-baie privée sur le lac. Pour rejoindre la Western du réseau de Morin-Heights, il suffit de mettre cap plein sud sur lac pour retomber dessus sur l'autre rive.

Après avoir reviré de bord une fois rendu au lac Théodore, j'ai tenté d'explorer la section au nord de l'intersection à la Fleur-de-Lys. La piste était un peu plus envahie par les branches et moins évidente à suivre. J'ai pu la skier jusqu'à un lac sans nom au nord de la Colline Beauvais. Mais j'ai perdu son tracé après. J'ai appris par contre jusqu'où elle va : elle doit traverser le lac Paquin, traverser le chemin du 7ème Rang et arriver tout au bout du Chemin des Pins à Sainte-Agathe.

Direction Sainte-Agathe On peut deviner un « Direction Sainte-Agathe »

Section de la Fleur-de-Lys à Sainte-Agathe

Bon, je n'ai pas eu le temps de publier cet article que je suis déjà retourné sur les traces de la Western ! Je rajoute ici ce qui devait être un second article.

Comme j'ai su que la Western pouvait se prendre tout au bout du Chemin des Pins à Sainte-Agathe, je me suis rendu là lundi dernier. Le Chemin des Pins se termine dans un camp de vacances, complètement abandonné pour la saison hivernale. Après avoir tourné un peu rond dans le camp, j'ai fini par trouver la piste ; elle file assez proche de la route avant de partir plein est. Son extrémité nord se trouve en fait 500 m plus loin puisqu'elle joint directement la Fournelle, piste tracée du centre de ski de fond du Camping Sainte-Agathe.

Après ça, la piste contourne la Colline aux Framboises par le sud puis file vers le sud-est en coupant les 8ème et 7ème rangs, jusqu'à arriver au lac Paquin. Un peu avant de croiser le 8ème rang, on se retrouve face à une descente toute droite assez impressionnante, digne d'une pente de ski alpin (je n'en ai jamais vu des aussi raides et longues ailleurs) ! Heureusement elle n'est pas trop étroite et comme j'ouvrais dans 40 cm de neige lourde, j'ai pu faire quelques beaux virages de telemark assez facilement (en fait un des rares moments durant cette journée où j'ai pu profiter de la glisse). Avec des conditions de neiges plus difficile par contre, je ne pense pas être capable de la descendre…

Pente de la Western La photo ne rend pas du tout de la pente.

On arrive au chemin du 7ème rang par la Rue du Continental, puis il faut marcher 50 m sur le rang vers l'est avant de pouvoir embarquer sur le lac (avant de traverser la décharge du lac). Sur le lac, je n'avais pas la moindre idée d'où aller, donc j'ai longé la rive est qui offre de belle falaises et cascades de glace. Mais il faut en réalité mettre le cap plutôt vers le sud-sud-ouest pour retomber sur le chemin Gascon Est. En réalité on peut tout aussi bien marcher/skier sur le chemin du 7ème rang pour arriver sur le chemin Gascon Est, ce sera certainement plus rapide si il n'y a aucune trace sur le lac et que les chemins ne sont pas totalement déneigés pour permettre de ski dessus.

La piste reprend depuis le chemin (facile à trouver, il y a une balise) et continu plein sud en passant à l'ouest d'une petite colline, jusqu'aux lignes électrique qu'il faut traverser. La piste traverse de bout-en-bout un lac sans nom, celui-là où je m'étais rendu la fois précédente. Après ça, la Western continue sud-ouest jusqu'à la Fleur-de-Lys.

Globalement, cette section de la Western reste assez bien balisée même si, sans connaitre son tracé approximatif, elle reste difficile à suivre à certains endroits où le balisage est un peu plus aléatoire (érablières clairsemées, traversées de lacs et intersections avec des chemins et rangs).

J'ai continué jusqu'à Sainte-Adèle en suivant la Fleur-de-Lys jusqu'au bout, très belle piste que j'aime beaucoup aussi, mais celle là je l'avais déjà cartographiée l'année passée.

Fleur-de-Lys Ancienne balise de la Fleur-de-Lys.

La Western est maintenant complète pour sa portion au nord de Morin-Heights dans OpenStreetMap.

March 13, 2019 10:34 PM

March 01, 2019

Romain Dessort

Automatically backup your Android phone with udev

I wrote a simple script and udev rule a few years ago to automatically backup my phone (running Replicant OS, a free, FSF-approved Android distribution) to my computer when I plug it to a USB port. I just published those scripts after some clean-up on a git repository and wrote instructions on how to set it up.

I know there are plenty of Android applications to backup my data and upload them somewhere via Internet. But my phone is rarely connected, the Wi-Fi is usually turned off and I have no data plan. Instead I do plug my phone to my computer on a regular basis to recharge it, so it seemed obvious to automatically trigger a backup of my phone via USB.

udev can be used to detect events, like plugging-in a device, and trigger actions based on these events. In my case, the udev rule looks like this:

ACTION=="add", SUBSYSTEMS=="usb", ENV{ID_SERIAL_SHORT}=="XXXXXXXXXXXXXXXX", TAG+="systemd", ENV{SYSTEMD_USER_WANTS}+="android-backup@$env{ID_SERIAL_SHORT}.service"

Where ID_SERIAL_SHORT is used to uniquely identify my phone. The rule triggers the android-backup@.service systemd unit (in user mode, no need to run it as root).

The systemd unit is very simple, there is no need to specify an [Install] section, targets or anything like that since it is triggered directly by udev:

[Service]
Type=oneshot
ExecStart=%h/.local/bin/android-backup.sh %I

So the unit simply calls the following shell script:

#!/bin/sh

export DISPLAY=0:0
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
BACKUP_DIR=~/replicant-backup/
RSYNC_EXCLUDE=~/.config/android-backup.exclude

notify-send "Android Backup" "Starting Android backup"

(
set -e

mkdir -p "$BACKUP_DIR"
adb wait-for-device 2>&1 |(grep -Ev "^(\* daemon not running; starting now at|* daemon started successfully$)" || true)
adb root
adb wait-for-device
adb shell rsync --daemon --no-detach --config=/system/etc/rsyncd.conf &
adb forward tcp:6010 tcp:873
sleep 2
rsync -av --chmod u+rwX --delete --delete-excluded --exclude-from "$RSYNC_EXCLUDE" rsync://localhost:6010/root/ $BACKUP_DIR
adb forward --remove tcp:6010
adb shell pkill rsync
)

if [ $? -ne 0 ]; then
    notify-send "Android Backup" "Backup terminated with errors. See journalctl --user -eu android-backup for details"
else
    notify-send "Android Backup" "Backup terminated successfully"

In this script, I first start adb (as root so it can have access to the whole system) and start a rsync daemon on the OS running on the phone and forward its TCP port through adb. I can then start a normal rsync from my computer to fetch all the files and directories. The destination directory (on my computer) is included in my daily backup, thus I automatically have incremental copies of my phone's data.

March 01, 2019 06:20 PM

February 16, 2019

Alexandre Bailly

USB Gadget - ethernet à travers USB

Cet article est aussi présent dans mes pages de documentation.
https://docs.nah.re/raspberry_pi/usb-otg-g-ether.html


Avant-propos

Ceci est un petit tutoriel en français, sur l'intégration de g_ether sur un Raspberry PI Zero.

Un minimum de connaissances de GNU/Linux et de la ligne de commande est nécessaire.

Photo d'un Raspberry PI Zero, dans son boîter blanc.

Un Raspberry PI Zero.

Installation de Raspbian

Premier détail : je n'ai pas copié une raspbian lite ou full sur la microSD, parce qu'on n'a pas du tout la même définition de léger.

Du coup, je suis parti sur raspberrypi-ua-netinst https://github.com/FooDeas/raspberrypi-ua-netinst

L'avantage d'une installation en unattended + netinst, c'est que l'on définit les paramètres dans un fichier de configuration, on branche un câble réseau (via un adaptateur micro USB OTG + une carte ethernet USB), on démarre et on laisse tourner.

Une fois l'installation terminée, je me suis connecté sur le Raspberry PI via SSH (toujours via le réseau).

Intégration de g_ether

Maintenant, on peut s'attaquer à l'intégration du gadget USB Ethernet (g_ether).

Se connecter avec le compte root (peu importe la méthode on va modifier des fichiers et installer des trucs)

Ouvrir /boot/config.txt. Ajouter à la fin du fichier, après [all]

#USB G_Ethernet
dtoverlay=dwc2

Si vous lisez des vidéos avec omxplayer et que celui-ci râle avec le message

COMXAudio::Decode timeout

Ajoutez dans le même fichier (/boot/config.txt)

gpu_mem=128

Enregistrer et fermer.

Ouvrir /boot/cmdline.txt

ajoutez à la fin de la commande.

modules-load=dwc2,g_ether

N'insérez surtout pas de saut de ligne.

La commande pourrait ressembler à ça :

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty3 elevator=deadline fsck.repair=yes root=/dev/mmcblk0p2 rootfstype=ext4 rootwait modules-load=dwc2,g_ether quiet logo.nologo loglevel=3 vt.global_cursor_default=0

Ici, j'ai configuré pour que tout soit silencieux, avec aucun message sur la sortie vidéo. Ne copiez pas bêtement la commande sans savoir à quoi correspond quiet ou vt.global_cursor_default=0. Ce n'est pas le but de ce tuto rapide, je ne détaillerai pas à quoi ça correspond.

Enregistrer et fermer le fichier.

Configuration du réseau

Pour éviter de se prendre la tête avec l'attribution de l'adresse IP une fois la carte branchée sur un PC (ou MAC) en USB, j'installe un serveur DHCP qui fera cette tâche automatiquement.

Installer le paquet isc-dhcp-server

apt install isc-dhcp-server

Activer le service (activer, ne pas démarrer tout de suite) avec systemd

systemctl enable isc-dhcp-server

On configurera le serveur DHCP juste après. On va d'abord configurer le réseau pour g_ether.

Ouvrir le fichier /etc/network/interfaces. Y ajouter :

# Ethernet over USB
allow-hotplug usb0
iface usb0 inet static
address 10.10.10.10
netmask 255.255.255.0
broadcast 10.10.10.255
gateway 10.10.10.10

Ici, j'ajoute une interface usb0, qui a une adresse IP 10.10.10.10. Cette IPv4 étant dans les plages réservées pour un usage privé, cela ne posera pas de problème ni de conflit. usb0 sera créé lorsque le raspberry pi sera branché en USB.

Enregistrer et fermer.

Ouvrir maintenant /etc/dhcp/dhcpcd.conf

Supprimer tout le contenu et insérer :

default-lease-time 600;
max-lease-time 7200;
ddns-update-style none;
authoritative;
log-facility local7;

subnet 10.10.10.0 netmask 255.255.255.0 {
range 10.10.10.11 10.10.10.20;
option routers 10.10.10.10;
option broadcast-address 10.10.10.255;
default-lease-time 600;
max-lease-time 7200;
option domain-name "local";
}

Les cinq premières lignes sont pour le paramétrage du serveur dhcp, les huit lignes en bas correspondent à la configuration du réseau.

Je définis le réseau comme étant sur 10.10.10.0, s'étendant jusqu'à 10.10.10.255 via le masque réseau.

Je définis ensuite la plage allant de 10.10.10.11 à 10.10.10.20 (ce qui me sera suffisant).

J'indique que le routage se fera sur 10.10.10.10, qui correspond à l'adresse du Raspberry PI (que j'ai défini dans /etc/network/interfaces).

Le reste n'est pas vraiment important (durée des sessions dhcp, nom de domaine local).

Enregistrer et fermer.

Ouvrir maintenant /etc/default/isc-dhcp-server

Décommenter les lignes

DHCPDv4_CONF=/etc/dhcp/dhcpd.conf
DHCPDv4_PID=/var/run/dhcpd.pid

Ajouter usb0 dans INTERFACESv4, comme ceci :

INTERFACESv4="usb0"

Enregistrer et fermer.

Tests

Éteindre le Raspberry PI

sudo poweroff

Tout débrancher, puis brancher un câble micro USB (data, pas charge seule) sur le port "USB" du Raspberry (pas sur "pwr IN"), brancher l'autre côté du câble USB sur un PC.

Photo d'un Raspberry PI Zero avec un câble Micro USD branché sur le port USB.

Branchement du câble micro USB.

Après quelques dizaines de secondes, une interface réseau devrait apparaître (sous Windows, il y ait de fortes chances que le pilote rdnis ne s'installe pas automatiquement, il faudra l'installer manuellement via ajouter un pilote, choisir le pilote, type carte réseau, fabricant Microsoft, sélectionner rdnis ou un truc s'en approchant, répondre oui à l'avertissement comme quoi le pilote n'est peut être pas compatible).

Vérifiez si le Raspberry PI est bien reconnu comme une carte réseau.

Sous Windows

ipconfig /all

Sous Mac OS X et autres BSD

ifconfig

Sous GNU/Linux

ip a

Normalement, quelques lignes indiquant

inet 10.10.10.17/24 brd 10.10.10.255 scope global dynamic noprefixroute enp0s19f2u1

ou

inet 10.10.10.17  netmask 255.255.255.0  broadcast 10.10.10.255

ou un truc indiquant qu'il y a une IP commençant par 10.10.10 devrait s'afficher.

Si c'est le cas, taper

ping 10.10.10.10

S'il y a une réponse, cela signifie que cela fonctionne.

PING 10.10.10.10 (10.10.10.10) 56(84) bytes of data.
64 bytes from 10.10.10.10: icmp_seq=1 ttl=64 time=0.504 ms
64 bytes from 10.10.10.10: icmp_seq=2 ttl=64 time=0.543 ms
64 bytes from 10.10.10.10: icmp_seq=3 ttl=64 time=0.539 ms
64 bytes from 10.10.10.10: icmp_seq=4 ttl=64 time=0.542 ms
^C
--- 10.10.10.10 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 111ms
rtt min/avg/max/mdev = 0.504/0.532/0.543/0.016 ms

Il n'y a plus qu'à se connecter dessus.

ssh pi@10.10.10.10

Si vous avez pu vous connecter via ssh à travers la connection USB, félicitations.

Conclusion

Ce n'est pas très compliqué à mettre en place, le problème étant de réunir les différentes informations éparpillées un peu partout sur le net, et souvent en anglais (ce qui ne me pose pas de problème, mais ce n'est pas le cas de tout le monde).

Remerciements

Un gros merci à Albert, pour m'avoir prêté son Raspberry PI Zero.

by nah at February 16, 2019 04:49 PM

February 12, 2019

Romain Dessort

Marathon Canadien de Ski

Le Marathon Canadien de Ski (MCS) est la plus longue épreuve de ski de fond en Amérique du Nord, 160 km (163,7 km exactement), qui se répartissent en 2 jours soit à peu près 80 km par jour. C'est aussi la plus ancienne épreuve en Amérique, il a été tenu pour la première fois en 1967, lors des 100 ans du Canada. Il s'agit plus d'un évènement populaire que d'une compétition, les skieurs ne sont pas chronométrés et sont là plus pour le plaisir de skier et le défi personnel que la performance. L'évènement attire en moyenne 2000 personnes, venant principalement du Québec, de l'Ontario et du nord-est des États-Unis.

La piste reliait à l'origine les villes de Gatineau et La Chute en longeant plus ou moins la rivière des Outaouais. Montebello était le point central et l'étape intermédiaire. À cause de l'enneigement de plus en plus incertain ces dernières années, il part maintenant d'un peu au sud de Saint-Jovitte (Mont-Tremblant) pour se rendre à La Chute, en faisant un crochet par Montebello, qui reste l'étape intermédiaire. La piste est tracée une seule fois par année pour l'évènement et passe en majorité sur des terres privées.

Pourquoi j'en parle ici ? Ça me titillait depuis que j'en ai entendu parlé en arrivant au Québec, mais cette année je m'y suis inscrit et suis très fier d'avoir skier l'intégralité des 160 km en 2 jours ! Je fais un petit retour sur mon expérience.

Il y plusieurs catégories lors de l'inscription, on peut soit faire le demi-marathon, le marathon entier (Coureur des Bois) ou les sections que l'on veux (Randonneur) (le parcours est divisés en 10 sections de 16 km chacune en moyenne). Jusqu'au dernier moment avant mon inscription, j'ai beaucoup hésité dans quelle catégorie m'inscrire. J'étais plus partant pour faire le marathon entier au début, puis à un moment j'ai douté beaucoup, je n'arrivais pas à m'entrainer autant que je voulais (on a eu un décembre sans neige) et j'ai fini par m'inscrire dans la catégorie Randonneur. Comme ça je me suis dit que si je ne skiais pas les 10 étapes, je ne perdais pas le défi. Le prix a joué aussi, car les frais de participation sont assez cher : 300 $ pour les Coureurs des Bois contre 200 $ pour les Randonneurs. Puisque les Randonneurs peuvent quand même skier l'intégralité du marathon, les 100 $ ne se retrouvent que dans la médaille à la fin, ça ne justifiait pas la différence pour moi. Surtout qu'à cela, il fallait ajouter les frais d'hébergement pour vendredi et samedi (150 $ pour dormir par terre dans les salles de classe de l'école de Papineauville et manger à la cantine de l'école) + plus la navette depuis Montréal (100 $), plus toutes les taxes… Bref ça faisait un sacré budget et faisait cher le kilomètre skié. Mais je dois admettre que c'était toute une organisation sur place vu le nombre de participant et c'était assez bien rodé.

La veille du marathon, j'avais encore changé d'avis et je voulais à tout pris skier les 80 km par jour. Sauf qu'une chose que j'ai appris au dernier moment est que les Randonneurs qui veulent skier les 10 sections sont clairement pénalisés par rapport aux Coureurs des Bois : les Coureurs des Bois partent à 5h, le départ des Randonneurs est à 7h30 et il y a un cutoff à 15h30 pour tout le monde au départ de la dernière section de la journée. Ça laisse beaucoup moins de temps aux Randonneurs ! Donc au final, je me retrouve avec un défi supplémentaire, skier les 4 premières sections de chaque jour plus vite que les Coureurs des Bois pour arriver avant le cutoff de 15h30.

Dans ma tête, c'était donc un défi de 66 km à skier en 8h, pauses comprises, puis les 15 km de la dernière section du jour que je pouvais skier à la vitesse d'un piéton si je serai à bout, ça n'aura plus d'importance. J'ai calculé précisément mes heures de passages aux différents checkpoints. Je devais skier à une vitesse minimum de 9 km/h et pouvais m'arrêter maximum 15 minutes à chaque checkpoint pour refaire le plein d'eau, manger et refarter mes skis si besoin. La veille, j'ai appris par cœur mes heures de passage et j'ai mémorisé le plus de détails possible des cartes des sections (traverses de routes, de lacs, grandes côtes, passages dans les champs) de façon à me donner des points de repères une fois sur la piste pour situer mon avancement ; 80 km c'est long et il est facile de se « perdre » dans les chiffres et d'avoir l'impression de ne pas avancer si dès le départ au matin l'objectif est le 80 ème kilomètre. Voir que j'arrive effectivement à telle place alors que j'en suis qu'au 38 km est très motivant et rassurant à la fois. Et je me prépare ensuite à atteindre le prochain repère, peut-être juste 10 km plus loin. En fait, je me fous complètement du tout dernier kilomètre.

De manière générale, je suis (assez involontairement mais maintenant que j'en ai conscience ça devient moins involontaire) pessimiste concernant mes objectifs, ce qui fait que je les surpasse bien au delà et ça devient une source de motivation plus grande. Dans ce cas si concrètement, mes heures de passage étaient vraiment le minimum, le cas vraiment limite (je skie plus vite que 9 km/h), donc à chaque fois j'arrivais toujours avec 5 ou 10 minutes d'avance sur ce que j'avais en tête. Là encore, ça me rassurait et me motivait pour la section suivante, où je me mettais au défi de maintenir mon avance ou faire encore mieux.

Le premier jour, je suis arrivé à 1h05 avant le cutoff, 1h05 d'avance sur ce que j'avais prévu, et 40 minutes d'avance pour le deuxième jour. J'ai fini la première journée à 16h30 et 16h37 le lendemain, en skiant à peu près 8h à 10,5 km/h de moyenne plus 1h de pause au total. Je skie habituellement entre 10 et 11 km/h et avant le marathon, je doutais être capable de maintenir ma vitesse habituelle sur une aussi grande distance. Mais en faisant des stats par section, je ne constate aucune baisse de vitesse en fin d'épreuve (sauf la dernière section du premier jour où je me ralentissais volontairement pour ne pas me bruler inutilement pour le lendemain). Même, je me sentais bizarrement encore plein d'énergie, en tout cas d'un point de vue mental et cardiaque, mes muscles eux commençaient à fatiguer sérieusement.

Autant j'ai skié plus vite que prévu, autant la durée des pauses passait très rapidement. Le fait est que contrairement à la course à pied, c'est bien moins facile de boire et manger en skiant, avec les bâtons, gants et dragonnes ergonomiques, alors que mon eau est coincée quelque part contre mon ventre pour éviter le gel au lieu d'une simple poche d'eau avec un tuyau comme en trail. Donc je me ravitaillais essentiellement aux checkpoints qui étaient d'ailleurs pas mal bien fourni (eau chaude miellée, soupe, chili, bananes, barres de céréales…).

L'autre chose étaient les conditions de ski assez difficiles : il y a eu plusieurs épisodes de pluie puis du regel la semaine précédent l'évènement et la neige était très transformée. Les 2 jours on a eu un grand beau soleil, bien agréable mais n'améliorant pas les conditions, la température oscillait entre -15 à -3 °C suivant l'avancement de la journée. Bref, des conditions très printanières, la glisse était rapide mais le fartage n'était pas évidant. Malgré une base de liant que j'avais appliqué à chaque fois la veille, le fart de jour ne durait pas longtemps avec les conditions abrasive et je refartais tous les 15 à 20 km en moyenne, et dépendamment de l'évolution de la température (après coup, à des moments j'aurai vraiment du m'arrêter et prendre 5 minutes pour ajuster le fartage au lieu de me fatiguer à skier sans plus aucun kick et me disant que sa attendra le prochain checkpoint). Par contre une chose de vraiment bien, des exposants étaient là à chaque checkpoints pour farter les skis ; j'arrivais, je leur laissais mes skis, je mangeais et buvais un bout puis dès qu'ils avait fini avec mes skis je repartais. Ça me faisait gagner pas mal de temps.

Par rapport à la course à pied, c'est bien plus facile, je ne me verrai pas du tout courir pendant 8h par exemple. Déjà il n'y a pas toute la fatigue du aux chocs à chaque impact, c'est que de la glisse, je peux me reposer en descente (si elles sont pas trop raides) et je peux changer de technique pour reposer plus le haut ou le bas du corps quand l'un ou l'autre fatigue. Je faisais même du pas de patin sur les pistes de motoneige pour varier un peu, preuve que ce n'était pas le cardio mais plus les muscles qui fatiguaient.

Au niveau des paysages traversés, c'était très varié, certaines sections étaient très belles, d'autres moins. On a traversé des pinèdes, des érablières et des forêts de cèdres, des terrains de golf, des champs, passé au travers de fermes, skié le long de routes et dans des vallées, traversé des lacs et des rivières, on a skié dans un tunnel sous l'autoroute… Par moment on était sur des sentiers étroits dans le bois, d'autres fois sur des larges pistes de motoneige.

Finalement je ne regrette pas de m'être inscrit en tant que Randonneur, ça a été un défi supplémentaire par rapport au temps, j'ai pu dormir plus longtemps que les Coureurs des Bois qui se levaient à 3h pour partir à la nuit, et aussi, comme peu de Randonneurs faisaient la première section de la journée, on était qu'un petit groupe de 20/30 personnes à partir au lieu des centaines de Coureurs des Bois qui, eux, partaient tous en même temps et créaient des gros bouchons (de ce qu'on m'a raconté).

D'ailleurs dans la catégorie Coureurs des Bois, il y a une catégorie spéciale pour ceux qui dorment dehors. C'est en fait le défi ultime, skier avec tout son matériel de camping d'hiver et dormir au camp aménagé (avec feu de camp et balles de foin) le samedi soir. Moi personnellement, je ne regrette pas du tout de trouver une douche et un endroit chaud et sec après 80 km de ski. Je m'étais dis à un moment que j'essayerai le camping d'hiver au Québec, mais finalement… ça me tente plus trop !

Bref, le Marathon Canadien de Ski aura été pour moi une superbe expérience, j'en garde plein de bon souvenir et je suis pas mal plus confiant et fier de mes capacités en ski de fond. Presque je m'en veux un peu d'avoir douté de mes capacités au moment de l'inscription !

Départ du jour 2 Dernier checkpoint Médaille

February 12, 2019 11:02 PM

February 11, 2019

Luc Didry

Un dernier merci à mes tipeurs 🙂

Pourquoi un « dernier merci » ?

Et bien… tout simple­ment parce que je vais couper mes pages Tipeee et Libe­ra­pay.

Cela fait plus de deux ans que je reçois des dons pour ce que je fais pour le libre, et ça fait chaud au cœur. Vrai­ment. Énor­mé­ment. Et ça m’a beau­coup motivé à déve­lop­per.

Néan­moins, je consi­dère que je n’ai pas besoin de ces dons, vu que je gagne confor­ta­ble­ment ma vie. De plus, j’avoue, j’éprouve une certaine lassi­tude à parfois me forcer à déve­lop­per pour ne pas lais­ser tomber mes dona­trices et dona­teurs.

Je préfère donc couper mes pages de don.

Si vous souhai­tez faire des dons à d’autres, il y a une chouette dépêche LinuxFr qui liste plein d’as­so­cia­tions qui seraient heureuses de rece­voir des dons. Pis y a le projet Fedi­lab (ancien­ne­ment Masta­lab) qui est chouette : d’un client Masto­don, c’est devenu un client pour plein de service de la fédi­verse, c’est vrai­ment un chouette projet et le déve­lop­pe­ment est très soutenu. Pour les dons, ça se passe ou alors y a la collecte pour que le déve­lop­peur puisse se payer un ordi­na­teur plus costaud pour déve­lop­per dans de bonnes condi­tions.

Ne vous inquié­tez pas, je ne vais pas arrê­ter de faire du libre, mais… disons que je pren­drais sans doute plus mon temps 😉

Et donc… Un dernier merci

Le 14 juillet 2016, j’ai lancé mes pages Tipeee et Libe­ra­pay.

La récom­pense de base est l’ap­pa­ri­tion sur une page mensuelle de remer­cie­ments… voici celle de janvier. Merci à :

Et un groooooooooooooos merci à tout ceux qui m’ont soutenu pendant ces plus de deux ans. Merci du fond du cœur ♥️


Voici mon bilan de janvier :

Crédits : Photo par Alex Hu sur Unsplash

by Luc at February 11, 2019 04:04 PM

February 03, 2019

Pierre Boesch

RocketChat : chargement infini sur Firefox

Problème incompréhensible au premier abord : après s'être identifié sur une instance RocketChat, le chargement tourne en boucle. L'un des premiers réflexes est de tester un autre navigateur (Chromium par exemple) : ça fonctionne. L'instance RocketChat n'est pas à remettre en cause…

Du coup ça confirme un problème avec Firefox. Deuxième réflexe : vider le cache web. Non ? Non. Troisième réflexe : redémarrer le navigateur sans les extensions. Peut-être que ça débloquera la situation chez vous ; il ne vous restera donc qu'à désactiver petit à petit les extensions pour trouver la coupable. De mon côté, ça ne marchait quand même pas.

Du coup il ne reste plus qu'à débugger… On ouvre la console du navigateur en étant dans l'onglet de RocketChat : Ctrl + Shift + K puis reload F5. On avance :

TypeError: localStorage is null

Autant dire qu'on trouve tout de suite de quoi il s'agit : le localstorage de Firefox est désactivé. Pour le réactiver :

  • about:config dans la barre de recherche
  • dom.storage.enabled le passer à True

On reload à nouveau RocketChat : on peut à nouveau profiter de sa lourdeur et voir son CPU être bouffé petit à petit :)

by Pierre Boesch at February 03, 2019 11:00 PM

January 31, 2019

Pierre Boesch

Fixer les warnings après l'upgrade vers Pelican 4

Depuis l'upgrade de Pelican 3.7 vers Pelican 4, lors de la génération du html avec make on voit rapidement des warnings liés à certains paramètres.

Le travail sur la rétro-compatibilité étant bien géré par les développeurs, les warnings ne sont pas du tout gênant voir bloquant mais autant les corriger avant de ne plus avoir le choix. De mon côté je n'avais que deux warnings. On va voir comment les fixer.

Premier warning probablement classique :

WARNING: {filename} used for linking to staticcontent pdf/CV_BOESCH_Pierre.pdf in pages/about.md. Use {static} instead

Pour fixer, on édite la page/article en question et on remplace la syntaxe {filename} lié à un contenu statique par une syntaxe plus explicite : {static}

vim content/pages/about.md

-Mon CV est consultable [en cliquant ici]({filename}/pdf/CV_BOESCH_Pierre.pdf).
+Mon CV est consultable [en cliquant ici]({static}/pdf/CV_BOESCH_Pierre.pdf).

Second warning se corrigeant rapidement :

WARNING: %s usage in CATEGORY_FEED_ATOM is deprecated, use {slug} instead.

On grep pour savoir où cette variable est utilisée :

$ grep -ir CATEGORY_FEED_ATOM .
./pelicanconf.py:CATEGORY_FEED_ATOM = None
./publishconf.py:CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'

On ouvre publishconf.py

vim publishconf.py

-CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
+CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'

C'est tout ! Les warnings d'un upgrade de Pelican 3 vers Pelican 4 sont fixés !

Le changelog complet de Pelican 4 se trouve ici. Et comme d'habitude pour obtenir de l'aide c'est par là.

by Pierre Boesch at January 31, 2019 11:00 PM

Fixer les warnings après l'upgrade de Pelican 4

Depuis l'upgrade de Pelican 3.7 vers Pelican 4, lors de la génération du html avec make on voit rapidement des warnings liés à certains paramètres.

Le travail sur la rétro-compatibilité étant bien géré par les développeurs, les warnings ne sont pas du tout gênant voir bloquant mais autant les corriger avant de ne plus avoir le choix. De mon côté je n'avais que deux warnings. On va voir comment les fixer.

Premier warning probablement classique :

WARNING: {filename} used for linking to staticcontent pdf/CV_BOESCH_Pierre.pdf in pages/about.md. Use {static} instead

Pour fixer, on édite la page/article en question et on remplace la syntaxe {filename} lié à un contenu statique par une syntaxe plus explicite : {static}

vim content/pages/about.md

-Mon CV est consultable [en cliquant ici]({filename}/pdf/CV_BOESCH_Pierre.pdf).
+Mon CV est consultable [en cliquant ici]({static}/pdf/CV_BOESCH_Pierre.pdf).

Second warning se corrigeant rapidement :

WARNING: %s usage in CATEGORY_FEED_ATOM is deprecated, use {slug} instead.

On grep pour savoir où cette variable est utilisée :

$ grep -ir CATEGORY_FEED_ATOM .
./pelicanconf.py:CATEGORY_FEED_ATOM = None
./publishconf.py:CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'

On ouvre publishconf.py

vim publishconf.py

-CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
+CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'

C'est tout ! Les warnings d'un upgrade de Pelican 3 vers Pelican 4 sont fixés !

Le changelog complet de Pelican 4 se trouve ici. Et comme d'habitude pour obtenir de l'aide c'est par là.

by Pierre Boesch at January 31, 2019 11:00 PM

January 30, 2019

Pierre Boesch

HAProxy : Redirection HTTP en utilisant les maps

Une map est globalement un fichier contenant une structure de keys / values. Si vous avez une liste importante de redirection à faire (un domaine vers un autre, vers URI, etc) il est intéressant de se servir des maps afin de gagner en lisibilité sur la configuration finale de HAProxy. C'est quand même plus sympa d'avoir une ligne de configuration pour 10 domaines que 10 lignes d'acls, non ? :)

Création d'un fichier map

Il faut commencer par créer son premier fichier de map. Personnellement, je les place dans un dossier maps.

mkdir /etc/haproxy/maps

Par convention, j'utilise l'extension .map.

touch /etc/haproxy/maps/domain.map

Maintenant qu'on a notre fichier, il ne reste plus qu'à le remplir…

Les exemples qui vont suivre n'existent pas mais n'hésitez pas à consulter les sites en question ;-)

Redirection d'un domaine vers un second

Prenons le cas le plus classique : redirection de domain.tld vers www.domain.tld

vim /etc/haproxy/maps/domain.map

Admettons que l'on veuille rediriger :

On reprend notre fichier de map crée précédemment et on fait une liste key/value

/etc/haproxy/maps/domain.map

toucher.rectal.digital    www.toucher.rectal.digital
chiffrer.info             www.chiffrer.info

Puis on termine par créer la règle dans la configuration de HAProxy : /etc/haproxy/haproxy.cfg

frontend http
  [...]
  http-request redirect code 301 location https://%[hdr(host),map(/etc/haproxy/maps/domain.map)]%HU if { hdr(host),map(/etc/haproxy/maps/domain.map) -m found }

On dit simplement à HAProxy : si tu trouves le domaine appelé (key) alors redirection (301) vers le domaine que je souhaite (value) en gardant l'URI d'origine (%HU).

Avec cette configuration ça ne marchera que pour un domaine (%hdr(host)). Et si l'on veut rediriger une URI vers un domaine en particulier (#SEO) ?

Redirection d'une URI vers un domaine spécifique

Si l'on souhaite cette fois redirigier :

  • www.chiffrer.info/faq vers faq.chiffrer.info
  • toucher.rectal.digital/crypter vers chiffrer.info/onditpascrypter

En application : /etc/haproxy/maps/path.map

www.chiffrer.info/faq              https://faq.chiffrer.info
toucher.rectal.digital/crypter     https://chiffrer.info/onditpascrypter

/etc/haproxy/haproxy.cfg

frontend http
  [...]
  http-request redirect location %[base,map(/etc/haproxy/maps/path.map)] code 301 if { base,map(/etc/haproxy/maps/path.map) -m found }

Même explication que précédemment. Si l'on test la première redirection mise en place :

$ curl -I www.chiffrer.info/faq
HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https://faq.chiffrer.info

Redirection à base de regex

Avec les regex on va pouvoir travailler sur des redirections plus complexes.

Si l'on reprend en exemple les deux domaines précédents et que l'on veuille rediriger A vers B en récupérant un ID aléatoire dans l'URI ?

Prenons un cas classique :

www.chiffrer.info/faq?id=1337 vers faq.chiffre.info/1337

La map doit ressembler à ça

^www.chiffrer.info/faq?id=[0-9]+$      faq.chiffre.info/\1

Globalement la regex va matcher le numéro d'id et l'ajouter à la back reference (\1).

Pour ce cas précis, il faut disposer de haproxy 1.7 minimum qui introduit la map map_regm.

Toujours dans le frontend :

http-request redirect location %[base,map_regm(/etc/haproxy/maps/reg.map)] code 301 if { base,map_regm(/etc/haproxy/maps/reg.map) -m found }

Cet exemple n'est pas testé mais ça devrait marcher ;-)

Ressources

by Pierre Boesch at January 30, 2019 11:00 PM

Haproxy - Redirection en utilisant les maps

Une map est globalement un fichier contenant une structure de keys / values. Si vous avez une liste importante de redirection à faire (un domaine vers un autre, vers URI, etc) il est intéressant de se servir des maps afin de gagner en lisibilité sur la configuration finale de haproxy. C'est quand même plus sympa d'avoir une ligne de configuration pour 10 domaines que 10 lignes d'acls, non ? :)

Création d'un fichier map

Il faut commencer par créer son premier fichier de map. Personnellement, je les place dans un dossier maps.

mkdir /etc/haproxy/maps

Par convention, j'utilise l'extension .map.

touch /etc/haproxy/maps/domain.map

Maintenant qu'on a notre fichier, il ne reste plus qu'à le remplir…

Les exemples qui vont suivre n'existent pas mais n'hésitez pas à consulter les sites en question ;-)

Redirection d'un domaine vers un second

Prenons le cas le plus classique : redirection de domain.tld vers www.domain.tld

vim /etc/haproxy/maps/domain.map

Admettons que l'on veuille rediriger :

On reprend notre fichier de map crée précédemment et on fait une liste key/value

/etc/haproxy/maps/domain.map

toucher.rectal.digital    www.toucher.rectal.digital
chiffrer.info             www.chiffrer.info

Puis on termine par créer la règle dans la configuration de haproxy : /etc/haproxy/haproxy.cfg

frontend http
  [...]
  http-request redirect code 301 location https://%[hdr(host),map(/etc/haproxy/maps/domain.map)]%HU if { hdr(host),map(/etc/haproxy/maps/domain.map) -m found }

On dit simplement à haproxy : si tu trouves le domaine appelé (key) alors redirection (301) vers le domaine que je souhaite (value) en gardant l'URI d'origine (%HU).

Avec cette configuration ça ne marchera que pour un domaine (%hdr(host)). Et si l'on veut rediriger une URI vers un domaine en particulier (#SEO) ?

Redirection d'une URI vers un domaine spécifique

Si l'on souhaite cette fois redirigier :

  • www.chiffrer.info/faq vers faq.chiffrer.info
  • toucher.rectal.digital/crypter vers chiffrer.info/onditpascrypter

En application : /etc/haproxy/maps/path.map

www.chiffrer.info/faq              https://faq.chiffrer.info
toucher.rectal.digital/crypter     https://chiffrer.info/onditpascrypter

/etc/haproxy/haproxy.cfg

frontend http
  [...]
  http-request redirect location %[base,map(/etc/haproxy/maps/path.map)] code 301 if { base,map(/etc/haproxy/maps/path.map) -m found }

Même explication que précédemment. Si l'on test la première redirection mise en place :

$ curl -I www.chiffrer.info/faq
HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https://faq.chiffrer.info

Redirection à base de regex

Avec les regex on va pouvoir travailler sur des redirections plus complexes.

Si l'on reprend en exemple les deux domaines précédents et que l'on veuille rediriger A vers B en récupérant un ID aléatoire dans l'URI ?

Prenons un cas classique :

www.chiffrer.info/faq?id=1337 vers faq.chiffre.info/1337

La map doit ressembler à ça

^www.chiffrer.info/faq?id=[0-9]+$      faq.chiffre.info/\1

Globalement la regex va matcher le numéro d'id et l'ajouter à la back reference (\1).

Pour ce cas précis, il faut disposer de haproxy 1.7 minimum qui introduit la map map_regm.

Toujours dans le frontend :

http-request redirect location %[base,map_regm(/etc/haproxy/maps/reg.map)] code 301 if { base,map_regm(/etc/haproxy/maps/reg.map) -m found }

Cet exemple n'est pas testé mais ça devrait marcher ;-)

Ressources

by Pierre Boesch at January 30, 2019 11:00 PM

January 24, 2019

Romain Dessort

List changed configuration files and restore the maintainer's version

Recently I had to clean one of my server (a virtual machine hosted by Tetaneutral) to make it as close to a fresh install as possible, but without reinstalling it or migrate it somewhere else. It is an old server and it has been installed by hand. Since I now use Ansible to manage my other servers, I wanted to include it to the inventory and apply the same configuration on it.

On Archlinux, I used to use this command which list me all configuration files (in /etc) which have diverged from the version shipped by the package:

$ pacman -Qii | awk '/^MODIFIED/ {print $2}'

I didn't find a simple equivalent way to do it in Debian with dpkg tools, however I found debsums, an extra package that does the job pretty well:

$ debsums -se

I then can restore the package's version by reinstalling the corresponding package with --force-confask option:

# apt install --reinstall -o Dpkg::Options::="--force-confask"

Here is an oneliner to automate the process (careful, it doesn't ask for any confirmation!):

# debsums -se 2>&1 |awk '{print $4}' |xargs -n 1 dpkg -S |awk -F: '{print $1}' |sort -u |xargs apt install --reinstall -o Dpkg::Options::="--force-confask" -o Dpkg::Options::="--force-confnew"

Since xargs doesn't work well with interactive commands, I force the choice by passing both --force-confask and --force-confnew options.

As a conclusion, keep in mind the configuration files added in *.d/ directories won't be listed nor removed, so you still have to clean these directories manually.

January 24, 2019 01:43 AM

January 20, 2019

Florent Peterschmitt

Namasté Népal - #4.2

Article précédent

La suite des photos de montagne, le 23 avril 2017 ^^

photo photo photo

Notre pote singe blanc de la vielle ? Nah, il y en avait tout de même plusieurs ^^

photo photo

La personne en bas à droite c’était mon guide. On parlait tout deux suffisemment d’anglais pour pouvoir discuter de choses et d’autres, de la situation du pays, de sa vie etc…

photo photo photo photo photo photo

On commence à sortir de la forêt où la température était écrasante et l’absence de circulation d’air rendait la marche franchement difficile.

Maintenant c’est tout l’inverse : le vent souffle et il est froid ! Je me suis sévèrement enrhumé à ce moment là d’ailleurs… avec fièvre et tout le tintouin.

photo photo photo

photo photo photo photo photo photo

Sur les prochaines photos, on observe ce qu’il reste de la montagne qui s’est effondrée lors du tremblement de terre du 25 avril 2015 dans le pays.

Un village d’environ 200 habitants a été rasé et beaucoup y sont morts.

photo photo photo

Le village a été reconstruit plus haut et c’est là qu’on passera la nuit.

photo

Article suivant

by Florent Peterschmitt at January 20, 2019 08:30 PM

January 14, 2019

Luc Didry

Instal­ler un serveur Bitwar­den_rs

Avoir des mots de passe solides, c’est bien (et même indis­pen­sable), mais s’en souve­nir… c’est dur. On serait tenté de tout enre­gis­tré dans son navi­ga­teur, avec un mot de passe maître pour stocker les mots de passe de façon chif­frée mais… Fire­fox par exemple a un système de chif­fre­ment par mot de passe maître tout pourri.

Il existe un paquet de gestion­naire de mot de passe libre. Évacuons tous ceux basé sur KeePass ou ses succes­seurs : pour utili­ser ses mots de passe sur un autre PC ou sur son télé­phone, il faut synchro­ni­ser le fichier conte­nant les mots de passe et s’il y a un souci de synchro et que le fichier se retrouve corrom­pu… paf, pastèque !.

J’ai essayé Pass­bolt ou encore l’ap­pli­ca­tion Pass­man de Next­cloud mais quand j’uti­li­sais la sécu­rité maxi­male qu’ils propo­sent… mon Fire­fox plan­tait (oui, quand on me propose des réglages de parano, je les utilise). Sans comp­ter qu’ils ne proposent pas d’ap­pli Android.

Et puis, j’ai testé Bitwar­den. Et c’est pas mal du tout, mais… c’est du C# et ça utilise une base de données SQL Server. Deux trucs Micro­soft.

Bon, passant outre mon aver­sion, j’ai testé. Dans du Docker (quand je vous dis que je suis passé outre mon aver­sion !). C’était pas mal du tout.

Mais en voulant tester le partage de mot de passe (dans l’hy­po­thèse d’une utili­sa­tion chez Frama­soft), j’ai rencon­tré une limi­ta­tion : pour créer une orga­ni­sa­tion (un groupe de personnes qui se partagent des mots de passe) de plus de deux personnes, il faut payer, même quand on héberge soi-même le serveur Bitwar­den. Pourquoi pas. C’est un busi­ness model comme un autre (mais bon, grmpf).

Par contre, les tarifs, c’est un très gros grmpf : 3 dollars par utili­sa­teur et par mois. Pour Frama­soft, cela ferait 3$ * 35 personnes * 12 mois = 1 260$ par an. Y a un autre plan qui pour­rait conve­nir, qui nous revien­drait à 780$ par an. C’est quand même pas négli­geable (surtout pour une asso­cia­tion qui ne vit quasi­ment que de vos dons) alors qu’on héber­ge­rait nous-même le service.

J’avais laissé tombé quand ces derniers jours, une implé­men­ta­tion en Rust de Bitwar­den est passée dans ma time­line Masto­don : https://github.com/dani-garcia/bitwar­den_rs. Et celle-ci n’a pas la limi­ta­tion sur les équipes (mais il encou­rage à donner des sous au projet upstream).

J’ai d’abord testé avec le contai­ner Docker fourni par le déve­lop­peur puis j’ai tenté la compi­la­tion pour voir si ça passait, et ça passe crème !

La grosse diffé­rence, outre la non-limi­ta­tion des orga­ni­sa­tions, c’est que la base de données est, pour l’ins­tant, SQLite. Ce qui peut avoir des consé­quences sur les perfor­mances lorsque la base est très solli­ci­tée.

Ceci dit, Frama­drop tourne toujours avec une base SQLite et ça fonc­tionne bien, et mon instance Lutim, ainsi que Frama­pic ont long­temps utilisé SQLite.

Bref, voyons comment compi­ler et instal­ler cette version de Bitwar­den en Rust.

ATTENTION : allez plutôt voir le tuto­riel sur https://wiki.fiat-tux.fr/admin:logi­ciels:bitwar­den_rs, c’est plus simple pour moi de le main­te­nir sur mon wiki.

Compi­la­tion

On va avoir besoin des back­ports Debian pour instal­ler npm (pour compi­ler l’in­ter­face web) :

echo "deb http://ftp.debian.org/debian stretch-backports main" | sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update

Instal­la­tion des dépen­dances :

sudo apt install pkg-config libssl-dev
sudo apt install -t stretch-backports npm

Instal­la­tion de rustup, qui nous four­nira le compi­la­teur Rust :

curl https://sh.rustup.rs -sSf > rustup.sh

On n’exé­cute pas direct un script tiré du web ! On regarde d’abord s’il ne va pas faire de salo­pe­ries :

vi rustup.sh

On le rend exécu­table :

chmod +x rustup.sh

On installe le compi­la­teur Rust (il sera dans notre $HOME) :

./rustup.sh --default-host x86_64-unknown-linux-gnu --default-toolchain nightly

On source un fichier qui nous permet de l’ap­pe­ler

source $HOME/.cargo/env

Gagnons du temps en clonant le projet bitwar­den_rs et l’in­ter­face web (qu’il faut compi­ler aussi) en même temps .

git clone https://github.com/dani-garcia/bitwarden_rs
git clone https://github.com/bitwarden/web.git web-vault

Compi­la­tion de Bitwar­den_rs :

cd bitwarden_rs
cargo build --release

Le résul­tat de la compi­la­tion est dans bitwarden_rs/target/release/.

Compi­la­tion de l’in­ter­face web :

cd ../web-vault
# On se positionne sur le dernier tag en date
git checkout "$(git tag | tail -n1)"
# Un petit patch pour que ça fonctionne avec notre installation
wget https://raw.githubusercontent.com/dani-garcia/bw_web_builds/master/patches/v2.8.0.patch
# On vérifie le patch
cat v2.8.0.patch
git apply v2.8.0.patch
npm run sub:init
npm install
npm run dist

ATTENTION : on m’a dit que la compi­la­tion de l’in­ter­face web prenait 1,5Gio de RAM, assu­rez-vous que vous en avez assez de libre.

Et on copie l’in­ter­face web dans le dossier où attend le résul­tat de la compi­la­tion de bitwar­den_rs :

cp -a build/ ../bitwarden_rs/target/release/web-vault/

Instal­la­tion

On va instal­ler Bitwar­den_rs dans /opt/bitwarden et on le fera tour­ner avec l’uti­li­sa­teur www-data :

cd ..
sudo rsync -a --info=progress2 bitwarden_rs/target/release/ /opt/bitwarden/
chown -R www-data: /opt/bitwarden

Puis on va créer un service systemd, /etc/systemd/system/bitwarden.service :

[Unit]
Description=Bitwarden Server (Rust Edition)
Documentation=https://github.com/dani-garcia/bitwarden_rs
After=network.target

[Service]
# The user/group bitwarden_rs is run under. the working directory (see below) should allow write and read access to this user/group
User=www-data
Group=www-data
# The location of the .env file for configuration
EnvironmentFile=/etc/bitwarden_rs.env
# The location of the compiled binary
ExecStart=/opt/bitwarden/bitwarden_rs
# Set reasonable connection and process limits
LimitNOFILE=1048576
LimitNPROC=64
# Isolate bitwarden_rs from the rest of the system
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
# Only allow writes to the following directory and set it to the working directory (user and password data are stored here)
WorkingDirectory=/opt/bitwarden/
ReadWriteDirectories=/opt/bitwarden/

[Install]
WantedBy=multi-user.target

Pour l’in­ter­face d’ad­mi­nis­tra­tion, on va créer un token avec :

openssl rand -base64 48

La confi­gu­ra­tion se fait via des variables d’en­vi­ron­ne­ment qu’on va mettre dans /etc/bitwarden_rs.env :

SIGNUPS_ALLOWED=false
WEBSOCKET_ENABLED=true
ADMIN_TOKEN=Un token généré avec `openssl rand -base64 48`
ROCKET_ADDRESS=127.0.0.1
WEBSOCKET_ADDRESS=127.0.0.1
SMTP_HOST=127.0.0.1
SMTP_FROM=bitwarden@example.org
SMTP_PORT=25
SMTP_SSL=false

Vous remarque­rez que je dis à Bitwar­den d’en­voyer les mails via le serveur SMTP local. À vous de faire en sorte qu’il fonc­tionne. Allez voir le wiki du projet pour voir quelles variables vous pour­riez ajou­ter, enle­ver, modi­fier…

Puis :

sudo systemctl daemon-reload
sudo systemctl enable bitwarden
sudo systemctl start bitwarden
sudo systemctl status bitwarden

Nginx

On installe Nginx s’il n’est pas déjà installé :

sudo apt install nginx

Confi­gu­ra­tion du virtual­host :

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name bitwarden.example.org;

    access_log /var/log/nginx/bitwarden.access.log;
    error_log /var/log/nginx/bitwarden.error.log;

    ssl_certificate      /etc/letsencrypt/live/bitwarden.example.org/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/bitwarden.example.org/privkey.pem;

    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:5m;

    ssl_prefer_server_ciphers On;
    ssl_protocols TLSv1.2;
    ssl_ciphers 'EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA';

    ssl_dhparam /etc/ssl/private/dhparam4096.pem;
    add_header Strict-Transport-Security max-age=15768000; # six months
    gzip off;

    if ($https != 'on') {
        rewrite ^/(.*)$ https://bitwarden.example.org/$1 permanent;
    }

    root /var/www/html;

    # Allow large attachments
    client_max_body_size 128M;

    location ^~ '/.well-known/acme-challenge' {
        default_type "text/plain";
        root /var/www/certbot;
    }

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://127.0.0.1:8000;
    }

    location /notifications/hub {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:3012;
    }

    location /notifications/hub/negotiate {
        proxy_pass http://127.0.0.1:8000;
    }
}

Pour créer /etc/ssl/private/dhparam4096.pem :

sudo openssl dhparam -out /etc/ssl/private/dhparam4096.pem 4096

Pour le certi­fi­cat Let’s Encrypt, on commente le brol rela­tif à ssl puis :

sudo nginx -t && sudo nginx -s reload
sudo apt install certbot
sudo mkdir /var/www/certbot/
certbot certonly --rsa-key-size 4096 --webroot -w /var/www/certbot/ --agree-tos --text --renew-hook "/usr/sbin/nginx -s reload" -d bitwarden.example.org

Une fois qu’on a le certi­fi­cat, on décom­mente le brol ssl puis :

sudo nginx -t && sudo nginx -s reload

Sauve­garde

Créer le script de sauve­garde /opt/backup_bitwarden.sh :

#!/bin/bash
function bwbackup {
    DATE=$(date '+%a%H')

    # Database
    if [[ ! -d /opt/backup_bitwarden/sqlite-backup/ ]]
    then
        mkdir -p /opt/backup_bitwarden/sqlite-backup/
    fi
    echo ".backup /opt/backup_bitwarden/sqlite-backup/db.${DATE}.sqlite3" | sqlite3 /opt/bitwarden/data/db.sqlite3 2>> /opt/backup_bitwarden/backup.log
    if [[ "$?" -ne "0" ]]
    then
        echo "Something went wrong with bitwarden database backup, please see /opt/backup_bitwarden/backup.log on verity" | mail -s "Bitwarden database backup" youraddress@mail.example.org
        bwbackup
    fi

    # Files
    if [[ ! -d /opt/backup_bitwarden/files-backup/ ]]
    then
        mkdir -p /opt/backup_bitwarden/files-backup/
    fi
    rsync -a --delete --exclude db.sqlite3 /opt/bitwarden/data/ /opt/backup_bitwarden/files-backup/$DATE/ 2>> /opt/backup_bitwarden/backup.log
    if [[ "$?" -ne "0" ]]
    then
        echo "Something went wrong with bitwarden files backup, please see /opt/backup_bitwarden/backup.log on verity" | mail -s "Bitwarden files backup" youraddress@mail.example.org
        bwbackup
    fi
}
bwbackup

Puis :

sudo chmod +x /opt/backup_bitwarden.sh
sudo mkdir /opt/backup_bitwarden
sudo chown www-data: /opt/backup_bitwarden
sudo apt install sqlite3

Puis, dans le cron de l’uti­li­sa­teur www-data :

42 4 * * * /opt/backup_bitwarden.sh

Logs

J’aime bien avoir mes logs dans un dossier dédié pour ce genre de service.

Dans /etc/rsyslog.d/bitwarden.conf :

if $programname == 'bitwarden_rs' then /var/log/bitwarden/bitwarden.log
if $programname == 'bitwarden_rs' then ~

Dans /etc/logrotate.d/bitwarden :

/var/log/bitwarden/bitwarden.log
{
        rotate 52
        dateext
        weekly
        missingok
        notifempty
        compress
        sharedscripts
        postrotate
                invoke-rc.d rsyslog rotate > /dev/null
        endscript
}

Puis :

sudo mkdir /var/log/bitwarden
sudo chown root:adm /var/log/bitwarden
sudo service rsyslog restart

Fail2­ban

Un fail2­ban qui surveille les logs, ça permet de bloquer les petits malins qui font du brute­force

Dans /etc/fail2ban/filter.d/bitwarden.conf :

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <HOST>\. Username:.*$
ignoreregex =

Dans /etc/fail2ban/jail.d/bitwarden.local :

[bitwarden]
enabled = true
port = 80,443
filter = bitwarden
action = iptables-allports[name=bitwarden]
logpath = /var/log/bitwarden/bitwarden.log
maxretry = 3
bantime = 14400
findtime = 14400

Pour la page d’ad­min, dans /etc/fail2ban/filter.d/bitwarden-admin.conf :

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*Unauthorized Error: Invalid admin token\. IP: <HOST>.*$
ignoreregex =

Dans /etc/fail2ban/jail.d/bitwarden-admin.local :

[bitwarden-admin]
enabled = true
port = 80,443
filter = bitwarden-admin
action = iptables-allports[name=bitwarden]
logpath = /var/log/bitwarden/bitwarden.log
maxretry = 3
bantime = 14400
findtime = 14400

Fina­le­ment :

sudo service fail2ban restart

Conclu­sion

Voilà, vous devriez avoir un serveur Bitwar­den_rs fonc­tion­nel. Plus qu’à aller sur l’in­ter­face web que vous venez de mettre en place ou télé­char­ger les clients et à les utili­ser !

Pour impor­ter vos mots de passe de Fire­fox, il faut passer par une appli­ca­tion pour les expor­ter, puis aller dans les outils de votre client (ou de l’in­ter­face web).

EDIT 15/01/2019 Rempla­ce­ment de la mention de l’ap­pli­ca­tion Next­cloud Pass­words par l’ap­pli­ca­tion Pass­man (je me suis trompé).

EDIT 17/01/2019 Rempla­ce­ment de npm run dist par npm run dist:selfhost dans la compi­la­tion de l’in­ter­face web. Cela désac­tive Google Analy­tics (voir https://github.com/bitwar­den/web/issues/243#issue­comment-412852726)

EDIT 18/01/2019 Rempla­ce­ment de npm run dist:selfhost par npm run dist dans la compi­la­tion de l’in­ter­face web. Google Analy­tics est déjà désac­tivé par le patch. Il semble­rait que j’ai vu passer des requêtes vers Google Analy­tics à cause du cache de mon navi­ga­teur qui gardait une version non patchée.

EDIT 18/01/2019 Ajout d’un aver­tis­se­ment sur la consom­ma­tion de RAM lors de la compi­la­tion de l’in­ter­face web.

Crédits : Photo par Matt Artz sur Unsplash

by Luc at January 14, 2019 07:02 PM

January 13, 2019

Florent Peterschmitt

Namasté Népal - #4.1

Récapitulatif

Dans l’article précédent j’étais donc à Pokhara et je sortais d’une grotte remplie de chauves-souris. Une fois ressorti j’avais prévu de passer encore une journée sur place, après-demain sera donc le retour vers Kathmandu pour commencer le trekking de la vallée de Langtang.

Le départ est à Syapru Besi, un village à fond pour les touristes venant marcher dans les montagnes. Rien de très étonnant à vrai dire.

L’arrivée était prévue à 4500 mètres d’altitudes, je me suis arrêté à environ 3000 mètres, avec de la fièvre (oui, je suis retombé malade…) et le souffle coupé. Comme quoi il faut s’entraîner avant, oui bien être fumeur : d’après mon guide les fumeurs, même sans expérience de la montagne arrivent à aller au bout. Ce qui est compréhensible vu qu’un fumeur a régulièrement un manque d’oxygène dans les poumons !

J’ai découpé le trek en 4 articles à venir ; les photos sont déjà triées, reste plus qu’à en retoucher quelques unes et à publier.

Et comme toujours, un peu de shell :D

for img in $(find 2* -name "*.*"); do
    echo '![photo](/static/images/nepal2017/'${img}')' >> pics.md
done

22 Avril 2017 - Vallée de Langtang

map-trek

Démarrage à 1600 mètres environ !

photo photo photo photo photo photo photo photo

Sur la gauche de la photo, sur ce flan de montagne effondré, des nids d’abeilles. Elles font un miel qui est récolté.

photo

Premier « singe blanc », qui n’a pas voulu montrer sa tête. Impossible de l’avoir en plus gros, le zoom était déjà à son maximum.

photo photo photo

Arrivé au gîte (guest house) ce pti bonhomme parlait mieux anglais que quand j’avais son âge, et débordait d’énergie ! Il a voulu tester l’appareil photo, je l’ai laissé faire, bon, le résultat était pas probant :D

photo photo

Des rhododendrons !

photo

Soir nuageux.

photo

Matin dégagé.

Article suivant

by Florent Peterschmitt at January 13, 2019 03:15 PM

January 10, 2019

Romain Dessort

Joindre les 2 bouts de la Laurentienne

La région des Laurentides est très riche en pistes de ski nordique datant du siècle dernier, des sentiers historiques tracés en partie par des norvégiens, dont le fameux Jackrabbit, à l'époque où le ski de fond était populaire au Québec. Malheureusement aujourd'hui, beaucoup sont abandonnées, trop méconnues et réduites en morceaux à cause des développements immobilier et des propriétés privées. Découvrir et cartographier ces pistes historiques est devenu ma grande passion, et je skie plus dans le but de répertorier des nouvelles pistes que juste pour le plaisir de skier.

Si il y a un blog de ski nordique que je ne me lasse pas de lire, c'est celui de Barclay. J'ai découvert plusieurs pistes des Laurentides comme ça et c'est toujours intéressant d'avoir régulièrement les conditions des sentiers.

De mon côté jusqu'à présent, je me « contentais » de cartographier les sentiers de ski que je suivais dans OpenStreetMap. La région des Laurentides dans OSM a d'ailleurs explosé depuis quelques années, quelques autres personnes en rajoute aussi, et c'est maintenant clairement la carte la plus complète possible en terme de sentiers de ski (de fond ou de rando nordique). Mais en parler aussi ailleurs est toujours une bonne idée, donc je vais essayer de parler un peu plus de mes sorties ici. En tout cas au moins mes découvertes de pistes moins connues, car je n'aurai pas la motivation d'écrire à chaque fois, et il n'y a peu d'intérêt pour les pistes plus connues ou bien cartographiées.

Voila pour l'introduction, pour revenir à la Laurentienne, il faut retourner à la fin de saison de l'année dernière, j'avais repéré sur la carte du réseau nordique de Morin-Heights une piste qui s'en allait plein ouest, la Laurentienne (#24) puis qui s'arrêtait sans raison au milieu de nulle part. Ça a évidemment titillé ma curiosité et j'étais parti avec pour objectif de voir où elle allait. J'avais réussi à la suivre sur un bon bout au delà de la partie cartographiée, mais j'avais perdu sa trace ensuite dans une érablière et rendu sur le lac Ida. J'avais rebroussé chemin.

Samedi dernier donc, je l'ai prise en partant de Saint-Adolph, en stationnant au lac Louise, là où elle est censée arriver : la carte du réseau de Saint-Adolph montre aussi un début de piste qui a bien l'air de partir dans la bonne direction pour la connecter, mais ils ne l'ont pas cartographiée jusqu'au bout non plus.

À ma grande surprise, la piste était plutôt bien visible sur le terrain, et il y avait même quelques traces de skieurs et de raquetteurs qui m'avaient précédé. Au moins jusqu'à la route de la Montée des Quatres Lacs. Après c'était plus délicat à suivre mais la piste se devinait bien. J'ai pu retrouver l'endroit où je m'étais arrêté l'année dernière, près du lac Ida. La Laurentienne est maintenant complète dans OpenStreetMap !

Par contre des mystères persistent : sur certains sections (un peu avant le lac Ida), j'ai vu des très vieilles balises métalliques indiquant LSD, une autre piste porte déjà ce nom dans le secteur, c'est bizarre. Et après avoir passé la route de la Montée des Quatres Lacs, j'ai vu 2 panneaux dans le même style que ceux utilisés à Morin-Heights indiquant la Picher (#29) et la Dutch-Dash (#27). La Dutch-Dash est le début de la Laurentienne côté Morin-Heights. J'en déduis que ces 2 pistes restent confondues sur un bon bout. Mais après où va-t-elle ? Et la Picher, je n'en ai aucune idée de ce que c'est… Je ne la vois référencée sur aucune carte que j'ai.

Concernant l'allure de la Laurentienne, elle n'est en soit pas très difficile, il y a quelques up and down dans sa partie ouest mais la piste est suffisamment large pour pouvoir manœuvrer en descente. La partie reste essentiellement sur du plat, en passant à travers une belle forêt de cèdres à l'ouest du lac Ida, et longe des zones ruisseau et passe sur des zones marécageuses et des lacs dans sa partie est vers Morin-Heights.

J'étais parti avec l'idée que j'allais devoir débroussailler pas mal la piste, mais elle était assez propre. Il reste quand même quelques coups de scie à donner par-ci par-là si quelqu'un y retourne.

J'en ai aussi profité pour aller explorer la Castor (#6) qui connecte la Laurentienne à la Renard et traverse le lac Saint-Denis. Je me suis arrêté une fois rendu au lac car je voyais bien où est-ce que la Renard commence. C'est intéressant à avoir en tête car ça permet de faire une belle boucle entre Morin-Heights et Saint-Adolph.

January 10, 2019 07:13 PM