docs: backup restic remis en place, cron Nextcloud réparé, plan de migration
- Backup : l'ancien Duplicity (2020) ne tournait plus ; remplacé par restic vers OVH Swift (script versionné dans backup/restic-backup.sh, cron 03:30), premier snapshot complet et restauration testés le 2026-10-03 - Nextcloud : cron en panne depuis le 2026-08-09 (DNS overlay perdu) corrigé ; rétention corbeille/versions à "auto, 30" - Incident Gitea : vérification de non-réinfection, mouvement latéral et intégrité de l'hôte (rien trouvé) - plan-migration.md : état des lieux, décisions à prendre, phases Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7e5f15577a
commit
f974203973
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# Backup quotidien sans.pub : dumps des bases + restic vers OVH Swift (conteneur sans-pub-restic)
|
||||
# Installé dans /root/restic/backup.sh, lancé par /etc/cron.d/restic-backup
|
||||
set -uo pipefail
|
||||
|
||||
LOG=/var/log/restic-backup.log
|
||||
DUMP_DIR=/var/backups/sans-pub-db
|
||||
VOLUMES=/var/lib/docker/volumes
|
||||
MAIL_TO=pierre@sans.pub
|
||||
ERRORS=()
|
||||
|
||||
exec 9>/run/restic-backup.lock
|
||||
flock -n 9 || { echo "$(date -Is) déjà en cours, abandon" >> "$LOG"; exit 1; }
|
||||
exec >> "$LOG" 2>&1
|
||||
echo "===== $(date -Is) début"
|
||||
|
||||
. /root/restic/env
|
||||
|
||||
notify_failure() {
|
||||
local pass
|
||||
pass=$(sed -n 's/^ *SMTP_PASS: *//p' /root/backup/docker-compose.yaml)
|
||||
printf 'From: system@sans.pub\r\nTo: %s\r\nSubject: [sans.pub] ECHEC backup restic\r\n\r\n%s\r\n\r\nVoir %s sur le serveur.\r\n' \
|
||||
"$MAIL_TO" "$(printf '%s\n' "${ERRORS[@]}")" "$LOG" |
|
||||
# -k : le certificat de poste.io a expiré (2021-02-06) ; connexion vers ce même serveur
|
||||
curl -sS -k --url smtps://smtp.sans.pub:465 --mail-from system@sans.pub --mail-rcpt "$MAIL_TO" \
|
||||
--user "system@sans.pub:$pass" --upload-file - || echo "envoi du mail d'alerte impossible"
|
||||
}
|
||||
|
||||
# 1. Dumps logiques des bases (cohérents, contrairement aux fichiers bruts)
|
||||
# Les fichiers bruts d'une base ne sont exclus du backup que si son dump a réussi.
|
||||
umask 077
|
||||
DB_VOLUMES_DUMPED=()
|
||||
rm -rf "$DUMP_DIR.new" && mkdir -p "$DUMP_DIR.new"
|
||||
for c in $(docker ps --format '{{.Names}}'); do
|
||||
name=${c#srv-captain--}; name=${name%%.*}
|
||||
envs=$(docker inspect "$c" --format '{{range .Config.Env}}{{println .}}{{end}}' | cut -d= -f1)
|
||||
attempts=()
|
||||
if grep -qx MYSQL_ROOT_PASSWORD <<<"$envs"; then
|
||||
# Variantes essayées dans l'ordre : socket puis TCP (certaines bases n'acceptent root@% qu'en TCP,
|
||||
# ex. limesurvey), puis sans --routines (mysql.proc incompatible, ex. writefreely-db)
|
||||
base='export MYSQL_PWD="$MYSQL_ROOT_PASSWORD"; exec mysqldump -uroot --all-databases --single-transaction --triggers'
|
||||
attempts=("$base --routines" "$base --routines -h127.0.0.1" "$base" "$base -h127.0.0.1")
|
||||
elif grep -qx POSTGRES_USER <<<"$envs"; then
|
||||
attempts=('exec pg_dumpall -U "$POSTGRES_USER"')
|
||||
else
|
||||
continue
|
||||
fi
|
||||
out="$DUMP_DIR.new/$name.sql.gz"
|
||||
ok=
|
||||
for cmd in "${attempts[@]}"; do
|
||||
if docker exec "$c" sh -c "$cmd" 2>"$DUMP_DIR.new/$name.err" | gzip -6 > "$out" \
|
||||
&& [ "$(zcat "$out" 2>/dev/null | head -c 1 | wc -c)" -gt 0 ]; then
|
||||
ok=1; break
|
||||
fi
|
||||
done
|
||||
if [ -n "$ok" ]; then
|
||||
rm -f "$DUMP_DIR.new/$name.err"
|
||||
echo "dump OK $name ($(du -h "$out" | cut -f1))"
|
||||
for v in $(docker inspect "$c" --format '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}} {{end}}{{end}}'); do
|
||||
DB_VOLUMES_DUMPED+=(--exclude "$VOLUMES/$v")
|
||||
done
|
||||
else
|
||||
ERRORS+=("dump ÉCHEC $name (fichiers bruts sauvegardés à la place) : $(tail -c 300 "$DUMP_DIR.new/$name.err")")
|
||||
fi
|
||||
done
|
||||
rm -rf "$DUMP_DIR" && mv "$DUMP_DIR.new" "$DUMP_DIR"
|
||||
|
||||
# 2. Sauvegarde restic
|
||||
# Exclus : fichiers bruts des bases dumpées avec succès, registres Docker (reconstructibles), caches, logs
|
||||
EXCLUDES=(
|
||||
"${DB_VOLUMES_DUMPED[@]}"
|
||||
--exclude "$VOLUMES/ffd465a8ae19be68cdfcd27ee8d5acfddf8d9269ad8e95dbbaf4562dcaacbeef"
|
||||
--exclude "$VOLUMES/edc80279a0f6294d1fb8efb4955b2a23bd861f394a5532120c29d6149f36490e"
|
||||
--exclude "/captain/data/registry"
|
||||
--exclude "$VOLUMES/*/_data/data/appdata_*/preview"
|
||||
--exclude "$VOLUMES/captain--bitwarden-data/_data/icon_cache"
|
||||
--exclude "$VOLUMES/*/_data/data/nextcloud.log*"
|
||||
--exclude "$VOLUMES/*/_data/data/audit.log*"
|
||||
)
|
||||
nice -n 10 ionice -c2 -n7 restic backup --tag quotidien --exclude-caches "${EXCLUDES[@]}" \
|
||||
"$DUMP_DIR" "$VOLUMES" /captain/data /home/cloud/apps_data /root/backup /etc \
|
||||
|| ERRORS+=("restic backup a échoué (code $?)")
|
||||
|
||||
# 3. Rétention ; purge et vérification le dimanche
|
||||
restic forget --tag quotidien --keep-daily 7 --keep-weekly 4 --keep-monthly 6 \
|
||||
|| ERRORS+=("restic forget a échoué")
|
||||
if [ "$(date +%u)" = 7 ]; then
|
||||
restic prune || ERRORS+=("restic prune a échoué")
|
||||
restic check --read-data-subset=5% || ERRORS+=("restic check a échoué")
|
||||
fi
|
||||
|
||||
if [ ${#ERRORS[@]} -gt 0 ]; then
|
||||
printf 'ERREUR: %s\n' "${ERRORS[@]}"
|
||||
notify_failure
|
||||
echo "===== $(date -Is) fin AVEC ERREURS"
|
||||
exit 1
|
||||
fi
|
||||
echo "===== $(date -Is) fin OK"
|
||||
Reference in New Issue
Block a user