Articles

Noms d'hôte docker

Utilisant Docker quotidiennement et travaillant sur plusieurs projets, je souhaitais harmoniser les configurations et utiliser des noms de domaines locaux pour accéder à mes conteneurs.

Il y a plusieurs solutions existantes pour faire ça, comme Traefik ou DNS Proxy Server.

Traefik fonctionne bien, apporte beaucoup de fonctionnalités, mais n'est pas très léger pour une utilisation sur un poste de travail.

DNS Proxy Server fonctionne pas trop mal mais pose problème sur la résolution des noms de domaines externes aux conteneurs. Un simple ping google.fr ne passe pas si on utilise pas le réseau en mode bridge.

Ne trouvant pas mon bonheur dans ces solutions, j'ai décidé de créer un petit script shell qui se chargerai de maintenir à jour le fichier /etc/hosts.

Le principe est simple : écouter les événements de Docker lorsque des conteneurs sont démarrés, arrêtés, détruits ou tués, et mettre à jour le fichier /etc/hosts avec la liste des conteneurs en fonctionnement, en se basant sur la configuration hostname des conteneurs et leur IP assignée.

Écouter les événements Docker

Créons un script bash :

echo '!#/bin/bash' > docker-hosts.sh && chmod +x docker-hosts.sh

Pour écouter les événements Docker, j'ai trouvé ce gist que j'ai adapté :

function listen_docker_events() {
    docker events --filter 'event=start' --filter 'event=stop' --filter 'event=kill' --filter 'event=destroy' | while read event
    do
        #update_hosts
    done
}

Ajoutons des marqueurs d'emplacement dans /etc/hosts afin de cloisonner la mise à jour des IPs. J'ai mis des marqueurs largement inspirés des marqueurs des recettes flex.

###> docker-hosts ###
###< docker-hosts ###

Dès qu'un conteneur est démarré ou arrêté, on mettra à jour la liste de correspondance des IPs et des noms d'hôtes dans notre fichier /etc/hosts.

Mise à jour de /etc/hosts

Pour la mise à jour du fichier /etc/hosts nous allons créer une fonction nommée update_hosts.

