feat(deploy): Unterstützung für Staging-Backups und -Wiederherstellungen
Erweitert die Backup- und Restore-Skripte um die Möglichkeit, Staging-Umgebungen zu unterstützen. Fügt die Option `-dest stage` hinzu, um spezifische Konfigurationen für Staging zu verwenden, einschließlich separater Docker-Compose-Dateien und Datenbankcontainer. Dokumentation aktualisiert, um manuelle Tests und Umgebungsvariablen für Staging zu reflektieren.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Automatische und manuelle Sicherung von PostgreSQL, `.env`, `docker-compose.yml` und App-Code (Git-Archiv) auf der Prod-VM.
|
Automatische und manuelle Sicherung von PostgreSQL, `.env`, `docker-compose.yml` und App-Code (Git-Archiv) auf der Prod-VM.
|
||||||
|
|
||||||
**Staging:** Kein automatisches Backup — Daten sind bewusst wegwerfbar. Deploy via `update-remotes.sh -dest stage` legt kein Backup an.
|
**Staging:** Kein automatisches Backup — Daten sind bewusst wegwerfbar. Deploy via `update-remotes.sh -dest stage` legt kein Backup an. Zum manuellen Testen auf Staging: `-dest stage` (oder Auto-Fallback, wenn nur `daagbox-staging-db` läuft).
|
||||||
|
|
||||||
## Was wird gesichert?
|
## Was wird gesichert?
|
||||||
|
|
||||||
@@ -36,6 +36,15 @@ cd /opt/kapteins-daagbok
|
|||||||
./scripts/backup.sh --reason manual --dry-run # Vorschau ohne Schreiben
|
./scripts/backup.sh --reason manual --dry-run # Vorschau ohne Schreiben
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Staging (manueller Test)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/kapteins-daagbok-staging
|
||||||
|
./scripts/backup.sh -dest stage --reason manual
|
||||||
|
# oder: Auto-Fallback, wenn nur daagbox-staging-db läuft
|
||||||
|
./scripts/backup.sh --reason manual
|
||||||
|
```
|
||||||
|
|
||||||
## Crontab (unbeaufsichtigt)
|
## Crontab (unbeaufsichtigt)
|
||||||
|
|
||||||
Beispiel: [`scripts/crontab.prod.example`](../../scripts/crontab.prod.example)
|
Beispiel: [`scripts/crontab.prod.example`](../../scripts/crontab.prod.example)
|
||||||
@@ -94,9 +103,9 @@ Vor [`rotate-postgres-password.sh`](../../scripts/rotate-postgres-password.sh) e
|
|||||||
|
|
||||||
## Umgebungsvariablen
|
## Umgebungsvariablen
|
||||||
|
|
||||||
| Variable | Default (Prod) |
|
| Variable | Prod (default) | Staging (`-dest stage`) |
|
||||||
|----------|----------------|
|
|----------|----------------|-------------------------|
|
||||||
| `BACKUP_DIR` | `/var/backups/kapteins-daagbok` |
|
| `COMPOSE_FILE` | `docker-compose.yml` | `docker-compose.staging.yml` |
|
||||||
| `COMPOSE_FILE` | `docker-compose.yml` |
|
| `DB_CONTAINER` | `daagbox-prod-db` | `daagbox-staging-db` |
|
||||||
| `DB_CONTAINER` | `daagbox-prod-db` |
|
| `BACKUP_DIR` | `/var/backups/kapteins-daagbok` | gleich |
|
||||||
| `RETENTION` | `5` |
|
| `RETENTION` | `5` | `5` |
|
||||||
|
|||||||
+50
-2
@@ -5,6 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./scripts/backup.sh
|
# ./scripts/backup.sh
|
||||||
|
# ./scripts/backup.sh -dest stage # Staging-Container (daagbox-staging-db)
|
||||||
# ./scripts/backup.sh --reason cron
|
# ./scripts/backup.sh --reason cron
|
||||||
# ./scripts/backup.sh --reason pre-deploy --tag v0.1.1.20
|
# ./scripts/backup.sh --reason pre-deploy --tag v0.1.1.20
|
||||||
# ./scripts/backup.sh --dry-run
|
# ./scripts/backup.sh --dry-run
|
||||||
@@ -14,18 +15,40 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BACKUP_DIR="${BACKUP_DIR:-/var/backups/kapteins-daagbok}"
|
BACKUP_DIR="${BACKUP_DIR:-/var/backups/kapteins-daagbok}"
|
||||||
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
|
|
||||||
DB_CONTAINER="${DB_CONTAINER:-daagbox-prod-db}"
|
|
||||||
ENV_FILE="${ENV_FILE:-.env}"
|
ENV_FILE="${ENV_FILE:-.env}"
|
||||||
RETENTION="${RETENTION:-5}"
|
RETENTION="${RETENTION:-5}"
|
||||||
|
DEST="prod"
|
||||||
REASON="manual"
|
REASON="manual"
|
||||||
EXPLICIT_TAG=""
|
EXPLICIT_TAG=""
|
||||||
DRY_RUN=0
|
DRY_RUN=0
|
||||||
|
COMPOSE_FILE=""
|
||||||
|
DB_CONTAINER=""
|
||||||
|
|
||||||
|
apply_dest_config() {
|
||||||
|
local dest="$1"
|
||||||
|
local force="${2:-0}"
|
||||||
|
if [[ "$dest" == "stage" ]]; then
|
||||||
|
if [[ "$force" == "1" || -z "${COMPOSE_FILE}" ]]; then
|
||||||
|
COMPOSE_FILE="docker-compose.staging.yml"
|
||||||
|
fi
|
||||||
|
if [[ "$force" == "1" || -z "${DB_CONTAINER}" ]]; then
|
||||||
|
DB_CONTAINER="daagbox-staging-db"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "$force" == "1" || -z "${COMPOSE_FILE}" ]]; then
|
||||||
|
COMPOSE_FILE="docker-compose.yml"
|
||||||
|
fi
|
||||||
|
if [[ "$force" == "1" || -z "${DB_CONTAINER}" ]]; then
|
||||||
|
DB_CONTAINER="daagbox-prod-db"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
sed -n '2,14p' "$0"
|
sed -n '2,14p' "$0"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
|
echo " -dest prod|stage Target environment (default: prod)"
|
||||||
echo " --reason cron|pre-deploy|manual Backup trigger (default: manual)"
|
echo " --reason cron|pre-deploy|manual Backup trigger (default: manual)"
|
||||||
echo " --tag TAG Git tag label (e.g. v0.1.1.20 for pre-deploy)"
|
echo " --tag TAG Git tag label (e.g. v0.1.1.20 for pre-deploy)"
|
||||||
echo " --dry-run Show actions without writing backup"
|
echo " --dry-run Show actions without writing backup"
|
||||||
@@ -34,6 +57,14 @@ usage() {
|
|||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
-dest)
|
||||||
|
DEST="${2:?-dest requires an argument}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-dest=*)
|
||||||
|
DEST="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--reason)
|
--reason)
|
||||||
REASON="${2:?--reason requires an argument}"
|
REASON="${2:?--reason requires an argument}"
|
||||||
shift 2
|
shift 2
|
||||||
@@ -58,6 +89,16 @@ while [ $# -gt 0 ]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
case "$DEST" in
|
||||||
|
prod|stage) ;;
|
||||||
|
*)
|
||||||
|
echo "Error: invalid -dest '$DEST' (use prod or stage)" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
apply_dest_config "$DEST"
|
||||||
|
|
||||||
case "$REASON" in
|
case "$REASON" in
|
||||||
cron|pre-deploy|manual) ;;
|
cron|pre-deploy|manual) ;;
|
||||||
*)
|
*)
|
||||||
@@ -124,8 +165,14 @@ if [ -z "${POSTGRES_PASSWORD:-}" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if ! docker inspect "$DB_CONTAINER" >/dev/null 2>&1; then
|
if ! docker inspect "$DB_CONTAINER" >/dev/null 2>&1; then
|
||||||
|
if [[ "$DEST" == "prod" ]] && docker inspect daagbox-staging-db >/dev/null 2>&1; then
|
||||||
|
echo "Note: $DB_CONTAINER not found — falling back to staging (daagbox-staging-db). Use -dest stage explicitly."
|
||||||
|
apply_dest_config stage 1
|
||||||
|
DEST="stage"
|
||||||
|
else
|
||||||
echo "Error: DB container '$DB_CONTAINER' not found" >&2
|
echo "Error: DB container '$DB_CONTAINER' not found" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$(docker inspect -f '{{.State.Running}}' "$DB_CONTAINER")" != "true" ]; then
|
if [ "$(docker inspect -f '{{.State.Running}}' "$DB_CONTAINER")" != "true" ]; then
|
||||||
@@ -173,6 +220,7 @@ from datetime import datetime, timezone
|
|||||||
manifest = {
|
manifest = {
|
||||||
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||||
"local_timestamp": "${TIMESTAMP}",
|
"local_timestamp": "${TIMESTAMP}",
|
||||||
|
"destination": "${DEST}",
|
||||||
"reason": "${REASON}",
|
"reason": "${REASON}",
|
||||||
"git_tag": "${GIT_TAG}",
|
"git_tag": "${GIT_TAG}",
|
||||||
"git_sha": "${GIT_SHA}",
|
"git_sha": "${GIT_SHA}",
|
||||||
|
|||||||
@@ -3,21 +3,46 @@
|
|||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./scripts/restore-backup.sh --list
|
# ./scripts/restore-backup.sh --list
|
||||||
|
# ./scripts/restore-backup.sh -dest stage --restore PATH
|
||||||
# ./scripts/restore-backup.sh --restore /var/backups/kapteins-daagbok/kapteins-daagbok_....tar.gz
|
# ./scripts/restore-backup.sh --restore /var/backups/kapteins-daagbok/kapteins-daagbok_....tar.gz
|
||||||
# ./scripts/restore-backup.sh --restore PATH --db-only
|
|
||||||
# ./scripts/restore-backup.sh --restore PATH --env-only
|
|
||||||
# ./scripts/restore-backup.sh --restore PATH --full --yes
|
|
||||||
#
|
#
|
||||||
# Environment overrides:
|
# Environment overrides:
|
||||||
# BACKUP_DIR, COMPOSE_FILE, DB_CONTAINER, BACKEND_CONTAINER, ENV_FILE
|
# BACKUP_DIR, COMPOSE_FILE, DB_CONTAINER, BACKEND_CONTAINER, ENV_FILE
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BACKUP_DIR="${BACKUP_DIR:-/var/backups/kapteins-daagbok}"
|
BACKUP_DIR="${BACKUP_DIR:-/var/backups/kapteins-daagbok}"
|
||||||
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
|
|
||||||
DB_CONTAINER="${DB_CONTAINER:-daagbox-prod-db}"
|
|
||||||
BACKEND_CONTAINER="${BACKEND_CONTAINER:-daagbox-prod-backend}"
|
|
||||||
ENV_FILE="${ENV_FILE:-.env}"
|
ENV_FILE="${ENV_FILE:-.env}"
|
||||||
MAX_WAIT=90
|
MAX_WAIT=90
|
||||||
|
DEST="prod"
|
||||||
|
COMPOSE_FILE=""
|
||||||
|
DB_CONTAINER=""
|
||||||
|
BACKEND_CONTAINER=""
|
||||||
|
|
||||||
|
apply_dest_config() {
|
||||||
|
local dest="$1"
|
||||||
|
local force="${2:-0}"
|
||||||
|
if [[ "$dest" == "stage" ]]; then
|
||||||
|
if [[ "$force" == "1" || -z "${COMPOSE_FILE}" ]]; then
|
||||||
|
COMPOSE_FILE="docker-compose.staging.yml"
|
||||||
|
fi
|
||||||
|
if [[ "$force" == "1" || -z "${DB_CONTAINER}" ]]; then
|
||||||
|
DB_CONTAINER="daagbox-staging-db"
|
||||||
|
fi
|
||||||
|
if [[ "$force" == "1" || -z "${BACKEND_CONTAINER}" ]]; then
|
||||||
|
BACKEND_CONTAINER="daagbox-staging-backend"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "$force" == "1" || -z "${COMPOSE_FILE}" ]]; then
|
||||||
|
COMPOSE_FILE="docker-compose.yml"
|
||||||
|
fi
|
||||||
|
if [[ "$force" == "1" || -z "${DB_CONTAINER}" ]]; then
|
||||||
|
DB_CONTAINER="daagbox-prod-db"
|
||||||
|
fi
|
||||||
|
if [[ "$force" == "1" || -z "${BACKEND_CONTAINER}" ]]; then
|
||||||
|
BACKEND_CONTAINER="daagbox-prod-backend"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
MODE="full"
|
MODE="full"
|
||||||
RESTORE_PATH=""
|
RESTORE_PATH=""
|
||||||
@@ -28,6 +53,7 @@ usage() {
|
|||||||
sed -n '2,10p' "$0"
|
sed -n '2,10p' "$0"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
|
echo " -dest prod|stage Target environment (default: prod)"
|
||||||
echo " --list List available backups"
|
echo " --list List available backups"
|
||||||
echo " --restore PATH Backup archive to restore"
|
echo " --restore PATH Backup archive to restore"
|
||||||
echo " --full Restore DB + .env (default)"
|
echo " --full Restore DB + .env (default)"
|
||||||
@@ -48,6 +74,14 @@ confirm() {
|
|||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
-dest)
|
||||||
|
DEST="${2:?-dest requires an argument}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-dest=*)
|
||||||
|
DEST="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--list)
|
--list)
|
||||||
LIST=1
|
LIST=1
|
||||||
shift
|
shift
|
||||||
@@ -84,6 +118,16 @@ while [ $# -gt 0 ]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
case "$DEST" in
|
||||||
|
prod|stage) ;;
|
||||||
|
*)
|
||||||
|
echo "Error: invalid -dest '$DEST' (use prod or stage)" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
apply_dest_config "$DEST"
|
||||||
|
|
||||||
list_backups() {
|
list_backups() {
|
||||||
if [ ! -d "$BACKUP_DIR" ]; then
|
if [ ! -d "$BACKUP_DIR" ]; then
|
||||||
echo "No backup directory: $BACKUP_DIR"
|
echo "No backup directory: $BACKUP_DIR"
|
||||||
@@ -138,9 +182,42 @@ echo "Backup manifest:"
|
|||||||
python3 -m json.tool "$MANIFEST"
|
python3 -m json.tool "$MANIFEST"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
GIT_TAG="$(python3 -c "import json; print(json.load(open('$MANIFEST'))['git_tag'])")"
|
read_manifest_field() {
|
||||||
GIT_SHA="$(python3 -c "import json; print(json.load(open('$MANIFEST'))['git_sha'])")"
|
python3 -c "import json; print(json.load(open('$MANIFEST')).get('$1', '') or '')"
|
||||||
BACKUP_TS="$(python3 -c "import json; print(json.load(open('$MANIFEST'))['local_timestamp'])")"
|
}
|
||||||
|
|
||||||
|
MANIFEST_COMPOSE="$(read_manifest_field compose_file)"
|
||||||
|
MANIFEST_DB="$(read_manifest_field db_container)"
|
||||||
|
MANIFEST_DEST="$(read_manifest_field destination)"
|
||||||
|
|
||||||
|
if [ -n "$MANIFEST_COMPOSE" ]; then
|
||||||
|
COMPOSE_FILE="$MANIFEST_COMPOSE"
|
||||||
|
fi
|
||||||
|
if [ -n "$MANIFEST_DB" ]; then
|
||||||
|
DB_CONTAINER="$MANIFEST_DB"
|
||||||
|
fi
|
||||||
|
if [ -n "$MANIFEST_DEST" ]; then
|
||||||
|
if [[ "$MANIFEST_DEST" == "stage" ]]; then
|
||||||
|
BACKEND_CONTAINER="${BACKEND_CONTAINER:-daagbox-staging-backend}"
|
||||||
|
else
|
||||||
|
BACKEND_CONTAINER="${BACKEND_CONTAINER:-daagbox-prod-backend}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker inspect "$DB_CONTAINER" >/dev/null 2>&1; then
|
||||||
|
if [[ "$DEST" == "prod" ]] && docker inspect daagbox-staging-db >/dev/null 2>&1; then
|
||||||
|
echo "Note: $DB_CONTAINER not found — falling back to staging (daagbox-staging-db). Use -dest stage explicitly."
|
||||||
|
apply_dest_config stage 1
|
||||||
|
DEST="stage"
|
||||||
|
else
|
||||||
|
echo "Error: DB container '$DB_CONTAINER' not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
GIT_TAG="$(read_manifest_field git_tag)"
|
||||||
|
GIT_SHA="$(read_manifest_field git_sha)"
|
||||||
|
BACKUP_TS="$(read_manifest_field local_timestamp)"
|
||||||
|
|
||||||
if ! confirm "Restore backup from $BACKUP_TS (tag: $GIT_TAG)? Mode: $MODE"; then
|
if ! confirm "Restore backup from $BACKUP_TS (tag: $GIT_TAG)? Mode: $MODE"; then
|
||||||
echo "Aborted."
|
echo "Aborted."
|
||||||
|
|||||||
Reference in New Issue
Block a user