Files
Amirhossein Khalili a617fdf1ec
Some checks failed
Deployment CI/CD / validate (push) Has been cancelled
Deployment CI/CD / deploy (push) Has been cancelled
feat(scripts): add scripts for backup/restore + upload to s3 storage
2026-06-08 20:01:43 +03:30

128 lines
3.2 KiB
Bash

#!/usr/bin/env bash
set -Eeuo pipefail
usage() {
cat <<'EOF'
Usage:
backup.sh [output-directory]
Environment:
DEPLOY_ROOT Deployment directory. Defaults to ~/guilan-ace-deployment.
Creates a timestamped .tar.gz archive containing:
- PostgreSQL dump from the db service
- media files from the Django media volume
- deployment, backend, and frontend .env files when present
EOF
}
log() {
printf '[backup] %s\n' "$*"
}
fail() {
printf '[backup] %s\n' "$*" >&2
exit 1
}
require_file() {
local path="$1"
[[ -f "$path" ]] || fail "Required file not found: $path"
}
compose() {
docker compose --env-file "$DEPLOY_ENV" -f "$DEPLOY_ROOT/docker-compose.yml" "$@"
}
wait_for_db() {
compose exec -T db sh -c '
set -eu
: "${DB_USER:?DB_USER is missing}"
: "${DB_NAME:?DB_NAME is missing}"
until pg_isready --username="$DB_USER" --dbname="$DB_NAME" --host=127.0.0.1; do
sleep 1
done
' >/dev/null
}
copy_env_if_exists() {
local source_path="$1"
local target_path="$2"
if [[ -f "$source_path" ]]; then
cp "$source_path" "$target_path"
fi
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/guilan-ace-deployment}"
OUTPUT_DIR="${1:-$DEPLOY_ROOT/backups}"
BACKEND_ENV="$DEPLOY_ROOT/backend/guilan-ace-backend/.env"
FRONTEND_ENV="$DEPLOY_ROOT/frontend/guilan-ace-frontend/.env"
DEPLOY_ENV="$DEPLOY_ROOT/.env"
require_file "$DEPLOY_ROOT/docker-compose.yml"
require_file "$DEPLOY_ENV"
require_file "$BACKEND_ENV"
mkdir -p "$OUTPUT_DIR"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
TIMESTAMP="$(date -u +'%Y%m%d-%H%M%S')"
ARCHIVE_NAME="guilan-ace-backup-$TIMESTAMP.tar.gz"
ARCHIVE_PATH="$OUTPUT_DIR/$ARCHIVE_NAME"
mkdir -p "$WORK_DIR/env"
log "Checking Docker Compose configuration"
compose config -q
log "Starting database service if needed"
compose up -d db
wait_for_db
log "Dumping database from db service"
compose exec -T db sh -c '
set -eu
: "${DB_USER:?DB_USER is missing}"
: "${DB_NAME:?DB_NAME is missing}"
pg_dump \
--username="$DB_USER" \
--dbname="$DB_NAME" \
--format=plain \
--clean \
--if-exists \
--no-owner \
--no-privileges
' > "$WORK_DIR/database.sql"
log "Archiving media files from Django media volume"
if compose ps --status running --services | grep -qx 'web'; then
compose exec -T web sh -c 'mkdir -p /app/media && tar -C /app/media -czf - .' > "$WORK_DIR/media.tar.gz"
else
log "Web service is not running; using a temporary container for media volume"
compose run --rm --no-deps --entrypoint sh web -c 'mkdir -p /app/media && tar -C /app/media -czf - .' > "$WORK_DIR/media.tar.gz"
fi
log "Copying environment files"
copy_env_if_exists "$DEPLOY_ENV" "$WORK_DIR/env/deployment.env"
copy_env_if_exists "$BACKEND_ENV" "$WORK_DIR/env/backend.env"
copy_env_if_exists "$FRONTEND_ENV" "$WORK_DIR/env/frontend.env"
cat > "$WORK_DIR/manifest.txt" <<EOF
name=$ARCHIVE_NAME
created_at_utc=$TIMESTAMP
deploy_root=$DEPLOY_ROOT
includes=database.sql,media.tar.gz,env/deployment.env,env/backend.env,env/frontend.env
EOF
log "Creating archive $ARCHIVE_PATH"
tar -C "$WORK_DIR" -czf "$ARCHIVE_PATH" .
log "Backup completed: $ARCHIVE_PATH"