162 lines
4.5 KiB
Bash
162 lines
4.5 KiB
Bash
#!/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
|
|
# ==================================================
|
|
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"
|
|
"$K8S_STORAGE_ROOT/lost+found"
|
|
)
|
|
|
|
# ==================================================
|
|
# ENVIRONMENT SWITCH
|
|
# ==================================================
|
|
case "$ENVIRONMENT" in
|
|
dev)
|
|
PG_SECRET_NAME="postgres-dev"
|
|
PG_POD_SELECTOR="app=postgres-dev"
|
|
S3_PREFIX="dev"
|
|
NAMESPACE="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"
|
|
PG_POD_SELECTOR="app=postgres-prod"
|
|
S3_PREFIX="prod"
|
|
NAMESPACE="default"
|
|
;;
|
|
*)
|
|
echo "❌ Invalid ENVIRONMENT: $ENVIRONMENT (must be dev or prod)"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo "=== Backup started ($(date -u)) ==="
|
|
echo "Environment: $ENVIRONMENT"
|
|
echo "Namespace: $NAMESPACE"
|
|
|
|
# ==================================================
|
|
# LOCATE POSTGRES POD
|
|
# ==================================================
|
|
POSTGRES_POD=$(kubectl get pods \
|
|
-n "$NAMESPACE" \
|
|
-l "$PG_POD_SELECTOR" \
|
|
-o jsonpath='{.items[0].metadata.name}')
|
|
|
|
if [[ -z "$POSTGRES_POD" ]]; then
|
|
echo "❌ No Postgres pod found for selector: $PG_POD_SELECTOR"
|
|
kubectl get pods -n "$NAMESPACE"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Using Postgres pod: $POSTGRES_POD"
|
|
|
|
# ==================================================
|
|
# READ DATABASE_URL FROM SECRET
|
|
# ==================================================
|
|
DATABASE_URL=$(kubectl get secret "$PG_SECRET_NAME" \
|
|
-n "$NAMESPACE" \
|
|
-o jsonpath='{.data.DATABASE_URL}' | base64 -d)
|
|
|
|
if [[ -z "$DATABASE_URL" ]]; then
|
|
echo "❌ DATABASE_URL missing in secret $PG_SECRET_NAME"
|
|
exit 1
|
|
fi
|
|
|
|
# Parse DATABASE_URL
|
|
POSTGRES_USER="$(echo "$DATABASE_URL" | sed -E 's|.*://([^:]+):.*|\1|')"
|
|
POSTGRES_DB="$(echo "$DATABASE_URL" | sed -E 's|.*/([^?]+).*|\1|')"
|
|
|
|
if [[ -z "$POSTGRES_USER" || -z "$POSTGRES_DB" ]]; then
|
|
echo "❌ Failed to parse DATABASE_URL"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Dumping database: $POSTGRES_DB (user: $POSTGRES_USER)"
|
|
|
|
# ==================================================
|
|
# POSTGRES LOGICAL DUMP (SAFE)
|
|
# ==================================================
|
|
kubectl exec -n "$NAMESPACE" "$POSTGRES_POD" -- \
|
|
pg_dump "$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 $POSTGRES_DB < postgres.sql"
|
|
echo ""
|
|
echo "=== Backup completed successfully ==="
|