#!/usr/bin/env bash set -euo pipefail # ================================================== # REQUIRED ENV VARS # ================================================== ENVIRONMENT="${ENVIRONMENT:-}" if [[ -z "$ENVIRONMENT" ]]; then echo "❌ ENVIRONMENT must be set to dev or prod" exit 1 fi # ================================================== # GLOBAL CONFIG # ================================================== NAMESPACE="default" K8S_STORAGE_ROOT="/k8s_storage" BACKUP_ROOT="/tmp/k8s-backups" DATE="$(date -u +%Y-%m-%d_%H-%M-%S)" BACKUP_DIR="$BACKUP_ROOT/$DATE" mkdir -p "$BACKUP_DIR" # NEVER touch raw Postgres data TAR_EXCLUDES=( "$K8S_STORAGE_ROOT/postgres" ) # ================================================== # ENVIRONMENT SWITCH # ================================================== case "$ENVIRONMENT" in dev) PG_SECRET_NAME="postgres-secret" PG_POD_SELECTOR="app=postgres-dev" S3_PREFIX="dev" ;; prod) if [[ "${I_UNDERSTAND_THIS_IS_PROD:-}" != "true" ]]; then echo "❌ Refusing to run PROD backup without confirmation" echo " Re-run with: I_UNDERSTAND_THIS_IS_PROD=true" exit 1 fi PG_SECRET_NAME="postgres-prod-secret" PG_POD_SELECTOR="app=postgres-prod" S3_PREFIX="prod" ;; *) echo "❌ Invalid ENVIRONMENT: $ENVIRONMENT (must be dev or prod)" exit 1 ;; esac echo "=== Backup started ($(date -u)) ===" echo "Environment: $ENVIRONMENT" # ================================================== # POSTGRES DUMP (SAFE) # ================================================== POSTGRES_POD=$(kubectl get pods \ -n "$NAMESPACE" \ -l "$PG_POD_SELECTOR" \ -o jsonpath='{.items[*].metadata.name}' | awk '{print $1}') if [[ -z "$POSTGRES_POD" ]]; then echo "❌ No Postgres pod found for selector: $PG_POD_SELECTOR" kubectl get pods -n "$NAMESPACE" exit 1 fi POSTGRES_USER=$(kubectl get secret "$PG_SECRET_NAME" \ -n "$NAMESPACE" \ -o jsonpath='{.data.POSTGRES_USER}' | base64 -d) POSTGRES_DB=$(kubectl get secret "$PG_SECRET_NAME" \ -n "$NAMESPACE" \ -o jsonpath='{.data.POSTGRES_DB}' 2>/dev/null | base64 -d || true) if [[ -z "$POSTGRES_DB" ]]; then echo "❌ POSTGRES_DB missing in secret $PG_SECRET_NAME" exit 1 fi echo "Dumping database: $POSTGRES_DB" kubectl exec -n "$NAMESPACE" "$POSTGRES_POD" -- \ pg_dump \ -h localhost \ -U "$POSTGRES_USER" \ "$POSTGRES_DB" \ > "$BACKUP_DIR/postgres.sql" echo "✔ pg_dump complete ($(du -h "$BACKUP_DIR/postgres.sql" | cut -f1))" # ================================================== # NORMALISE PERMISSIONS (EXCLUDING POSTGRES) # ================================================== echo "Normalising permissions (excluding Postgres data)..." sudo find "$K8S_STORAGE_ROOT" \ -mindepth 1 \ -maxdepth 1 \ ! -name postgres \ -exec chmod -R a+rX {} \; || true # ================================================== # ARCHIVE K8S STORAGE (SAFE) # ================================================== TAR_EXCLUDE_ARGS=() for path in "${TAR_EXCLUDES[@]}"; do TAR_EXCLUDE_ARGS+=(--exclude="$path") done tar \ --ignore-failed-read \ --warning=no-file-changed \ -czf "$BACKUP_DIR/k8s_storage_$DATE.tar.gz" \ "${TAR_EXCLUDE_ARGS[@]}" \ "$K8S_STORAGE_ROOT" echo "✔ k8s_storage archived ($(du -h "$BACKUP_DIR/k8s_storage_$DATE.tar.gz" | cut -f1))" # ================================================== # UPLOAD TO S3 # ================================================== S3_BUCKET="s3://mist-backups/$S3_PREFIX/$DATE" aws s3 cp "$BACKUP_DIR" "$S3_BUCKET" --recursive echo "✔ Uploaded to $S3_BUCKET" # ================================================== # RESTORE GUIDE # ================================================== echo "" echo "========================================" echo "=== RESTORE GUIDE ($ENVIRONMENT)" echo "========================================" echo "" echo "Restore volumes:" echo " sudo tar -xzf k8s_storage_$DATE.tar.gz -C /" echo "" echo "Restore Postgres:" echo " kubectl exec -n $NAMESPACE -i $POSTGRES_POD -- \\" echo " psql -U $POSTGRES_USER $POSTGRES_DB < postgres.sql" echo "" echo "=== Backup completed successfully ==="