Aller au contenu
English
English
Culture DevOps medium

Sauvegarder et restaurer Gitea

15 min de lecture

gitea dump rassemble les dépôts Git, la base de données, la configuration et les fichiers LFS dans une seule archive ZIP. L'instance doit être arrêtée pendant le dump, faute de quoi l'archive n'est pas garantie cohérente. La restauration est manuelle mais déterministe.

Le cycle complet de ce guide, sauvegarde puis destruction puis restauration, a été rejoué sur Gitea 1.27.3 avec SQLite3 comme base de données. Les étapes sont identiques pour PostgreSQL ou MySQL, sauf pour la restauration de la base.

  • Identifier ce que l'archive gitea dump embarque, et ce qu'elle laisse dehors
  • Produire une sauvegarde cohérente, instance arrêtée
  • Automatiser le cycle par une tâche cron qui tourne sous le bon compte
  • Restaurer dans l'ordre qu'impose app.ini, configuration avant dépôts
  • Prouver la restauration autrement qu'en regardant la taille de l'archive

Une archive gitea dump est autoportante : elle réunit tout ce qu'il faut pour reconstruire l'instance, et il n'y a rien d'autre à sauvegarder à côté. Le tableau ci-dessous sert à vérifier qu'une archive est complète avant de la juger bonne.

ContenuChemin sourceRôle
Dépôts GitREPOSITORY ROOTTous les dépôts bare
Base de donnéesdata/gitea.dbDonnées applicatives (users, issues, PRs...)
Fichiers LFSdata/lfs/Objets Git LFS
Configurationapp.iniParamètres de l'instance
Paquetsdata/packages/Registre de paquets
Donnéesdata/Autres données (avatars, attachements...)

La ligne app.ini compte autant que les autres. Ce fichier porte SECRET_KEY, la clé qui chiffre les données sensibles en base, dont les secrets à deux facteurs : perdue, elles sont indéchiffrables. Il porte aussi trois jetons d'authentification, INTERNAL_TOKEN pour la communication interne, JWT_SECRET pour OAuth2 et LFS_JWT_SECRET pour LFS. Une archive Gitea est donc elle-même un secret, à stocker et à transférer comme tel.

Les archives se déposent dans un répertoire qui appartient à git, le compte sous lequel tourne le dump. Le mode 750 le ferme aux autres utilisateurs de la machine, ce qui découle directement du point précédent : une archive qui contient les secrets de l'instance n'a rien à faire dans un répertoire lisible par tous.

Fenêtre de terminal
# Créer le répertoire dédié aux archives
sudo mkdir -p /var/lib/gitea/backups
sudo chown git:git /var/lib/gitea/backups
sudo chmod 750 /var/lib/gitea/backups

L'instance doit être arrêtée pendant le dump. La documentation Gitea est explicite sur ce point : « To ensure the consistency of the Gitea instance, it must be shutdown during backup ». La raison est une course entre la base de données et les dépôts. Pendant une migration ou un simple push, une transaction s'écrit en base pendant que le dépôt Git est copié : l'archive contient alors une base qui affirme l'existence d'un dépôt incomplet. Le dump se termine sans erreur, et l'incohérence ne se découvre qu'à la restauration.

Fenêtre de terminal
# 1. Arrêter l'instance : sans cela, la cohérence n'est pas garantie
sudo systemctl stop gitea.service
# 2. Dump, en tant qu'utilisateur git
sudo -u git GITEA_WORK_DIR=/var/lib/gitea \
gitea dump \
--config /etc/gitea/app.ini \
--work-path /var/lib/gitea \
--file /var/lib/gitea/backups/gitea-$(date +%Y%m%d-%H%M%S).zip \
--type zip
# 3. Redémarrer, puis confirmer que le service répond
sudo systemctl start gitea.service
sudo systemctl is-active gitea.service

La fenêtre d'indisponibilité dure le temps du dump, quelques secondes sur une petite forge, plusieurs minutes dès que les dépôts pèsent. Si cette interruption n'est pas acceptable, la parade n'est pas de dumper à chaud : elle consiste à prendre un instantané cohérent au niveau du stockage (LVM, ZFS, volume cloud) et de la base de données, puis à dumper depuis cet instantané.

La commande affiche la progression et se termine par :

Finish dumping in file /var/lib/gitea/backups/gitea-20260328-194600.zip

L'archive réunit la configuration, les données, les dépôts et un dump SQL de la base. Ce dernier, gitea-db.sql, est présent même avec SQLite, où le fichier .db figure déjà sous data/ : une restauration peut donc repartir de l'un ou de l'autre, ce qui compte quand la base est corrompue plutôt que perdue.

