Les backups nocturnes du 4 et 5 octobre ont échoué ("restic : commande
introuvable") : cron ne fournit que /usr/bin:/bin alors que restic est
dans /usr/local/bin. Testé avec le PATH de cron (systemd-run -E).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
4.2 KiB
Bash
100 lines
4.2 KiB
Bash
#!/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
|
|
# cron ne fournit que /usr/bin:/bin ; restic est dans /usr/local/bin
|
|
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
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" |
|
|
curl -sS --url smtps://mail.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"
|