Skip to main content

3_Sync_git_to_bookstack

Contexte technique du projet

Vous disposez d’un serveur GitLab servant de dépôt pour vos fichiers de documentation (Markdown ou HTML), ainsi que d’un serveur BookStack utilisé comme base documentaire interne.
L’objectif est de mettre en place un mécanisme d’intégration continue permettant qu’à chaque commit poussé sur GitLab, le contenu soit automatiquement synchronisé dans BookStack.

L’architecture repose sur trois éléments :

GitLab comme source de vérité

  • Le dépôt GitLab contient les fichiers de documentation.
  • Chaque push sur la branche principale déclenche un pipeline CI/CD.

Script d’intégration

  • Script Bash exécuté par GitLab Runner.
  • Il appelle l’API REST de BookStack pour créer ou mettre à jour les pages.
  • Il gère également les images, les livres et les pages.

API REST de BookStack

  • Utilisée pour synchroniser automatiquement les pages.
  • Le script envoie les requêtes nécessaires : création, mise à jour, gestion des livres et chapitres.

Création d’un compte dédié dans BookStack

Connectez-vous à BookStack avec un compte administrateur.
Rendez-vous dans Préférences → Utilisateurs → Ajouter un nouvel utilisateur.

compte-1
compte-2
En bas de la page, vous trouverez la section Jeton API.
Générez un jeton et conservez soigneusement :

  • l’ID du token
  • le secret du token

Ces deux valeurs seront utilisées par le script. compte-3

Création d’un rôle dédié dans BookStack

Dans Rôles → Créer un nouveau rôle, créez un rôle spécifique pour la synchronisation.

role_-1

Droits recommandés :

  • Gérer les permissions sur les livres, chapitres et pages
  • Gérer les permissions de ses propres livres, chapitres et pages
  • Gérer les modèles de page
  • Accès à l’API du système
  • Importer le contenu
  • Exporter le contenu (optionnel)
  • Changer l’éditeur de page

Assurez-vous également de configurer les permissions des ressources selon vos besoins.

N’oubliez pas d’assigner ce rôle à l’utilisateur dédié.

role_-2
role_-3

Structure du dossier BookStack côté GitLab

Exemple d’arborescence :

Bookstack
├── .gitlab-ci.yml
├── scripts/
│   ├── sync_bookstack.sh
│
└── docs/
    ├── Proxmox/
    │   ├── _book.json
    │   ├── Install_ISO.md
    │   ├── images/
    │   │   ├── proxmox_install.png
    │   │   └── autre_image.jpg
    │   └── autres_pages.md
    │
    ├── Shell/
    │   ├── _book.json
    │   ├── Introduction.md
    │   ├── images/
    │   │   └── shell_example.png
    │   └── commandes.md
    │
    ├── Firewall/
    │   ├── _book.json
    │   └── Configuration.md
    │
    ├── Switches/
    │   ├── _book.json
    │   └── VLAN.md
    │
    └── Divers/
        ├── _book.json
        └── Notes.md

Fichier .gitlab-ci.yml

stages:
  - sync

sync_bookstack:
  stage: sync
  script:
    - bash scripts/sync_bookstack.sh

Ce fichier doit être placé à la racine du dépôt GitLab.
À chaque push, GitLab déclenche automatiquement le pipeline.

Installation de GitLab Runner

Un runner est la machine qui exécute réellement les jobs définis dans .gitlab-ci.yml.

Pour l’installer :

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" -o script.deb.sh
sudo bash script.deb.sh
sudo apt update
sudo apt install gitlab-runner

Installation de jq (nécessaire au script) :

sudo apt install jq

Création du runner dans GitLab

Dans votre projet :

Paramètres → CI/CD → Runners → Créer un runner de projet

runner-1
runner-2

GitLab vous fournira la commande suivante :

sudo gitlab-runner register \
  --url http://<IP> \
  --registration-token TON_TOKEN

runner-3
Une fois enregistré, vous pouvez suivre l’activité du runner dans :

  • Analyse → Données d’analyse CI/CD
    runner-4
  • Compilation → Jobs
    runner-5

Initialisation du dépôt Git

Placez-vous à la racine du dossier :

Bookstack/

Initialisez Git :

git init
git config --global user.name "Rohba"
git remote add origin http://<IP>/<User>/<Projet>.git
git pull origin master --allow-unrelated-histories

Ajoutez vos fichiers :

git add .
git commit -m "Initialisation du projet"
git push

Pour rendre un script exécutable :

git update-index --chmod=+x scripts/sync_bookstack.sh
git add scripts/sync_bookstack.sh
git commit -m "Script exécutable"
git push

Fichier _book.json

Version minimale :

{
  "name": "Nom du livre",
  "description": "Description du contenu.",
  "tags": [],
  "order": null,
  "chapters": null,
  "options": {
    "create_missing_pages": true,
    "delete_removed_pages": false
  }
}

Version avec chapitres :

{
  "name": "Infrastructure",
  "description": "Documentation technique.",
  "tags": ["infra"],
  "order": [
    "introduction.md",
    "vm.md",
    "backup.md"
  ],
  "chapters": {
    "Serveurs": [
      "vm.md",
      "hyperviseur.md"
    ],
    "Sauvegardes": [
      "backup.md",
      "restore.md"
    ]
  },
  "options": {
    "create_missing_pages": true,
    "delete_removed_pages": false
  }
}

Script Bash de synchronisation

Voici en parti le script bash :

#!/usr/bin/env bash