Vérifiez le contenu de l'archive :

Fenêtre de terminal
sudo -u git unzip -l /var/lib/gitea/backups/gitea-20260328-194600.zip | head -20

Une commande unique en crontab ne convient pas ici, puisque la sauvegarde suppose d'arrêter puis de redémarrer le service. Un gitea dump lancé seul dans cron automatiserait la procédure incohérente décrite plus haut, et un échec au milieu laisserait la forge arrêtée jusqu'au lendemain. On passe donc par un script, dont le trap garantit le redémarrage même en cas d'erreur.

/usr/local/bin/gitea-backup.sh
#!/usr/bin/env bash
set -euo pipefail
DEST=/var/lib/gitea/backups
ARCHIVE="$DEST/gitea-$(date +%Y%m%d-%H%M%S).zip"
# Quoi qu'il arrive ensuite, la forge redémarre : une sauvegarde ratée ne
# doit jamais se solder par une indisponibilité prolongée.
trap 'systemctl start gitea.service' EXIT
systemctl stop gitea.service
sudo -u git GITEA_WORK_DIR=/var/lib/gitea \
gitea dump --config /etc/gitea/app.ini \
--work-path /var/lib/gitea --file "$ARCHIVE" --type zip
# Relire ce qu'on vient d'écrire : une archive illisible n'est pas une
# sauvegarde, et `unzip -t` le dit tout de suite.
unzip -t "$ARCHIVE" > /dev/null
find "$DEST" -name 'gitea-*.zip' -mtime +30 -delete

Ce script tourne en root, et non sous le compte git : il arrête et redémarre le service, ce qu'un utilisateur non privilégié ne peut pas faire. Une tâche posée dans la crontab de git sortirait dès la première ligne, sur un refus de systemd. Le répertoire /etc/cron.d/ règle le problème, puisque son format porte l'utilisateur d'exécution en sixième colonne :

Fenêtre de terminal
sudo chmod 750 /usr/local/bin/gitea-backup.sh
/etc/cron.d/gitea-backup
# Sauvegarde Gitea quotidienne à 2h00, exécutée en root
0 2 * * * root /usr/local/bin/gitea-backup.sh >> /var/log/gitea-backup.log 2>&1

Le lendemain, c'est le journal qui dit si la tâche a tourné, et l'archive qui dit si elle a réussi. Contrôlez les deux, car une tâche qui ne se déclenche pas ne laisse aucune trace ailleurs :

Fenêtre de terminal
sudo tail -n 20 /var/log/gitea-backup.log
ls -lh /var/lib/gitea/backups/

