refractor code,

This commit is contained in:
root
2026-05-21 12:35:49 -04:00
parent e74681e810
commit f009ca10c6
158 changed files with 215801 additions and 5884 deletions
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "${ROOT_DIR}/scripts/docker-prod-common.sh"
BACKUP_ROOT="${1:-${ROOT_DIR}/backups}"
shift || true
EXTRA_VOLUMES=("$@")
TIMESTAMP="$(date -u +"%Y%m%dT%H%M%SZ")"
mkdir -p "${BACKUP_ROOT}"
BACKUP_ROOT="$(cd "${BACKUP_ROOT}" && pwd)"
BACKUP_DIR="${BACKUP_ROOT}/rentaldrivego-prod-${TIMESTAMP}"
VOLUME_BACKUP_DIR="${BACKUP_DIR}/volumes"
DEFAULT_VOLUMES=(
"${PROJECT_NAME}_api_uploads"
"${PROJECT_NAME}_pgmanage_prod_data"
"${PROJECT_NAME}_postgres_prod_data"
"${PROJECT_NAME}_redis_prod_data"
)
mkdir -p "${BACKUP_DIR}"
mkdir -p "${VOLUME_BACKUP_DIR}"
ensure_env_file
ensure_traefik_network
echo "Creating production backup in ${BACKUP_DIR}"
prod_compose up -d postgres >/dev/null
echo "Backing up PostgreSQL database..."
prod_compose exec -T postgres sh -lc 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "${BACKUP_DIR}/postgres.dump"
echo "Backing up uploaded files..."
prod_compose run --rm --no-deps api bash -lc '
mkdir -p /var/lib/rentaldrivego/storage
tar -czf - -C /var/lib/rentaldrivego/storage .
' > "${BACKUP_DIR}/api-uploads.tar.gz"
echo "Backing up Traefik certificates..."
if traefik_compose run --rm traefik sh -lc 'test -d /letsencrypt' > /dev/null 2>&1; then
traefik_compose run --rm traefik sh -lc '
mkdir -p /letsencrypt
tar -czf - -C /letsencrypt .
' > "${BACKUP_DIR}/traefik-letsencrypt.tar.gz"
else
echo "Skipping Traefik certificate backup; Traefik runtime is not available."
fi
declare -a ALL_VOLUMES=("${DEFAULT_VOLUMES[@]}")
if [[ -n "${DOCKER_EXTRA_BACKUP_VOLUMES:-}" ]]; then
# shellcheck disable=SC2206
ALL_VOLUMES+=(${DOCKER_EXTRA_BACKUP_VOLUMES})
fi
if [[ "${#EXTRA_VOLUMES[@]}" -gt 0 ]]; then
ALL_VOLUMES+=("${EXTRA_VOLUMES[@]}")
fi
echo "Backing up named Docker volumes..."
declare -A seen_volumes=()
for volume_name in "${ALL_VOLUMES[@]}"; do
[[ -n "${volume_name}" ]] || continue
if [[ -n "${seen_volumes[${volume_name}]:-}" ]]; then
continue
fi
seen_volumes["${volume_name}"]=1
if ! docker_volume_exists "${volume_name}"; then
echo "Skipping missing volume: ${volume_name}"
continue
fi
echo " - ${volume_name}"
backup_named_volume "${volume_name}" "${VOLUME_BACKUP_DIR}/${volume_name}.tar.gz"
done
cat > "${BACKUP_DIR}/manifest.txt" <<EOF
project=${PROJECT_NAME}
created_at_utc=${TIMESTAMP}
database_backup=postgres.dump
uploads_backup=api-uploads.tar.gz
traefik_backup=traefik-letsencrypt.tar.gz
volume_backup_dir=volumes
EOF
echo "Backup complete:"
echo " - ${BACKUP_DIR}/postgres.dump"
echo " - ${BACKUP_DIR}/api-uploads.tar.gz"
if [[ -f "${BACKUP_DIR}/traefik-letsencrypt.tar.gz" ]]; then
echo " - ${BACKUP_DIR}/traefik-letsencrypt.tar.gz"
fi
if compgen -G "${VOLUME_BACKUP_DIR}/*.tar.gz" > /dev/null; then
echo " - ${VOLUME_BACKUP_DIR}/*.tar.gz"
fi
echo " - ${BACKUP_DIR}/manifest.txt"
+46 -1
View File
@@ -8,6 +8,7 @@ TRAEFIK_COMPOSE_FILE="${ROOT_DIR}/traefik.yaml"
ENV_FILE="${ROOT_DIR}/.env.docker.production"
PROJECT_NAME="${DOCKER_PROD_PROJECT_NAME:-rentaldrivego-prod}"
TRAEFIK_NETWORK="${DOCKER_PROD_TRAEFIK_NETWORK:-traefik-proxy}"
VOLUME_BACKUP_IMAGE="${DOCKER_VOLUME_BACKUP_IMAGE:-postgres:16-alpine}"
ensure_env_file() {
if [[ ! -f "${ENV_FILE}" ]]; then
@@ -29,7 +30,7 @@ prod_compose() {
}
traefik_compose() {
docker compose -f "${TRAEFIK_COMPOSE_FILE}" "$@"
docker compose --env-file "${ENV_FILE}" -f "${TRAEFIK_COMPOSE_FILE}" "$@"
}
start_prod_services() {
@@ -42,3 +43,47 @@ start_traefik() {
ensure_traefik_network
traefik_compose up -d
}
stop_traefik() {
ensure_env_file
traefik_compose down >/dev/null 2>&1 || true
}
docker_volume_exists() {
docker volume inspect "$1" >/dev/null 2>&1
}
backup_named_volume() {
local volume_name="$1"
local archive_path="$2"
docker run --rm \
-v "${volume_name}:/volume:ro" \
-v "$(dirname "${archive_path}"):/backup" \
"${VOLUME_BACKUP_IMAGE}" \
sh -lc "tar -czf /backup/$(basename "${archive_path}") -C /volume ."
}
volume_running_containers() {
docker ps --filter "volume=$1" --format '{{.Names}}'
}
restore_named_volume() {
local volume_name="$1"
local archive_path="$2"
local running
running="$(volume_running_containers "${volume_name}")"
if [[ -n "${running}" ]]; then
echo "Volume ${volume_name} is currently used by running containers:" >&2
echo "${running}" >&2
echo "Stop those containers before restoring this volume." >&2
exit 1
fi
docker run --rm \
-v "${volume_name}:/volume" \
-v "$(dirname "${archive_path}"):/backup" \
"${VOLUME_BACKUP_IMAGE}" \
sh -lc "find /volume -mindepth 1 -delete && tar -xzf /backup/$(basename "${archive_path}") -C /volume"
}
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "${ROOT_DIR}/scripts/docker-prod-common.sh"
BACKUP_DIR="${1:-}"
CONFIRM_FLAG="${2:-}"
if [[ -z "${BACKUP_DIR}" ]]; then
echo "Usage: bash scripts/docker-prod-restore.sh <backup-dir> --yes" >&2
exit 1
fi
if [[ "${CONFIRM_FLAG}" != "--yes" ]]; then
echo "Restore is destructive and will overwrite the production database and uploaded files." >&2
echo "Re-run with: bash scripts/docker-prod-restore.sh ${BACKUP_DIR} --yes" >&2
exit 1
fi
if [[ ! -d "${BACKUP_DIR}" ]]; then
echo "Backup directory not found: ${BACKUP_DIR}" >&2
exit 1
fi
if [[ ! -f "${BACKUP_DIR}/postgres.dump" ]]; then
echo "Missing database backup: ${BACKUP_DIR}/postgres.dump" >&2
exit 1
fi
if [[ ! -f "${BACKUP_DIR}/api-uploads.tar.gz" ]]; then
echo "Missing uploads backup: ${BACKUP_DIR}/api-uploads.tar.gz" >&2
exit 1
fi
ensure_env_file
ensure_traefik_network
RAW_VOLUME_DIR="${BACKUP_DIR}/volumes"
SKIP_RAW_RESTORE_VOLUMES=(
"${PROJECT_NAME}_api_uploads"
"${PROJECT_NAME}_postgres_prod_data"
)
echo "Stopping application services before restore..."
prod_compose stop api marketplace dashboard admin pgmanage >/dev/null || true
prod_compose stop redis >/dev/null || true
stop_traefik
echo "Ensuring PostgreSQL is running..."
prod_compose up -d postgres >/dev/null
echo "Waiting for PostgreSQL..."
until prod_compose exec -T postgres sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' >/dev/null 2>&1; do
sleep 2
done
echo "Restoring PostgreSQL database..."
cat "${BACKUP_DIR}/postgres.dump" | prod_compose exec -T postgres sh -lc '
pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists --no-owner --no-privileges
'
echo "Restoring uploaded files..."
cat "${BACKUP_DIR}/api-uploads.tar.gz" | prod_compose run --rm --no-deps api bash -lc '
mkdir -p /var/lib/rentaldrivego/storage
find /var/lib/rentaldrivego/storage -mindepth 1 -delete
tar -xzf - -C /var/lib/rentaldrivego/storage
'
if [[ -f "${BACKUP_DIR}/traefik-letsencrypt.tar.gz" ]]; then
echo "Restoring Traefik certificates..."
cat "${BACKUP_DIR}/traefik-letsencrypt.tar.gz" | traefik_compose run --rm traefik sh -lc '
mkdir -p /letsencrypt
find /letsencrypt -mindepth 1 -delete
tar -xzf - -C /letsencrypt
chmod 600 /letsencrypt/acme.json 2>/dev/null || true
'
fi
if [[ -d "${RAW_VOLUME_DIR}" ]]; then
echo "Restoring raw Docker volume archives..."
for archive_path in "${RAW_VOLUME_DIR}"/*.tar.gz; do
[[ -e "${archive_path}" ]] || continue
volume_name="$(basename "${archive_path}" .tar.gz)"
skip_volume=false
for managed_volume in "${SKIP_RAW_RESTORE_VOLUMES[@]}"; do
if [[ "${volume_name}" == "${managed_volume}" ]]; then
skip_volume=true
break
fi
done
if [[ "${skip_volume}" == true ]]; then
echo "Skipping raw restore for ${volume_name}; handled by service-aware restore."
continue
fi
if ! docker_volume_exists "${volume_name}"; then
echo "Creating missing volume ${volume_name}"
docker volume create "${volume_name}" >/dev/null
fi
echo " - ${volume_name}"
restore_named_volume "${volume_name}" "${archive_path}"
done
fi
echo "Starting production services..."
start_traefik
start_prod_services
echo "Restore complete from ${BACKUP_DIR}"