################################################################################################

# Nom du script        : sync_bookstack.sh
# Description          : Sync mon projet gitlab sur mon bookstack pour ma documentation
#
# Auteur               : Thomas MARLIER (alias Rohba)
# Date de création     : 11/04/2026
# Dernière modification: 11/04/2026
#
# Version              : 1.5
#
# Notes :
#   - Ce script doit être exécuté en tant que root.
#
# Licence              : Interne
################################################################################################

set -euo pipefail

  

echo "=== DEBUG ==="
echo "BOOKSTACK_URL=${BOOKSTACK_URL:-<vide>}"
echo "BOOKSTACK_TOKEN_ID=${BOOKSTACK_TOKEN_ID:-<vide>}"
echo "BOOKSTACK_TOKEN_SECRET=${BOOKSTACK_TOKEN_SECRET:-<vide>}"
echo "DOCS_DIR=${DOCS_DIR:-docs}"
echo "=============="
 

BOOKSTACK_URL="${BOOKSTACK_URL:?BOOKSTACK_URL manquant}"
BOOKSTACK_TOKEN_ID="${BOOKSTACK_TOKEN_ID:?BOOKSTACK_TOKEN_ID manquant}"
BOOKSTACK_TOKEN_SECRET="${BOOKSTACK_TOKEN_SECRET:?BOOKSTACK_TOKEN_SECRET manquant}"
DOCS_DIR="${DOCS_DIR:-docs}"


api() {

    local method="$1"
    local endpoint="$2"
    local data="${3:-}"

    curl -s -X "$method" "$BOOKSTACK_URL/api/$endpoint" \
        -H "Authorization: Token $BOOKSTACK_TOKEN_ID:$BOOKSTACK_TOKEN_SECRET" \
        -H "Content-Type: application/json" \
        -d "$data"
}

  

###############################################
# SUPPRIMER TOUS LES LIVRES AVANT SYNC
###############################################
echo "🗑 Suppression de tous les livres existants..."

books=$(api GET "books?count=500" | jq -r '.data[].id')

for id in $books; do
    echo "  - Suppression du livre ID $id"
    api DELETE "books/$id"
done

echo "✔ Tous les livres ont été supprimés."
echo ""

###############################################
# UPLOAD IMAGE TO BOOKSTACK
###############################################
upload_image() {
    local image_path="$1"
    local filename
    filename=$(basename "$image_path")
    echo "    📤 Upload image : $filename"
    response=$(curl -s -X POST "$BOOKSTACK_URL/api/images" \
        -H "Authorization: Token $BOOKSTACK_TOKEN_ID:$BOOKSTACK_TOKEN_SECRET" \
        -F "image=@$image_path" \
        -F "type=gallery")
    echo "$response" | jq -r '.url'
}

###############################################
# REPLACE LOCAL IMAGE PATHS IN MARKDOWN
###############################################
replace_local_images() {
    local markdown="$1"
    local base_dir="$2"

    local images
    images=$(echo "$markdown" | grep -oP '!
\[.*?\]
\(\Kimages/[^)]+' || true)
    for img in $images; do
        local full_path="$base_dir/$img"
        if [[ -f "$full_path" ]]; then
            url=$(upload_image "$full_path")
            markdown=$(echo "$markdown" | sed "s|$img|$url|g")
        else
            echo "    ⚠️ Image introuvable : $full_path"
        fi
    done
    echo "$markdown"
}

###############################################
# MAIN SYNC LOGIC
###############################################
if [[ ! -d "$DOCS_DIR" ]]; then
    echo "❌ Le dossier $DOCS_DIR n'existe pas."
    exit 1
fi

for dir in "$DOCS_DIR"/*; do
    [[ -d "$dir" ]] || continue
    echo "📘 Traitement du dossier : $dir"
    if [[ ! -f "$dir/_book.json" ]]; then
        echo "⚠️ Aucun fichier _book.json dans $dir"
        continue
    fi

    book_name=$(jq -r '.name' "$dir/_book.json")
    book_desc=$(jq -r '.description' "$dir/_book.json")

    echo "➡ Livre : $book_name"
    echo "➕ Création du livre : $book_name"

    book_id=$(api POST "books" "$(jq -n --arg name "$book_name" --arg desc "$book_desc" '{name:$name,description:$desc}')" | jq -r '.id')

  

    for file in "$dir"/*.md; do
        [[ -f "$file" ]] || continue
        page_name=$(basename "$file" .md)
        echo "  📄 Page : $page_name"
        raw_content=$(cat "$file")
        content=$(replace_local_images "$raw_content" "$dir")
        echo "    ➕ Création"
        api POST "pages" "$(jq -n \
            --arg name "$page_name" \
            --arg content "$content" \
            --argjson book_id "$book_id" \
            '{name:$name,book_id:$book_id,markdown:$content}')"
    done
    echo "✔ Livre synchronisé : $book_name"
done

echo "🎉 Synchronisation terminée."

Point important :
Ton script doit être enregistré en LF et non en CRLF.

Différence entre LF et CRLF

LF (Line Feed)

  • Caractère : \n
  • Utilisé par Linux / macOS
  • Représente un saut de ligne

CRLF (Carriage Return + Line Feed)

  • Caractères : \r\n
  • Utilisé par Windows
  • Retour au début de la ligne + saut de ligne

Pour convertir un fichier en LF sous VS Code :

En bas à droite → CRLF → LF

Ou en CLI :

dos2unix scripts/sync_bookstack.sh