Sauvegardes : dump avant migration, fichiers en 077, chiffrement age avant envoi

deploy.sh sauvegarde la base dans $BACKUP_ROOT/pre-migration quand prisma migrate
status signale un écart, garde les 10 derniers dumps et s'arrête si pg_dump échoue.
backup.sh crée ses fichiers en umask 077 et, si BACKUP_AGE_RECIPIENT est renseigné,
n'envoie sur S3 que des copies chiffrées avec age. Commentaires sur les polices
du frontend mis à jour (deploy.sh, ci.yml).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
IliesAllaliandClaude Opus 5.5 committed 2026-09-30 17:41:15 +02:00
1 parent 79b6ad356b
commit 46f19ee87e
3 files changed
+85 -4

No files matched your search

+1 -1
View File
@@ -71,7 +71,7 @@ jobs:
- name: Tests
run: npm test
# Le build lit les polices dans ../frontend/src/assets, présentes dans le dépôt.
# Le build ne lit que landing/ : aucune police n'est prise dans ../frontend.
- name: Build
run: npm run build
+44 -2
View File
@@ -5,7 +5,15 @@
# La base est sauvegardée à chaque passage. L'archive des médias n'est refaite que si les médias ont
# changé (sinon une archive identique par jour), et seules les
# BACKUP_MEDIA_KEEP dernières archives sont gardées.
#
# Envoi hors du serveur (facultatif, BACKUP_S3_BUCKET) : si BACKUP_AGE_RECIPIENT est renseigné (une ou
# plusieurs clés publiques séparées par des espaces, « age1... » ou « ssh-ed25519 AAAA... » sans commentaire),
# le dump et l'archive sont chiffrés avec age avant l'envoi et seules les copies chiffrées (.age) partent.
# Les fichiers gardés sur le serveur restent en clair, lisibles par root seulement.
# Restauration d'une copie chiffrée : age -d -i cle-privee.txt fichier.dump.age > fichier.dump
set -euo pipefail
# Dumps, archives et empreintes lisibles par le seul propriétaire
umask 077
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
BACKEND_ENV_FILE="$REPO_DIR/backend/.env"
@@ -115,9 +123,43 @@ if [ -n "${BACKUP_S3_BUCKET:-}" ]; then
S3_ARGS+=(--region "$BACKUP_S3_REGION")
fi
aws s3 cp "$DB_BACKUP_FILE" "$S3_BASE_URI/db/$(basename "$DB_BACKUP_FILE")" "${S3_ARGS[@]}"
# Copies à envoyer : chiffrées si une clé age est configurée, sinon les fichiers tels quels (comportement historique)
UPLOAD_DB_FILE="$DB_BACKUP_FILE"
UPLOAD_MEDIA_FILE="$MEDIA_BACKUP_FILE"
if [ -n "${BACKUP_AGE_RECIPIENT:-}" ]; then
if ! command -v age >/dev/null 2>&1; then
echo "ERROR: BACKUP_AGE_RECIPIENT is set but age is not installed (apt install age)."
exit 1
fi
AGE_ARGS=()
read -r -a AGE_KEYS <<< "$BACKUP_AGE_RECIPIENT"
# Une clé ssh s'écrit en deux mots (« ssh-ed25519 AAAA... ») : on recolle type et clé
i=0
while [ "$i" -lt "${#AGE_KEYS[@]}" ]; do
key="${AGE_KEYS[$i]}"
if [[ "$key" == ssh-* ]]; then
i=$((i + 1))
key="$key ${AGE_KEYS[$i]:-}"
fi
AGE_ARGS+=(-r "$key")
i=$((i + 1))
done
ENCRYPT_DIR="$(mktemp -d "$BACKUP_ROOT/.offsite-XXXXXX")"
trap 'rm -rf "$ENCRYPT_DIR"' EXIT
UPLOAD_DB_FILE="$ENCRYPT_DIR/$(basename "$DB_BACKUP_FILE").age"
age "${AGE_ARGS[@]}" -o "$UPLOAD_DB_FILE" "$DB_BACKUP_FILE"
if [ "$MEDIA_IS_NEW" = "1" ]; then
UPLOAD_MEDIA_FILE="$ENCRYPT_DIR/$(basename "$MEDIA_BACKUP_FILE").age"
age "${AGE_ARGS[@]}" -o "$UPLOAD_MEDIA_FILE" "$MEDIA_BACKUP_FILE"
fi
echo "Copies chiffrées avec age avant envoi."
else
echo "WARNING: BACKUP_AGE_RECIPIENT absent, les sauvegardes partent en clair."
fi
aws s3 cp "$UPLOAD_DB_FILE" "$S3_BASE_URI/db/$(basename "$UPLOAD_DB_FILE")" "${S3_ARGS[@]}"
if [ "$MEDIA_IS_NEW" = "1" ]; then
aws s3 cp "$MEDIA_BACKUP_FILE" "$S3_BASE_URI/media/$(basename "$MEDIA_BACKUP_FILE")" "${S3_ARGS[@]}"
aws s3 cp "$UPLOAD_MEDIA_FILE" "$S3_BASE_URI/media/$(basename "$UPLOAD_MEDIA_FILE")" "${S3_ARGS[@]}"
fi
aws s3 cp "$CHECKSUM_FILE" "$S3_BASE_URI/meta/$(basename "$CHECKSUM_FILE")" "${S3_ARGS[@]}"
else
+40 -1
View File
@@ -92,6 +92,43 @@ fix_perms() {
find "$REPO_DIR/$1" -type f -exec chmod 644 {} +
}
# Sauvegarde de la base avant migration, à côté de celles de deploy/backup.sh ($BACKUP_ROOT/pre-migration).
# « prisma migrate status » sort en 0 quand la base est à jour : pas de dump. Tout autre code (migration en
# attente, historique divergent, base injoignable) déclenche le dump, et un dump raté arrête le déploiement.
# Les PREMIGRATION_KEEP derniers dumps sont gardés (10 par défaut).
predeploy_dump() {
if (cd "$REPO_DIR/backend" && npx --no-install prisma migrate status >/dev/null 2>&1); then
log "Aucune migration en attente, pas de sauvegarde avant migration"
return 0
fi
log "Migrations en attente : sauvegarde de la base avant de les appliquer"
(
set -euo pipefail
umask 077
# Sous-shell : DATABASE_URL et les réglages de sauvegarde ne fuient pas dans les builds qui suivent
set -a
# shellcheck disable=SC1090,SC1091
. "$REPO_DIR/backend/.env"
if [ -f "${BACKUP_ENV_FILE:-/etc/genealogy/backup.env}" ]; then
# shellcheck disable=SC1090
. "${BACKUP_ENV_FILE:-/etc/genealogy/backup.env}"
fi
set +a
command -v pg_dump >/dev/null 2>&1 || { echo "ERROR: pg_dump absent, déploiement arrêté avant migration"; exit 1; }
[ -n "${DATABASE_URL:-}" ] || { echo "ERROR: DATABASE_URL absent de backend/.env"; exit 1; }
dir="${BACKUP_ROOT:-/var/backups/genealogy}/pre-migration"
mkdir -p "$dir"
file="$dir/genealogy-premigration-$(date -u +%Y%m%dT%H%M%SZ)-${NEW_SHA:0:12}.dump"
if ! pg_dump "$DATABASE_URL" --format=custom --file "$file"; then
rm -f "$file"
echo "ERROR: pg_dump a échoué, déploiement arrêté avant migration"
exit 1
fi
echo "[deploy] Base sauvegardée : $file"
ls -1t "$dir"/genealogy-premigration-*.dump 2>/dev/null | tail -n +"$((${PREMIGRATION_KEEP:-10} + 1))" | xargs -r rm -f
)
}
# 4. Backend
BACKEND_CHANGED=0
if changed backend ecosystem.config.cjs; then
@@ -101,6 +138,7 @@ if changed backend ecosystem.config.cjs; then
|| { echo "ERROR: dépendances backend manquantes après installation"; exit 1; }
log "Build backend"
(cd "$REPO_DIR/backend" && NODE_ENV='' npm run prisma:generate && NODE_ENV='' npm run build)
predeploy_dump
log "Migrations"
(cd "$REPO_DIR/backend" && npm run prisma:deploy)
else
@@ -117,7 +155,8 @@ else
log "Frontend inchangé"
fi
# 6. Landing (elle utilise les polices du frontend)
# 6. Landing (autonome, plus aucune police lue dans le frontend ; frontend/src/assets reste dans le
# déclencheur par prudence, sans effet tant que rien n'y change)
if changed landing frontend/src/assets; then
install_deps landing
log "Build landing"