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.


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.

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.

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é.


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


GitLab vous fournira la commande suivante :
sudo gitlab-runner register \
--url http://<IP> \
--registration-token TON_TOKEN

Une fois enregistré, vous pouvez suivre l’activité du runner dans :
- Analyse → Données d’analyse CI/CD

- Compilation → Jobs

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