Sauvegarde OpenProject
Script : /usr/local/bin/backup_openproject.sh — tous les jours à 03:30, rétention 14 jours.
Ce qui est sauvegardé
Chaque nuit, un dossier /srv/backups/openproject/AAAA-MM-JJ_HHMM/ contenant :
| Fichier | Contenu |
|---|---|
openproject.dump | Base PostgreSQL (pg_dump -Fc), vérifiée avec pg_restore --list |
opdata.tar.gz | Pièces jointes (/var/openproject/assets) |
docker-compose.yml, env, proxy.tar.gz | Configuration — env contient SECRET_KEY_BASE, indispensable |
versions.txt | Versions des images au moment de la sauvegarde |
SHA256SUMS | Sommes de contrôle |
Installation
-
Renseigner
OP_DIR(chemin absolu du dossier compose, donné pardocker compose ls) en tête de script. -
Installer et tester :
sudo install -m 750 backup_openproject.sh /usr/local/bin/backup_openproject.shsudo /usr/local/bin/backup_openproject.sh -
Planifier (
sudo crontab -e) :30 3 * * * /usr/local/bin/backup_openproject.sh >> /var/log/backup_openproject.log 2>&1
Pas de ~ dans OP_DIR : le script tourne en root et ~ n'est pas développé entre guillemets.
Restauration
sudo ./restore_openproject.sh /srv/backups/openproject/AAAA-MM-JJ_HHMM
Le script vérifie les sommes de contrôle, demande de taper OUI, arrête OpenProject (sauf la base), restaure la base et les pièces jointes puis redémarre.
Restauration sur un nouveau serveur : réinstaller Docker, recopier docker-compose.yml, env (→ .env) et proxy/, faire docker compose up -d db, puis lancer le script de restauration.
Depuis Google Drive : voir Copie hors site.
Scripts
backup_openproject.sh
#!/usr/bin/env bash
# Sauvegarde OpenProject (docker compose) : base PostgreSQL + pièces jointes + configuration.
#
# Installation sur le serveur OpenProject :
# sudo install -m 750 backup_openproject.sh /usr/local/bin/backup_openproject.sh
# sudo crontab -e puis ajouter :
# 30 3 * * * /usr/local/bin/backup_openproject.sh >> /var/log/backup_openproject.log 2>&1
#
# Test manuel : sudo /usr/local/bin/backup_openproject.sh
set -euo pipefail
# ---------- À adapter ----------
OP_DIR="/chemin/vers/openproject" # dossier qui contient docker-compose.yml et .env
BACKUP_DIR="/srv/backups/openproject" # où stocker les sauvegardes locales
KEEP_DAYS=14 # nombre de jours de sauvegardes gardées en local
DB_USER="postgres"
DB_NAME="openproject"
RCLONE_REMOTE="" # ex. "gdrive-crypt:backups/openproject" (vide = pas de copie hors site)
# --------------------------------
STAMP="$(date +%F_%H%M)"
DEST="$BACKUP_DIR/$STAMP"
log() { echo "[$(date '+%F %T')] $*"; }
fail() { log "ERREUR : $*"; exit 1; }
# Un seul backup à la fois
exec 9>/var/lock/backup_openproject.lock
flock -n 9 || fail "une sauvegarde est déjà en cours"
cd "$OP_DIR" || fail "dossier $OP_DIR introuvable"
mkdir -p "$DEST"
chmod 700 "$BACKUP_DIR" "$DEST"
log "Début de la sauvegarde -> $DEST"
# 1. Base de données (format custom : compressé, restaurable avec pg_restore)
log "Dump PostgreSQL..."
docker compose exec -T db pg_dump -U "$DB_USER" -Fc "$DB_NAME" > "$DEST/openproject.dump" \
|| fail "pg_dump a échoué"
# Vérifie que le dump est lisible
docker compose exec -T db pg_restore --list < "$DEST/openproject.dump" > /dev/null \
|| fail "le dump est illisible"
# 2. Pièces jointes (volume ou dossier monté sur /var/openproject/assets dans le conteneur web)
log "Archive des pièces jointes..."
WEB_ID="$(docker compose ps -q web)"
[ -n "$WEB_ID" ] || fail "conteneur web introuvable (OpenProject est-il démarré ?)"
ASSETS_SRC="$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/var/openproject/assets"}}{{if .Name}}{{.Name}}{{else}}{{.Source}}{{end}}{{end}}{{end}}' "$WEB_ID")"
[ -n "$ASSETS_SRC" ] || fail "montage /var/openproject/assets introuvable"
docker run --rm -v "$ASSETS_SRC:/data:ro" -v "$DEST:/backup" alpine \
tar czf /backup/opdata.tar.gz -C /data . \
|| fail "archive des pièces jointes échouée"
# 3. Configuration (.env contient SECRET_KEY_BASE : indispensable pour restaurer)
log "Copie de la configuration..."
cp docker-compose.yml "$DEST/"
[ -f .env ] && cp .env "$DEST/env"
[ -d proxy ] && tar czf "$DEST/proxy.tar.gz" proxy
docker compose images > "$DEST/versions.txt" 2>/dev/null || true
# 4. Sommes de contrôle
( cd "$DEST" && sha256sum ./* > SHA256SUMS )
log "Taille : $(du -sh "$DEST" | cut -f1)"
# 5. Copie hors site (optionnelle)
if [ -n "$RCLONE_REMOTE" ]; then
log "Copie vers $RCLONE_REMOTE/$STAMP ..."
rclone copy "$DEST" "$RCLONE_REMOTE/$STAMP" || fail "copie rclone échouée"
rclone delete "$RCLONE_REMOTE" --min-age "${KEEP_DAYS}d" --rmdirs || log "ATTENTION : nettoyage distant échoué"
fi
# 6. Rotation locale
find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +"$KEEP_DAYS" -exec rm -rf {} +
log "Sauvegarde terminée."
restore_openproject.sh
#!/usr/bin/env bash
# Restaure une sauvegarde faite par backup_openproject.sh.
# ATTENTION : remplace la base et les pièces jointes actuelles.
#
# Usage : sudo ./restore_openproject.sh /srv/backups/openproject/2026-11-03_0330
set -euo pipefail
OP_DIR="/chemin/vers/openproject"
DB_USER="postgres"
DB_NAME="openproject"
SRC="${1:?Usage : $0 /chemin/vers/le/dossier/de/sauvegarde}"
[ -f "$SRC/openproject.dump" ] || { echo "openproject.dump introuvable dans $SRC"; exit 1; }
( cd "$SRC" && sha256sum -c --quiet SHA256SUMS ) || { echo "Sommes de contrôle invalides"; exit 1; }
read -r -p "Ceci ÉCRASE la base et les pièces jointes actuelles. Taper OUI pour continuer : " ok
[ "$ok" = "OUI" ] || { echo "Annulé."; exit 1; }
cd "$OP_DIR"
echo "Arrêt d'OpenProject (sauf la base)..."
docker compose stop web worker cron seeder proxy hocuspocus
echo "Restauration de la base..."
docker compose exec -T db dropdb -U "$DB_USER" --if-exists "$DB_NAME"
docker compose exec -T db createdb -U "$DB_USER" "$DB_NAME"
docker compose exec -T db pg_restore -U "$DB_USER" -d "$DB_NAME" --no-owner < "$SRC/openproject.dump"
echo "Restauration des pièces jointes..."
WEB_ID="$(docker compose ps -aq web)"
ASSETS_SRC="$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/var/openproject/assets"}}{{if .Name}}{{.Name}}{{else}}{{.Source}}{{end}}{{end}}{{end}}' "$WEB_ID")"
docker run --rm -v "$ASSETS_SRC:/data" -v "$SRC:/backup:ro" alpine \
sh -c 'rm -rf /data/* /data/..?* /data/.[!.]* 2>/dev/null; tar xzf /backup/opdata.tar.gz -C /data'
echo "Redémarrage..."
docker compose up -d
echo "Terminé. Vérifie la connexion et une pièce jointe."
echo "Si le .env a été perdu, recopie $SRC/env vers $OP_DIR/.env AVANT de redémarrer."