backup.sh now covers all data: - SQLite via podman exec into server container (fallback to host path) - Postgres via pg_dumpall inside postgres container - Forgejo data volume via podman volume export - Caddy TLS certificates via podman volume export - .env file (plaintext secrets — store archive securely) restore.sh reverses each step: imports volumes, restores Postgres, restores SQLite, optionally restores .env (--force to overwrite). Both scripts find containers dynamically via compose service labels so they work regardless of the container name podman-compose assigns. .env.example documents HIY_BACKUP_DIR, HIY_BACKUP_REMOTE, HIY_BACKUP_RETAIN_DAYS. https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
143 lines
5.7 KiB
Bash
Executable file
143 lines
5.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# HIY restore script
|
|
#
|
|
# Restores a backup archive produced by infra/backup.sh.
|
|
#
|
|
# Usage:
|
|
# ./infra/restore.sh /path/to/hiy-backup-20260101-030000.tar.gz
|
|
#
|
|
# What is restored:
|
|
# 1. SQLite database (hiy.db)
|
|
# 2. Env files and git repos
|
|
# 3. Postgres databases (pg_dumpall dump)
|
|
# 4. Forgejo data volume
|
|
# 5. Caddy TLS certificates
|
|
# 6. .env file (optional — skipped if already present unless --force is passed)
|
|
#
|
|
# ⚠ Run this with the stack STOPPED, then bring it back up afterwards:
|
|
# podman compose -f infra/docker-compose.yml down
|
|
# ./infra/restore.sh hiy-backup-*.tar.gz
|
|
# podman compose -f infra/docker-compose.yml up -d
|
|
|
|
set -euo pipefail
|
|
|
|
ARCHIVE="${1:-}"
|
|
FORCE="${2:-}"
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
ENV_FILE="${SCRIPT_DIR}/../.env"
|
|
HIY_DATA_DIR="${HIY_DATA_DIR:-/data}"
|
|
|
|
log() { echo "[hiy-restore] $(date '+%H:%M:%S') $*"; }
|
|
die() { log "ERROR: $*"; exit 1; }
|
|
|
|
# ── Validate ───────────────────────────────────────────────────────────────────
|
|
[ -z "${ARCHIVE}" ] && die "Usage: $0 <archive.tar.gz> [--force]"
|
|
[ -f "${ARCHIVE}" ] || die "Archive not found: ${ARCHIVE}"
|
|
|
|
WORK_DIR=$(mktemp -d)
|
|
trap 'rm -rf "${WORK_DIR}"' EXIT
|
|
|
|
log "=== HIY Restore ==="
|
|
log "Archive : ${ARCHIVE}"
|
|
log "Work dir: ${WORK_DIR}"
|
|
|
|
log "Extracting archive…"
|
|
tar -xzf "${ARCHIVE}" -C "${WORK_DIR}"
|
|
|
|
# ── Helper: find a running container by compose service label ──────────────────
|
|
find_container() {
|
|
local service="$1"
|
|
podman ps --filter "label=com.docker.compose.service=${service}" \
|
|
--format '{{.Names}}' | head -1
|
|
}
|
|
|
|
# ── 1. .env file ───────────────────────────────────────────────────────────────
|
|
log "--- .env ---"
|
|
if [ -f "${WORK_DIR}/dot-env" ]; then
|
|
if [ -f "${ENV_FILE}" ] && [ "${FORCE}" != "--force" ]; then
|
|
log "SKIP: ${ENV_FILE} already exists (pass --force to overwrite)"
|
|
else
|
|
cp "${WORK_DIR}/dot-env" "${ENV_FILE}"
|
|
log "Restored .env to ${ENV_FILE}"
|
|
fi
|
|
else
|
|
log "No .env in archive — skipping"
|
|
fi
|
|
|
|
# ── 2. SQLite ──────────────────────────────────────────────────────────────────
|
|
log "--- SQLite ---"
|
|
if [ -f "${WORK_DIR}/hiy.sql" ]; then
|
|
DB_PATH="${HIY_DATA_DIR}/hiy.db"
|
|
mkdir -p "$(dirname "${DB_PATH}")"
|
|
if [ -f "${DB_PATH}" ]; then
|
|
log "Moving existing hiy.db to hiy.db.bak…"
|
|
mv "${DB_PATH}" "${DB_PATH}.bak"
|
|
fi
|
|
log "Restoring hiy.db…"
|
|
sqlite3 "${DB_PATH}" < "${WORK_DIR}/hiy.sql"
|
|
log "SQLite restored."
|
|
else
|
|
log "No hiy.sql in archive — skipping"
|
|
fi
|
|
|
|
# ── 3. Env files & git repos ───────────────────────────────────────────────────
|
|
log "--- Env files ---"
|
|
if [ -f "${WORK_DIR}/envs.tar.gz" ]; then
|
|
log "Restoring envs/…"
|
|
tar -xzf "${WORK_DIR}/envs.tar.gz" -C "${HIY_DATA_DIR}"
|
|
fi
|
|
|
|
log "--- Git repos ---"
|
|
if [ -f "${WORK_DIR}/repos.tar.gz" ]; then
|
|
log "Restoring repos/…"
|
|
tar -xzf "${WORK_DIR}/repos.tar.gz" -C "${HIY_DATA_DIR}"
|
|
fi
|
|
|
|
# ── 4. Postgres ────────────────────────────────────────────────────────────────
|
|
log "--- Postgres ---"
|
|
if [ -f "${WORK_DIR}/postgres.sql" ]; then
|
|
PG_CTR=$(find_container postgres)
|
|
if [ -n "${PG_CTR}" ]; then
|
|
log "Restoring Postgres via container ${PG_CTR}…"
|
|
# Drop existing connections then restore.
|
|
podman exec -i "${PG_CTR}" psql -U hiy_admin -d postgres \
|
|
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IN ('hiy','forgejo') AND pid <> pg_backend_pid();" \
|
|
> /dev/null 2>&1 || true
|
|
podman exec -i "${PG_CTR}" psql -U hiy_admin -d postgres \
|
|
< "${WORK_DIR}/postgres.sql"
|
|
log "Postgres restored."
|
|
else
|
|
log "WARNING: postgres container not running"
|
|
log " Start Postgres first, then run:"
|
|
log " podman exec -i <postgres_container> psql -U hiy_admin -d postgres < ${WORK_DIR}/postgres.sql"
|
|
fi
|
|
else
|
|
log "No postgres.sql in archive — skipping"
|
|
fi
|
|
|
|
# ── 5. Forgejo data volume ─────────────────────────────────────────────────────
|
|
log "--- Forgejo volume ---"
|
|
if [ -f "${WORK_DIR}/forgejo-data.tar" ]; then
|
|
log "Importing forgejo-data volume…"
|
|
podman volume exists forgejo-data 2>/dev/null || podman volume create forgejo-data
|
|
podman volume import forgejo-data "${WORK_DIR}/forgejo-data.tar"
|
|
log "forgejo-data restored."
|
|
else
|
|
log "No forgejo-data.tar in archive — skipping"
|
|
fi
|
|
|
|
# ── 6. Caddy TLS certificates ──────────────────────────────────────────────────
|
|
log "--- Caddy volume ---"
|
|
if [ -f "${WORK_DIR}/caddy-data.tar" ]; then
|
|
log "Importing caddy-data volume…"
|
|
podman volume exists caddy-data 2>/dev/null || podman volume create caddy-data
|
|
podman volume import caddy-data "${WORK_DIR}/caddy-data.tar"
|
|
log "caddy-data restored."
|
|
else
|
|
log "No caddy-data.tar in archive — skipping"
|
|
fi
|
|
|
|
log "=== Restore complete ==="
|
|
log "Bring the stack back up with:"
|
|
log " podman compose -f ${SCRIPT_DIR}/docker-compose.yml up -d"
|