Merge pull request 'feat(backup): restore subcommand (db + documents), proven on the sandbox' (#34) from claude/dolibarr-backup-restore into main
This commit was merged in pull request #34.
This commit is contained in:
+13
-5
@@ -60,14 +60,22 @@ orchestrator and the scheduled CronJob (see "Automation" below).
|
||||
(`erp/prod/db/…` 1.2 MB, `erp/prod/docs/…` 12.5 MB). Proven end-to-end live on the
|
||||
sandbox (dump + tar + GCS upload + retention prune).
|
||||
|
||||
## Restore (manual, for now)
|
||||
## Restore
|
||||
|
||||
```sh
|
||||
# DB: aws s3 cp s3://arcodange-backup/erp/<env>/db/<ts>.dump - | pg_restore -h <host> -U <user> -d <db> --clean
|
||||
# docs: aws s3 cp s3://arcodange-backup/erp/<env>/docs/<ts>.tar.gz - | tar -C /var/www/documents -xzf -
|
||||
ops/backup/dolibarr-backup.sh list --env <e> # find the <ts> key
|
||||
ops/backup/dolibarr-backup.sh restore --db <ts>.dump --env <e> --yes
|
||||
ops/backup/dolibarr-backup.sh restore --docs <ts>.tar.gz --env <e> --yes
|
||||
```
|
||||
The sandbox iso-prod refresh (`ops/sandbox/sandbox-lifecycle.sh`) is the natural
|
||||
restore-drill bench. A `restore` subcommand is wired next.
|
||||
|
||||
**DESTRUCTIVE** (requires `--yes` + an explicit `--env`): scales the app to 0, then
|
||||
- **`--db`**: `DROP OWNED BY <owner_role> CASCADE` + `pg_restore --no-owner --role`
|
||||
(same reset mechanics as `ops/sandbox/sandbox-lifecycle.sh`), then scales back;
|
||||
- **`--docs`**: clears `/var/www/documents` and untars the archive, then scales back.
|
||||
|
||||
The key is the bare `<ts>` filename from `list`; `--db`/`--docs` selects the
|
||||
`db/` or `docs/` subpath. Proven on the sandbox: a mutated `MAIN_INFO_SOCIETE_NOM`
|
||||
was reverted to the backup's value by `restore --db`.
|
||||
|
||||
## Automation — the CronJob (gated on creds)
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
case "$ENV" in
|
||||
prod) NS="erp"; DB="erp" ;;
|
||||
sandbox) NS="erp-sandbox"; DB="erp-sandbox" ;;
|
||||
prod) NS="erp"; DB="erp"; OWNER_ROLE="erp_role" ;;
|
||||
sandbox) NS="erp-sandbox"; DB="erp-sandbox"; OWNER_ROLE="erp_sandbox_role" ;;
|
||||
*) die "--env must be prod|sandbox" ;;
|
||||
esac
|
||||
PVC="$NS"
|
||||
@@ -159,13 +159,88 @@ EOF
|
||||
cleanup_secret; trap - EXIT
|
||||
}
|
||||
|
||||
# restore a half from a stored key. DESTRUCTIVE: scales the app to 0, replaces the
|
||||
# DB (DROP OWNED BY <owner> CASCADE + pg_restore) or the documents (clear + untar),
|
||||
# then scales back. Mirrors ops/sandbox/sandbox-lifecycle.sh's reset mechanics.
|
||||
run_restore() {
|
||||
trap cleanup_secret EXIT
|
||||
copy_s3_secret
|
||||
local DEPLOY="$NS" # release/instance name == namespace (erp / erp-sandbox)
|
||||
log "Restore ${KIND} on '${ENV}' from ${KEY} (scaling ${DEPLOY} to 0)"
|
||||
kubectl scale deploy "$DEPLOY" -n "$NS" --replicas=0 >/dev/null 2>&1 || true
|
||||
kubectl wait --for=delete pod -l app.kubernetes.io/instance="$NS" -n "$NS" --timeout=120s >/dev/null 2>&1 || true
|
||||
|
||||
local SCRIPT VOLS="[]" MOUNTS="[]"
|
||||
if [[ "$KIND" == "db" ]]; then
|
||||
SCRIPT="$(cat <<EOF
|
||||
$PREAMBLE
|
||||
S3 cp "s3://$BUCKET/$PREFIX/db/$KEY" /tmp/r.dump
|
||||
echo "fetched \$(wc -c < /tmp/r.dump) bytes"
|
||||
psql -h "$PGHOST" -U "\$PGUSER" -d "$DB" -v ON_ERROR_STOP=1 -c "DROP OWNED BY $OWNER_ROLE CASCADE;"
|
||||
pg_restore -h "$PGHOST" -U "\$PGUSER" -d "$DB" --no-owner --role=$OWNER_ROLE /tmp/r.dump \\
|
||||
&& echo "RESTORED db" || echo "restored db (ignorable warnings)"
|
||||
EOF
|
||||
)"
|
||||
else
|
||||
VOLS="
|
||||
- name: docs
|
||||
persistentVolumeClaim: { claimName: ${PVC} }"
|
||||
MOUNTS="
|
||||
- { name: docs, mountPath: /docs }"
|
||||
SCRIPT="$(cat <<EOF
|
||||
$PREAMBLE
|
||||
S3 cp "s3://$BUCKET/$PREFIX/docs/$KEY" /tmp/r.tgz
|
||||
echo "fetched \$(wc -c < /tmp/r.tgz) bytes"
|
||||
rm -rf /docs/* 2>/dev/null || true
|
||||
tar -C /docs -xzf /tmp/r.tgz && echo "RESTORED docs"
|
||||
EOF
|
||||
)"
|
||||
fi
|
||||
local B64; B64="$(b64 "$SCRIPT")"
|
||||
kubectl delete job dolibarr-restore -n "$NS" --ignore-not-found >/dev/null 2>&1 || true
|
||||
kubectl apply -f - >/dev/null <<EOF
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata: { name: dolibarr-restore, namespace: $NS }
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
ttlSecondsAfterFinished: 600
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
volumes: ${VOLS}
|
||||
containers:
|
||||
- name: restore
|
||||
image: $PG_IMAGE
|
||||
envFrom:
|
||||
- secretRef: { name: $TMP_S3_SECRET }
|
||||
env:
|
||||
- { name: PGUSER, valueFrom: { secretKeyRef: { name: vso-db-credentials, key: username } } }
|
||||
- { name: PGPASSWORD, valueFrom: { secretKeyRef: { name: vso-db-credentials, key: password } } }
|
||||
volumeMounts: ${MOUNTS}
|
||||
command: ["/bin/sh","-c"]
|
||||
args: ["echo $B64 | base64 -d | sh"]
|
||||
EOF
|
||||
if ! kubectl wait --for=condition=complete job/dolibarr-restore -n "$NS" --timeout=300s >/dev/null 2>&1; then
|
||||
kubectl logs -n "$NS" job/dolibarr-restore 2>&1 | sed 's/^/ /'
|
||||
kubectl scale deploy "$DEPLOY" -n "$NS" --replicas=1 >/dev/null 2>&1 || true
|
||||
die "restore Job did not complete"
|
||||
fi
|
||||
kubectl logs -n "$NS" job/dolibarr-restore | sed 's/^/ /'
|
||||
kubectl delete job dolibarr-restore -n "$NS" --ignore-not-found >/dev/null 2>&1 || true
|
||||
log "Scaling ${DEPLOY} back to 1"
|
||||
kubectl scale deploy "$DEPLOY" -n "$NS" --replicas=1 >/dev/null 2>&1 || true
|
||||
cleanup_secret; trap - EXIT
|
||||
log "Restore complete."
|
||||
}
|
||||
|
||||
case "$CMD" in
|
||||
backup) run_backup ;;
|
||||
list) run_list ;;
|
||||
restore)
|
||||
[[ -n "$KEY" && -n "$KIND" ]] || die "restore needs --db <key> or --docs <key>"
|
||||
[[ "$YES" == "1" ]] || die "restore is DESTRUCTIVE on '$ENV' — re-run with --yes"
|
||||
die "restore: wired in the chart Job (next iteration) — key=$KEY kind=$KIND env=$ENV"
|
||||
run_restore
|
||||
;;
|
||||
*) echo "usage: $0 {backup|list|restore} [--env prod|sandbox] [--db|--docs <key>] [--yes]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user