From 83262368ac19895528ee1b911db08161711e9ffb Mon Sep 17 00:00:00 2001 From: sindoring Date: Thu, 2 Apr 2026 22:28:07 +0400 Subject: [PATCH] backups --- scripts/db-export.sh | 110 ++++++++++++++++++++++++++++++++++ scripts/db-import.sh | 138 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 scripts/db-export.sh create mode 100644 scripts/db-import.sh diff --git a/scripts/db-export.sh b/scripts/db-export.sh new file mode 100644 index 0000000..1159b9c --- /dev/null +++ b/scripts/db-export.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" +OUTPUT_FILE="" +COMPRESS=1 + +if [[ -f "$ENV_FILE" ]]; then + set -a + # shellcheck disable=SC1090 + source "$ENV_FILE" + set +a +fi + +usage() { + cat <&2; exit 1; } + +require_running_db() { + if ! docker compose ps --status running --services 2>/dev/null | grep -qx 'ac-database'; then + die "ac-database is not running. Start it with: docker compose up -d ac-database" + fi +} + +# ── argument parsing ────────────────────────────────────────────────────────── + +while (($#)); do + case "$1" in + --output) OUTPUT_FILE="$2"; shift ;; + --no-compress) COMPRESS=0 ;; + -h|--help) usage; exit 0 ;; + *) printf 'Unknown option: %s\n\n' "$1" >&2; usage >&2; exit 1 ;; + esac + shift +done + +# ── main ────────────────────────────────────────────────────────────────────── + +cd "$ROOT_DIR" + +require_running_db + +if [[ -z "$OUTPUT_FILE" ]]; then + mkdir -p "$ROOT_DIR/backups" + timestamp="$(date '+%Y-%m-%d_%H-%M-%S')" + if [[ "$COMPRESS" -eq 1 ]]; then + OUTPUT_FILE="$ROOT_DIR/backups/${timestamp}.sql.gz" + else + OUTPUT_FILE="$ROOT_DIR/backups/${timestamp}.sql" + fi +fi + +mkdir -p "$(dirname "$OUTPUT_FILE")" + +log "exporting all databases → $OUTPUT_FILE" +log "this may take a few minutes for large worlds..." + +if [[ "$COMPRESS" -eq 1 ]]; then + docker compose exec -T ac-database bash -lc \ + "mysqldump -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \ + --all-databases \ + --add-drop-database \ + --single-transaction \ + --quick \ + --routines \ + --triggers \ + --events" \ + | gzip -6 > "$OUTPUT_FILE" +else + docker compose exec -T ac-database bash -lc \ + "mysqldump -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \ + --all-databases \ + --add-drop-database \ + --single-transaction \ + --quick \ + --routines \ + --triggers \ + --events" \ + > "$OUTPUT_FILE" +fi + +log "done: $OUTPUT_FILE ($(du -sh "$OUTPUT_FILE" | cut -f1))" +log "" +log "to restore on the new server:" +if [[ "$OUTPUT_FILE" == *.gz ]]; then + log " bash scripts/db-import.sh --from $OUTPUT_FILE" +else + log " bash scripts/db-import.sh --from $OUTPUT_FILE" +fi diff --git a/scripts/db-import.sh b/scripts/db-import.sh new file mode 100644 index 0000000..8064dcb --- /dev/null +++ b/scripts/db-import.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" +DUMP_FILE="" +TARGET_HOST="127.0.0.1" +TARGET_PORT="3306" +TARGET_USER="root" +TARGET_PASSWORD="" +DRY_RUN=0 +FORCE=0 + +if [[ -f "$ENV_FILE" ]]; then + set -a + # shellcheck disable=SC1090 + source "$ENV_FILE" + set +a +fi + +# Use password from .env if not overridden +TARGET_PASSWORD="${TARGET_PASSWORD:-${DOCKER_DB_ROOT_PASSWORD:-}}" + +usage() { + cat <&2; exit 1; } + +mysql_cmd() { + mysql \ + --host="$TARGET_HOST" \ + --port="$TARGET_PORT" \ + --user="$TARGET_USER" \ + --password="$TARGET_PASSWORD" \ + --connect-timeout=10 \ + "$@" +} + +# ── argument parsing ────────────────────────────────────────────────────────── + +while (($#)); do + case "$1" in + --from) DUMP_FILE="$2"; shift ;; + --host) TARGET_HOST="$2"; shift ;; + --port) TARGET_PORT="$2"; shift ;; + --user) TARGET_USER="$2"; shift ;; + --password) TARGET_PASSWORD="$2"; shift ;; + --dry-run) DRY_RUN=1 ;; + --force) FORCE=1 ;; + -h|--help) usage; exit 0 ;; + *) printf 'Unknown option: %s\n\n' "$1" >&2; usage >&2; exit 1 ;; + esac + shift +done + +# ── validation ──────────────────────────────────────────────────────────────── + +[[ -z "$DUMP_FILE" ]] && { usage >&2; die "--from FILE is required"; } +[[ -f "$DUMP_FILE" ]] || die "file not found: $DUMP_FILE" +[[ -z "$TARGET_PASSWORD" ]] && die "no password set — use --password or set DOCKER_DB_ROOT_PASSWORD in .env" + +if ! command -v mysql >/dev/null 2>&1; then + die "'mysql' client not found. Install it: apt install mysql-client or brew install mysql-client" +fi + +cd "$ROOT_DIR" + +# ── preview ─────────────────────────────────────────────────────────────────── + +file_size="$(du -sh "$DUMP_FILE" | cut -f1)" + +printf '\nMigration plan:\n' +printf ' Source file : %s (%s)\n' "$DUMP_FILE" "$file_size" +printf ' Target host : %s:%s\n' "$TARGET_HOST" "$TARGET_PORT" +printf ' Target user : %s\n\n' "$TARGET_USER" + +if [[ "$DRY_RUN" -eq 1 ]]; then + log "dry-run mode — nothing imported" + exit 0 +fi + +if [[ "$FORCE" -eq 0 ]]; then + printf 'WARNING: this will DROP and recreate all databases in the dump.\n' + read -r -p "Continue? [y/N] " confirm + [[ "$confirm" =~ ^[Yy]$ ]] || { log "aborted"; exit 0; } +fi + +# ── connectivity check ──────────────────────────────────────────────────────── + +log "checking connection to ${TARGET_HOST}:${TARGET_PORT}..." +if ! mysql_cmd --execute="SELECT 1;" >/dev/null 2>&1; then + die "cannot connect to MySQL at ${TARGET_HOST}:${TARGET_PORT} as ${TARGET_USER}" +fi +log "connection OK" + +# ── import ──────────────────────────────────────────────────────────────────── + +log "restoring $DUMP_FILE → ${TARGET_HOST}:${TARGET_PORT}..." +log "this may take several minutes..." + +if [[ "$DUMP_FILE" == *.gz ]]; then + zcat "$DUMP_FILE" | mysql_cmd +else + mysql_cmd < "$DUMP_FILE" +fi + +log "import complete" +log "" +log "next steps on the new server:" +log " 1. Update realmlist: bash scripts/sync-realmlist.sh" +log " 2. Start the server: bash scripts/prod-deploy.sh --no-build"