#!/usr/bin/env bash # backup-db.sh — dump the production MySQL (jiu_db) from the production host # and keep a gzip snapshot on the mac (host) runner's local disk # (~/jiu-db-backups), retaining the last 30 days. The DB password is read on # the host from production.env's DATABASE_DSN, so no DB-password secret is # needed. # # Target host (post-cutover 2026-07-03: Ali is the primary): pass # DEPLOY_SSH_KEY / DEPLOY_HOST / DEPLOY_USER (falls back to EC2_* legacy vars). set -euo pipefail # shellcheck source=scripts/ci/lib-forgejo.sh . "$(dirname "$0")/lib-forgejo.sh" BACKUP_DIR="${HOME}/jiu-db-backups" mkdir -p "${BACKUP_DIR}" FILENAME="jiu_db_$(date +%Y%m%d_%H%M%S).sql.gz" DEST="${BACKUP_DIR}/${FILENAME}" TARGET_HOST="${DEPLOY_HOST:-$EC2_HOST}" TARGET_USER="${DEPLOY_USER:-$EC2_USER}" setup_ssh trap teardown_ssh EXIT echo "==> backup-db: dumping jiu_db from ${TARGET_HOST}" # On the host: parse the DSN password from production.env, then mysqldump the # container to stdout; stream back over ssh and gzip locally. The heredoc is # single-quoted, so it runs verbatim on the host (no local expansion). ${SSH} carries # no -t, keeping stdout a clean dump stream. ${SSH} "${TARGET_USER}@${TARGET_HOST}" 'bash -s' <<'ENDSSH' | gzip > "${DEST}" set -euo pipefail PW=$(python3 -c 'import re;e=open("/opt/jiu/config/production.env").read();d=re.search(r"DATABASE_DSN=(.*)",e).group(1).strip().strip(chr(34)).strip(chr(39));print(re.match(r"[^:]+:([^@]+)@",d).group(1))') exec docker exec -e MYSQL_PWD="${PW}" jiu_mysql mysqldump -uroot --single-transaction --no-tablespaces jiu_db ENDSSH # Integrity + sanity + retention. gzip -t "${DEST}" SIZE=$(stat -f%z "${DEST}" 2>/dev/null || stat -c%s "${DEST}") if [ "${SIZE}" -lt 100000 ]; then echo "==> backup-db: dump suspiciously small (${SIZE} bytes), aborting" >&2 rm -f "${DEST}" exit 1 fi find "${BACKUP_DIR}" -name '*.sql.gz' -mtime +30 -delete echo "==> backup-db: saved ${DEST} (${SIZE} bytes)"