function update_hosts() {
    CONTAINERS=$(docker ps -q | awk '{ print $1 }')

    HOST_PLACEHOLDER_START='###> docker\-hosts ###'
    HOST_PLACEHOLDER_END='###< docker\-hosts ###'
    HOST_ITEMS=''

    while read -r CONTAINER; do
        HOST_ITEMS+=$(echo -e $(docker inspect  --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}\t\t{{ .Config.Hostname }}' $CONTAINER))
        HOST_ITEMS+='\n'
    done <<< "$CONTAINERS"

    sed -i -e "/${HOST_PLACEHOLDER_START}/,/${HOST_PLACEHOLDER_END}/c\\${HOST_PLACEHOLDER_START}\n${HOST_ITEMS}${HOST_PLACEHOLDER_END}" /etc/hosts
}

Décomposons cette fonction :

CONTAINERS=$(docker ps -q | awk '{ print $1 }')

On récupère la liste des identifiants des conteneurs en cours de fonctionnement et on la stocke dans la variable CONTAINERS.

HOST_PLACEHOLDER_START='###> docker\-hosts ###'
HOST_PLACEHOLDER_END='###< docker\-hosts ###'
HOST_ITEMS=''

On définis les marqueurs qui serviront au remplacement et la variable qui contiendra la liste de correspondance des IPs et des noms d'hôte (HOST_ITEMS).

while read -r CONTAINER; do
# ...
done <<< "$CONTAINERS"

On boucle sur la liste des identifiants des conteneurs en cours de fonctionnement.

HOST_ITEMS+=$(echo -e $(docker inspect  --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}\t\t{{ .Config.Hostname }}' $CONTAINER))

À l'aide de la commande docker inspect, on récupère les informations utiles de chaque conteneur : Adresse IP et nom d'hôte à l'aide du format suivant :

{{ if gt (len .NetworkSettings.Networks) 1 }}
    {{ range \$name,\$network := .NetworkSettings.Networks }}
        {{ \$hasUnderscore := gt (len (split \$name "_")) 0 }}
        {{ if \$hasUnderscore }}
            {{ range \$part := (split \$name "_") }}
                {{ if eq \$part "default" }}
                    {{ \$network.IPAddress }}
                {{ end }}
            {{ end }}
        {{ else }}
            {{ .NetworkSettings.Networks.IPAddress }}
        {{ end }}
    {{ end }}
{{ else }}
    {{ range .NetworkSettings.Networks }}
        {{ .IPAddress }}
    {{ end }}
{{ end }}
\t\t
{{ .Config.Hostname }}

On remplace tout le contenu situé entre le marqueur de début ###> docker-hosts ### et le marqueur de fin ###< docker-hosts ### par la liste des IPs et noms d'hôtes de la variable HOST_ITEMS.

sed -i -e "/${HOST_PLACEHOLDER_START}/,/${HOST_PLACEHOLDER_END}/c\\${HOST_PLACEHOLDER_START}${HOST_ITEMS}${HOST_PLACEHOLDER_END}" /etc/hosts
Vu que /etc/hosts n'est inscriptible qu'en ayant les droits root, on pourra tester le script en essayant sur un fichier host de test situé dans le même répertoire que le script : cat /etc/hosts > ./hosts

Ensuite, on remplace dans la commande sed le fichier sur lequel le remplacement se fera :

sed -i -e "[...]" ./hosts

Déclarer le daemon

Déclarons un service systemd pour démarrer automatiquement ce script lors du démarrage de l'OS.

# /etc/systemd/system/docker-host.service
[Unit]
Description=Docker host daemon
After=docker.service

[Service]
ExecStart=/bin/bash <CHEMIN_VERS_VOTRE_SCRIPT>/docker-hosts.sh
Restart=on-failure

[Install]
WantedBy=multi-user.target

On active ensuite le service et on le lance :

sudo systemctl enable docker-host.service
sudo systemctl start docker-host.service

Exemple avec docker-compose

On utilise un docker-compose.yml avec 2 services basiques :

version: '3'
services:
    hello-world-1:
        image: strm/helloworld-http
        container_name: hello-world-1
        hostname: hello-world-1.papsou

    hello-world-2:
        image: strm/helloworld-http
        container_name: hello-world-2
        hostname: hello-world-2.papsou

Notre fichier /etc/hosts ressemble à ça avant le lancement :

# /etc/hosts

127.0.0.1              localhost localhost.localdomain localhost4 localhost4.localdomain4
::1                    localhost localhost.localdomain localhost6 localhost6.localdomain6

# Some fixed IP

# [...]

###> docker-hosts ###
###< docker-hosts ###

On lance ces 2 services :

docker-compose up -d --build
Creating hello-world-1 ... done
Creating hello-world-2 ... done

Et voici le fichier après le lancement :

# /etc/hosts

127.0.0.1              localhost localhost.localdomain localhost4 localhost4.localdomain4
::1                    localhost localhost.localdomain localhost6 localhost6.localdomain6

# Some fixed IP

# [...]

###> docker-hosts ###
192.168.48.3		hello-world-2.papsou
192.168.48.2		hello-world-1.papsou
###< docker-hosts ###

On accède donc au premier service hello-world-1 en se rendant sur http://hello-world-1.papsou et sur hello-world-2 en se rendant sur http://hello-world-2.papsou.

On coupe les services :

docker-compose down && cat /etc/hosts
Stopping hello-world-2 ... done
Stopping hello-world-1 ... done
Removing hello-world-2 ... done
Removing hello-world-1 ... done
Removing network hello-world_default

Et voici le contenu de /etc/hosts après la coupure :

# /etc/hosts

127.0.0.1              localhost localhost.localdomain localhost4 localhost4.localdomain4
::1                    localhost localhost.localdomain localhost6 localhost6.localdomain6

# Some fixed IP

# [...]

###> docker-hosts ###
###< docker-hosts ###

Le script final

#!/bin/bash

function update_hosts() {
    CONTAINERS=$(docker ps -q | awk '{ print $1 }')

    HOST_PLACEHOLDER_START='###> docker\-hosts ###'
    HOST_PLACEHOLDER_END='###< docker\-hosts ###'
    HOST_ITEMS=''
    INSPECT_FORMAT_STRING=$(cat << EOT
{{ if gt (len .NetworkSettings.Networks) 1 }}
    {{ range \$name,\$network := .NetworkSettings.Networks }}
        {{ \$hasUnderscore := gt (len (split \$name "_")) 0 }}
        {{ if \$hasUnderscore }}
            {{ range \$part := (split \$name "_") }}
                {{ if eq \$part "default" }}
                    {{ \$network.IPAddress }}
                {{ end }}
            {{ end }}
        {{ else }}
            {{ .NetworkSettings.Networks.IPAddress }}
        {{ end }}
    {{ end }}
{{ else }}
    {{ range .NetworkSettings.Networks }}
        {{ .IPAddress }}
    {{ end }}
{{ end }}
\t\t
{{ .Config.Hostname }}
EOT
    )

    while read -r CONTAINER; do
        HOST_ITEMS+=$(echo -e $(docker inspect  --format "${INSPECT_FORMAT_STRING}" $CONTAINER))
        HOST_ITEMS+='\n'
    done <<< "$CONTAINERS"

    sed -i -e "/${HOST_PLACEHOLDER_START}/,/${HOST_PLACEHOLDER_END}/c\\${HOST_PLACEHOLDER_START}\n${HOST_ITEMS}${HOST_PLACEHOLDER_END}" /etc/hosts
}

function listen_docker_events() {
    docker events --filter 'event=start' --filter 'event=stop' --filter 'event=kill' --filter 'event=destroy' | while read event
    do
        update_hosts
    done
}

listen_docker_events

Portainer

Procédure d'installation officielle : https://portainer.readthedocs.io/en/latest/deployment.html

Installation / Exécution de portainer

Exécuter la commande suivante :

/usr/bin/docker run -d -p 9000:9000 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /opt/portainer:/data \
    --name portainer \
    portainer/portainer

La première fois, cela télécharge l'image de Portainer, ensuite, cela lancera uniquement le conteneur.

Création d'un service Systemd

Créer un service systemd permet de lancer automatiquement Portainer au démarrage de votre machine.

Créer le fichier /etc/systemd/system/portainer.service et ajouter le contenu ci-dessous :

[Unit]
Description=Start Portainer at startup
After=network.target
Requires=docker.service

[Service]
Type=simple
KillMode=none
ExecStart=/usr/bin/docker start -a portainer
ExecStop=/usr/bin/docker stop -t 5 portainer

[Install]
WantedBy=default.target

Puis charger et activer le service.

sudo systemctl daemon-reload
sudo systemctl start portainer.service
sudo systemctl enable portainer.service
J'ai fais un thème Stylish (chrome ou firefox) pour que l'interface adopte un style sombre : https://userstyles.org/styles/176993/portainer-dark-red.

Aperçu du thème stylish

Ouvrir directement les fichiers sources

Astuce très utile permettant d'ouvrir directement les fichiers concernés dans le profiler Symfony dans votre IDE préféré.

Configurer php

Configurer Xdebug pour que les liens de fichiers (utilisés par le debugger Symfony) soient ouvert via une url spécifique.

Dans le fichier de configuration /etc/php/7.0/conf.d/20-xdebug.ini, ajouter (ou modifiez si déjà présent)

xdebug.file_link_format = 'ide://%f:%l'

Cela générera des liens de ce type :

ide://YOUR_PROJECT_PATH/index.php:65

Ajouter l'URL handler

Votre environnement de bureau doit savoir comment gérer ce nouveau protocole.

Pour gnome, cherchez où se trouve le fichier mimeapps.list

find ~/ -name mimeapps.list
/home/PapsOu/.config/mimeapps.list

Éditez-le et ajoutez l'entrée suivante :

[Added Associations]
x-scheme-handler/ide=ide-handler.desktop;

La chaîne doit commencer par x-scheme-handler/ suivi du nom de protocole personnalisé.

Fichier .desktop

Créez le fichier desktop qui se chargera de lancer le un script correspondant au protocole personnalisé.

nano ~/.local/share/applications/ide-handler.desktop
[Desktop Entry]
Version=1.0
Type=Application
Exec=/PATH_TO_YOUR_IDE_SCRIPT/run-ide.sh %u
StartupNotify=false
Terminal=false
Categories=Utility;
MimeType=x-scheme-handler/ide
Name=IDE quick open
Comment=Launch IDE

Ne pas oublier un chmod +x sur ce fichier pour qu'il soit exécutable.

Script de lancement d'IDE

Créez le script qui sera exécuté lors des clics sur ces liens.

Mettez-le où bon vous semble, adaptez simplement la ligne Exec du fichier .desktop précédent.

run-ide.sh :

#!/bin/bash

# Strip de la partie « ide:// » de l'url
url="${1:6}"

# Atom
# /bin/atom ${fileName}:${lineNumber}

# Netbeans
# /bin/netbeans $url

# VSCode
/usr/bin/code -g ${fileName}:${lineNumber}:0

Appliquer les changements

Redémarrez votre navigateur et gnome (Alt + F2 puis R) et le clic sur un lien de fichier du debugger ouvrira votre IDE avec le fichier et placera le curseur à la ligne correspondante.

(Optionnel) Configurer Firefox

Si Firefox n'arrive pas à associer les liens ide:// à votre script, il est peut être nécessaire de modifier sa configuration.

Ajouter les entrées suivantes dans about:config pour que Firefox sache que ce protocole nouvellement créé existe.

network.protocol-handler.expose.ide: false
network.protocol-handler.external.ide: true
network.protocol-handler.warn-external.ide: false

Rediriger sur le referer

Une astuce simple. Lorsqu'on fait un traitement côté contrôleur et qu'on désire rediriger l'utilisateur vers la page appelante, il suffit de faire une redirection basée sur le referer.

On récupère d'abord le referer dans un controlleur :

$referer = $request->headers->get('referer');

Tout simple. Ensuite on fait la redirection comme d'habitude :

return $this->redirect($referer);

Et voila.

Services symfony

Créer un service dans Symfony est très facile. Je vais présenter 2 méthodes pour créer un service rapidement.

L'injection de dépendance

On le voit partout, on le dit très souvent : Il ne faut pas injecter le conteneur de services comme dépendance à un service (ou à tout autre élément). C'est un peu comme écraser une mouche à coup de batte de baseball.

Mais parfois, ça permet de se simplifier la vie. Par exemple dans un contrôleur, par habitude, on accède à n'importe quel service en faisant un petit :

$monService = $this->getContainer()->get('nom.du.service');

ou

$monService = $this->container->get('nom.du.service');

Grâce à ça, on accède directement aux services. Simple, efficace, pratique.

Service de fainéant

Ajoutons la définition de notre service dans la configuration de notre bundle.

services:
    papsou.acme.monService:
        class: PapsOu\AcmeBundle\DependencyInjection\Services\MonService
        calls:
            - [setContainer, ["@service_container"]]

Et notre classe de service :

namespace PapsOu\AcmeBundle\DependencyInjection\Services;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class MonService implements ContainerAwareInterface
{
    /**
     * @var ContainerInterface
     */
    private $container;

    /**
     * @inheritdoc
     */
    public function setContainer(ContainerInterface $container = null): void {
        $this->container = $container;
    }

    public function maFonction($parameters): void {
        // Votre fonction...
        $unService = $this->container->get('un.service');
    }
}

Pour résumer, on créé un service qui à besoin du conteneur de service de Symfony pour fonctionner (car on utilise le conteneur dans une méthode de la classe du service). Cette classe doit implémenter l'interface ContainerAwareInterface pour injecter le conteneur. On peut très bien aussi injecter le conteneur par le biais d'une injection par constructeur.

Voila, c'est rapide, et on change pas trop nos habitudes. Seulement, c'est bien crade !

Service « Propre »

Cette fois-ci, on va devoir réfléchir un minimum sur ce point : De quels services ai-je besoin ?

Pour notre exemple, on va dire qu'on a besoin de l'EntityManager de Doctrine et d'un logger. Allons-y !

La définition de notre service :

services:
    papsou.acme.monService:
        class: PapsOu\AcmeBundle\DependencyInjection\Services\MonService
        calls:
            - [setEm, ["@doctrine.orm.entity_manager"]]
            - [setLogger, ["@logger"]]

Et notre classe de service :

namespace PapsOu\AcmeBundle\DependencyInjection\Services;

use Doctrine\ORM\EntityManager;
use Symfony\Bridge\Monolog\Logger;

class MonService
{
    /**
     * @var EntityManager
     */
    private $_em;

    /**
     * @var Logger
     */
    private $logger;

    /**
     * @param EntityManager $em
     */
    public function setEm(EntityManager $em = null): void {
        $this->_em = $em;
    }

    /**
     * @param Logger $logger
     */
    public function setLogger(Logger $logger): void {
        $this->logger = $logger;
    }

    public function maFonction($parameters): void {
        // Votre fonction...
        $mesEntites = $this->_em
            ->getRepository('PapsOuAcmeBundle:Entite')
            ->findAll();
        // On fait des traitement sur nos entités
        // On va logger ces manipulations
        $this->logger->info('mon message');
    }
}

Voila. C'est un peu plus long à écrire, il faut savoir quels services on va devoir utiliser pour notre service. Mais c'est bien plus propre !

Si comme moi, vous avez fait pas mal de services en injectant directement le conteneur (par flemme probablement), je vous conseille de trouver quelques minutes pour optimiser tout ça et remplacer l'injection du conteneur en injectant uniquement les services que vous utilisez dans vos propres services. Juste histoire d'être plus carré, ou juste histoire de se dire « mes services sont propres ».

Captures d'écran

Petit script bash pour automatiser un peu les captures d'écran :

#!/bin/bash
NOW=`date +"%Y%m%d"`
TIME=`date +"%H%M%S"`
DIRECTORY="${HOME}/Images/Screenshots/${NOW}"
mkdir -p "${DIRECTORY}"
gnome-screenshot -a -f "${DIRECTORY}/${NOW}-${TIME}.png"

En associant le raccourci clavier de la touche Imp. écran à ce script, la capture par défaut prend une région et enregistre l'image dans le dossier: ~/Images/Screenshots/%DATE_FORMAT_US%/%DATE_FORMAT_US%-%TIME_FORMAT_US%.png

Sinon il y a Flameshot qui fonctionne très bien et permet de dessiner, écrire, flouter par dessus les régions capturées lors de la capture.

Rechercher une chaine de caractères

Astuce bien pratique, on recherche des occurrences d'une chaine de caractères dans des fichiers. comment les trouver facilement ?

Simplement avec la commande find couplée avec grep.

Deux façons :

find . | xargs grep 'ma chaine' -sl

Ou :

find ./ -exec grep -Hn "ma chaine" {} \;

Ou encore plus simple à retenir :

grep -Rin "ma chaine"

Pacman

Petite liste des commandes basiques de pacman

Commandes

Synchronisation (équivalent du apt-get update chez Debian) :

pacman -Sy

Mise à jour des paquets :

pacman -Su

Installer un paquet :

pacman -S nom_du_paquet

Recherche un paquet parmis ceux installés :

pacman -Qs nom_du_paquet

Recherche un paquet dans les dépôts :

pacman -Ss nom_du_paquet

Exemple

On va installer pkfile histoire de retrouver l'équivalent de yum whatprovides disponible sous fedora (et autres distribution utilisant YUM) :

pacman -Sy
:: Synchronisation des bases de données de paquets...
 core est à jour
 extra est à jour
 community est à jour
 alarm est à jour
 aur est à jour

pacman -Ss pkgf
extra/pkgfile 14-1
    a pacman .files metadata explorer

pacman -S pkgfile
résolution des dépendances...
recherche des conflits entre paquets...

Paquets (1): pkgfile-14-1

Taille totale de téléchargement : 0,02 MiB
Taille totale installé :           0,12 MiB

:: Procéder à l’installation ? [O/n] O
:: Récupération des paquets...
 pkgfile-14-1-armv6h       21,1 KiB   176K/s 00:00 [######################] 100%
(1/1) vérification des clés dans le trousseau      [######################] 100%
(1/1) vérification de l’intégrité des paquets      [######################] 100%
(1/1) chargement des fichiers des paquets          [######################] 100%
(1/1) analyse des conflits entre fichiers          [######################] 100%
(1/1) vérification de l’espace disque disponible   [######################] 100%
(1/1) installation de pkgfile                      [######################] 100%
==> Run ''pkgfile --update'' to initialize the database
synchronizing filesystem...

Voila, c'est installé. Maintenant il suffit juste de faire ce qu'on nous demande, c'est à dire :

pkgfile --update
:: Updating 5 repos...
  download complete: core                 [   442.8 KiB   452K/s  4 remaining]
  download complete: alarm                [    62.9 KiB  11.5K/s  3 remaining]
  download complete: aur                  [   103.0 KiB  18.4K/s  2 remaining]
  download complete: community            [     6.1 MiB   564K/s  1 remaining]
  download complete: extra                [     6.2 MiB   503K/s  0 remaining]
:: download complete in 12.66s            <    12.9 MiB  1044K/s  5 files    >
:: waiting for 2 processes to finish repacking repos...

Voila.

Maintenant, mettre à jour la base de pkgfile :

pkgfile -u

Et pour connaître le(s) paquet(s) qui fournis(sent) une commande particulière (exemple pour scp) :

pkgfile scp
core/openssh
extra/bash-completion

SSH Agent

Chose bien pratique, c'est de n'avoir qu'une seule fois à déverrouiller une clé lorsqu'on utilise ssh plusieurs fois par jours.

Par défaut, à chaque connexion ssh, il faut entrer le mot de passe associé à la clé. Quand on l'utilise 2 ou 3 fois par jours, ça peut aller. Mais si, comme dans mon cas, vous l'utilisez au minimum 20 fois par jours, ça devient très vite fatiguant.

L'astuce consiste simplement à ajouter la clé dans l'agent qui gère ces clés (ssh-agent ou seahorse-agent) :

ssh-add ~/.ssh/votre_cle_ssh
Enter passphrase for ~/.ssh/votre_cle_ssh:
Identity added: ~/.ssh/votre_cle_ssh (~/.ssh/votre_cle_ssh)

Et voila.

Seulement, cela fonctionnera uniquement pour la session en cours. Redémarrez, et vous devrez refaire cette manipulation.

Utilisant KDE, j'ai décidé d'utiliser le Kwallet qui gère les mots de passe de la session KDE. Il demande un seul mot de passe pour déverrouiller les autres qu'il gère, par exemple des calendriers Google calendar, etc.

Pour ce faire, ce n'est pas du tout compliqué.

Créez un script bash qui s’exécutera au démarrage de la session KDE :

nano ~/.kde/Autostart/ssh-add.sh
Sur certaines distributions, le dossier ~/.kde/ se nomme ~/.kde4/

Saisissez les commandes d'ajout d'identité pour votre clé (ou vos clés si vous en avez plusieurs) :

#!/bin/bash
ssh-add ~/.ssh/votre_cle_ssh_1 </dev/null
ssh-add ~/.ssh/votre_cle_ssh_2 </dev/null

Rendez le script exécutable :

chmod +x ~/.kde/Autostart/ssh-add.sh

Exécutez-le une fois pour la session courante (vous serez invité à saisir le mot de passe associé à votre clé) :

bash ~/.kde/Autostart/ssh-add.sh

Au prochain démarrage de votre session, le mot de passe de Kwallet vous sera demandé, et déverrouillera par la même occasion vos clé ssh.

MdBook

MdBook est un GitBook like écrit en Rust, mais, contrairement à GitBook, il a gardé sa simplicité.

Le principe est simple, vous écrivez vos articles / chapitres en MarkDown, vous organisez votre sommaire, et vous générer le book en format html.

La documentation officielle est faite avec mdbook ainsi que ce « blog » que vous êtes en train de lire.

Installation

J'ai opté pour la méthode la plus simple : récupérer le binaire linux sur leur dépôt git : https://github.com/rust-lang/mdBook/releases et extraire le binaire dans un dossier qui est dans mon PATH.

Et voila, mdbook est installé.

CLI

C'est donc un outil en CLI très simple à utiliser.

Comme tout bon outil CLI, une aide très claire est présente :

mdbook -h
mdbook v0.4.7
Mathieu David <mathieudavid@mathieudavid.org>
Creates a book from markdown files

USAGE:
    mdbook [SUBCOMMAND]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    build    Builds a book from its markdown files
    clean    Deletes a built book
    help     Prints this message or the help of the given subcommand(s)
    init     Creates the boilerplate structure and files for a new book
    serve    Serves a book at http://localhost:3000, and rebuilds it on changes
    test     Tests that a book's Rust code samples compile
    watch    Watches a book's files and rebuilds it on changes

For more information about a specific command, try `mdbook <command> --help`
The source code for mdBook is available at: https://github.com/rust-lang/mdBook

Initialisation

Pour commencer, on execute la commande suivante :

mdbook init

Do you want a .gitignore to be created? (y/n)
y
What title would you like to give the book? 
Example book
2021-04-24 16:41:57 [INFO] (mdbook::book::init): Creating a new book with stub content

All done, no errors...

On se retrouve avec la structure de fichier suivante :

.
├── book
├── book.toml
└── src
    ├── chapter_1.md
    └── SUMMARY.md
Le dossier book/ est un dossier généré. Tout son contenu sera intégralement remplacé lors des constructions du book.

Construction du book

Pour générer le book en html, on éxécute simplement la commande suivante :

mdbook build

2021-04-24 17:07:57 [INFO] (mdbook::book): Book building has started
2021-04-24 17:07:57 [INFO] (mdbook::book): Running the html backend

Le book est généré dans le dossier <projet>/book/ :

./book
├── 404.html
├── ayu-highlight.css
├── book.js
├── chapter_1.html
├── clipboard.min.js
├── css
│   └── [...]
├── elasticlunr.min.js
├── favicon.png
├── favicon.svg
├── FontAwesome
│   └── [...]
├── fonts
│   └── [...]
├── highlight.css
├── highlight.js
├── index.html
├── mark.min.js
├── print.html
├── searcher.js
├── searchindex.js
├── searchindex.json
└── tomorrow-night.css

Il vous suffit d'ouvrir le fichier index.html pour voir le book.

Construction à la volée

Lors de la rédaction de vos articles, vous ne voudrez pas faire manuellement un mdbook build à chaque changement.

Pour ce faire, la commande mdbook watch permet de reconstruire le book à chaque sauvegarde d'article.

Cela permet d'avoir le rendu (moyennant un rafraîchissement manuel) dans son navigateur pendant qu'on rédige un article dans son IDE préféré.

Éléments markdown

Images

Image

    
    {{ image-centered "../img/avatar-new.png"}}
    

Image centrée et réduite

    
    {{ image-centered "../img/avatar-new.png" 25% }}
    

Image déformée

    
    {{ image-centered "../img/avatar-new.png" 200px 50px }}
    

Image réduite

    
    {{ image-centered "../img/avatar-new.png" 25% }}
    

Blocs

Note

{{ block note }}
    une note
{{ endblock }}
une note

Warning

{{ block warning }}
    un warning
{{ endblock }}
un warning

Danger

{{ block danger }}
    un danger
{{ endblock }}
un danger

Success

{{ block success }}
    une note
{{ endblock }}
une note

Citation

Une citation

Une citation

Une citation

Une citation

Code

En ligne

un bout de `code` en ligne

un bout de code en ligne

En bloc

Bash

```bash
echo "Test"
```
echo "Test"

Php

```php
namespace My\Namespace;

use DateTime;

class MyClass
{
    public function myFunction(): void
    {
        // do nothing
    }
}
```
namespace My\Namespace;

use DateTime;

class MyClass
{
    public function myFunction(): void
    {
        // do nothing
    }
}