La restauration reconstruit l'instance à partir de l'archive, et c'est le seul moyen de prouver que la sauvegarde vaut quelque chose : un dump qui se termine sans erreur ne dit rien de sa restaurabilité. Elle suppose que Gitea est déjà installé (binaire + service systemd), mais non configuré (pas d'app.ini).

  1. Arrêter le service

    Fenêtre de terminal
    sudo systemctl stop gitea.service
  2. Vider les répertoires de données existants

    Fenêtre de terminal
    sudo rm -rf /var/lib/gitea/data
    sudo rm -rf /etc/gitea/app.ini

    Les dépôts partent avec data/, puisque Gitea les y range par défaut. Si votre app.ini déclare un ROOT hors de data/, supprimez aussi ce répertoire : l'étape 6 explique comment le lire.

    Ces répertoires sont recréés aux étapes 5 et 6, et c'est cette recréation qu'on saute le plus volontiers. Sans elle, les copies échouent sur No such file or directory, la restauration ne dépose rien, et le service redémarre quand même : vous croyez avoir restauré une forge, vous avez redémarré une forge vide.

  3. Extraire l'archive

    Fenêtre de terminal
    # Créer un répertoire temporaire pour l'extraction
    sudo -u git mkdir -p /tmp/gitea-restore
    sudo -u git unzip /var/lib/gitea/backups/gitea-20260328-194600.zip \
    -d /tmp/gitea-restore
  4. Restaurer la configuration, avant tout le reste

    L'ordre compte : app.ini porte le chemin des dépôts, dont l'étape 6 a besoin.

    Fenêtre de terminal
    sudo cp /tmp/gitea-restore/app.ini /etc/gitea/app.ini
    sudo chown root:git /etc/gitea/app.ini
    sudo chmod 660 /etc/gitea/app.ini # 660 pour le 1er démarrage post-restauration
  5. Recréer le répertoire de données, puis le remplir

    install -d pose le répertoire avec son propriétaire et ses droits en une commande, là où un mkdir laisserait un répertoire appartenant à root que Gitea ne saurait pas écrire. Le point final de data/. copie le contenu du répertoire, fichiers cachés compris, et non le répertoire lui-même.

    Fenêtre de terminal
    sudo install -d -o git -g git -m 750 /var/lib/gitea/data
    sudo cp -a /tmp/gitea-restore/data/. /var/lib/gitea/data/

    Avec SQLite, cette seule commande restaure aussi la base, puisque gitea.db vit sous data/. Avec PostgreSQL ou MySQL, il faut en plus rejouer gitea-db.sql dans une base vide, avec psql ou mysql.

  6. Restaurer les dépôts à l'emplacement que déclare app.ini

    Par défaut, Gitea range les dépôts sous data/, dans data/gitea-repositories : la clé ROOT de la section [repository] vaut {APP_DATA_PATH}/gitea-repositories, et APP_DATA_PATH vaut lui-même data/. Beaucoup d'installations déclarent pourtant un autre chemin. Plutôt que d'en recopier un, lisez celui que votre configuration déclare :

    Fenêtre de terminal
    ROOT=$(sudo grep -A20 '^\[repository\]' /etc/gitea/app.ini \
    | sed -n 's/^ROOT *= *//p' | head -1)
    ROOT=${ROOT:-/var/lib/gitea/data/gitea-repositories}
    echo "$ROOT"
    sudo install -d -o git -g git -m 750 "$ROOT"
    sudo cp -a /tmp/gitea-restore/repos/. "$ROOT"/
    sudo chown -R git:git /var/lib/gitea/data "$ROOT"

    Le repli couvre le cas où la clé ROOT n'est pas écrite dans app.ini : Gitea applique alors son défaut, data/gitea-repositories. Se tromper de chemin ici ne produit aucune erreur : les dépôts atterrissent là où Gitea ne les lit jamais, le service redémarre, les comptes répondent, et tous les dépôts manquent.

  7. Redémarrer et vérifier

    Fenêtre de terminal
    sudo systemctl start gitea.service
    sleep 5
    sudo systemctl is-active gitea.service
    # active
    curl -o /dev/null -s -w "%{http_code}\n" http://localhost:3000/
    # 200

    Un service actif ne prouve pas une restauration réussie. Comparez le contenu à ce que vous aviez avant, sans quoi vous validez une forge vide qui démarre très bien :

    Fenêtre de terminal
    # Les comptes sont-ils revenus ?
    sudo -u git gitea admin user list --config /etc/gitea/app.ini | tail -n +2 | wc -l
    # Et les dépôts ? (jeton d'un compte administrateur)
    curl -s -H "Authorization: token $TOKEN" \
    http://localhost:3000/api/v1/user/repos | grep -c '"name"'

    Poussez la vérification jusqu'à un clone réel d'un dépôt restauré : c'est la seule façon de savoir que les objets Git sont intacts, et pas seulement référencés par la base.

  8. Durcir les permissions

    Fenêtre de terminal
    sudo chmod 640 /etc/gitea/app.ini
    sudo chmod 750 /etc/gitea
    sudo systemctl restart gitea.service
  9. Nettoyer les fichiers temporaires

    Fenêtre de terminal
    rm -rf /tmp/gitea-restore

Pour une stratégie robuste, transférez les archives vers un stockage distant après chaque backup.

Fenêtre de terminal
# Exemple avec rsync vers un serveur distant
rsync -az --progress \
/var/lib/gitea/backups/ \
backup-user@backup.example.com:/backups/gitea/
# Exemple avec rclone vers S3 / Backblaze B2 / etc.
rclone copy /var/lib/gitea/backups/ remote:bucket/gitea-backups/
  • gitea dump sauvegarde tout, dépôts, base, configuration et LFS, en une seule commande
  • L'instance doit être arrêtée pendant le dump : à chaud, la cohérence de l'archive n'est pas garantie
  • La restauration suppose Gitea installé mais non configuré, sans app.ini
  • Restaurez app.ini en premier : c'est lui qui déclare où vont les dépôts, data/gitea-repositories par défaut
  • L'archive porte SECRET_KEY, sans laquelle les données chiffrées sont perdues, et les jetons INTERNAL_TOKEN, JWT_SECRET et LFS_JWT_SECRET : protégez-la comme un secret
  • La tâche planifiée tourne en root, pas sous git, puisqu'elle arrête le service
  • Le script fourni conserve 30 jours d'archives ; l'historique long vit sur le stockage distant

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens ce site gratuitement, sans publicité, sans profilage et sans compte à créer. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn