From 0f2ec7c1b366f8423ef6f06f4dbaf2a186b82055 Mon Sep 17 00:00:00 2001 From: sindoring Date: Thu, 2 Apr 2026 21:47:39 +0400 Subject: [PATCH 1/6] feat(Core/Docker): production deploy configuration - docker-compose.prod.yml: MySQL tuning (innodb_buffer_pool=4G), resource limits, log rotation - scripts/prod-deploy.sh: production startup script mirroring start-server.sh sequence with pre-flight checks and sysctl tuning - conf/dist/env.production: production environment variable template Co-Authored-By: Claude Sonnet 4.6 --- conf/dist/env.production | 29 +++++ docker-compose.prod.yml | 45 ++++++++ scripts/prod-deploy.sh | 226 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 conf/dist/env.production create mode 100644 docker-compose.prod.yml create mode 100644 scripts/prod-deploy.sh diff --git a/conf/dist/env.production b/conf/dist/env.production new file mode 100644 index 0000000..7e8df6d --- /dev/null +++ b/conf/dist/env.production @@ -0,0 +1,29 @@ +# Production environment — copy to .env before deploying +# cp .env.production .env + +# Docker image tag (master = latest stable build) +DOCKER_IMAGE_TAG=master + +# Database root password — CHANGE THIS +DOCKER_DB_ROOT_PASSWORD=CHANGE_ME_STRONG_PASSWORD + +# External ports +DOCKER_WORLD_EXTERNAL_PORT=8085 +DOCKER_AUTH_EXTERNAL_PORT=3724 +DOCKER_SOAP_EXTERNAL_PORT=7878 +DOCKER_DB_EXTERNAL_PORT=3306 + +# Run server as this user (match host UID/GID to avoid volume permission issues) +# Run: id -u && id -g to get your values +DOCKER_USER=acore +DOCKER_USER_ID=1000 +DOCKER_GROUP_ID=1000 + +# Volume paths (use absolute paths in production) +DOCKER_VOL_ETC=./env/dist/etc +DOCKER_VOL_LOGS=./env/dist/logs +DOCKER_VOL_DATA=ac-client-data + +# Game rates (can also be set in worldserver.conf) +AC_RATE_XP_KILL=1 +ACORE_PLAYERBOTS_RANDOM_BOT_AUTOLOGIN=0 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..df79697 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,45 @@ +services: + ac-database: + command: > + --innodb_buffer_pool_size=4G + --innodb_log_file_size=512M + --innodb_flush_log_at_trx_commit=2 + --innodb_flush_method=O_DIRECT + --max_connections=300 + --query_cache_size=0 + --query_cache_type=0 + --slow_query_log=1 + --long_query_time=2 + deploy: + resources: + limits: + cpus: '2' + memory: 5G + + ac-worldserver: + deploy: + resources: + limits: + cpus: '6' + memory: 6G + ulimits: + nofile: + soft: 65536 + hard: 65536 + logging: + driver: "json-file" + options: + max-size: "100m" + max-file: "5" + + ac-authserver: + deploy: + resources: + limits: + cpus: '1' + memory: 512M + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "3" diff --git a/scripts/prod-deploy.sh b/scripts/prod-deploy.sh new file mode 100644 index 0000000..40a5ad4 --- /dev/null +++ b/scripts/prod-deploy.sh @@ -0,0 +1,226 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" +BUILD_FLAG="--build" + +# Production compose stack — layered on top of the standard override +COMPOSE="docker compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.prod.yml" + +# ── helpers ────────────────────────────────────────────────────────────────── + +log() { printf '[prod-deploy] %s\n' "$*"; } +die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } + +print_service_logs() { + local service="$1" + printf '\n[%s logs]\n' "$service" >&2 + $COMPOSE logs --tail=120 "$service" >&2 || true + printf '\n' >&2 +} + +wait_for_healthy() { + local container_name="$1" + local timeout_seconds="${2:-180}" + local start_ts now status + + start_ts="$(date +%s)" + while :; do + status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_name" 2>/dev/null || true)" + case "$status" in + healthy|running) return 0 ;; + unhealthy|exited|dead) return 1 ;; + esac + now="$(date +%s)" + if (( now - start_ts >= timeout_seconds )); then return 1; fi + sleep 2 + done +} + +run_one_shot_service() { + local service="$1" + local container_name="$2" + + if [[ -n "$BUILD_FLAG" ]]; then + if ! $COMPOSE up --build --no-deps "$service"; then + print_service_logs "$service" + printf '%s failed during startup\n' "$service" >&2 + exit 1 + fi + else + if ! $COMPOSE up --no-deps "$service"; then + print_service_logs "$service" + printf '%s failed during startup\n' "$service" >&2 + exit 1 + fi + fi + + local exit_code + exit_code="$(docker inspect -f '{{.State.ExitCode}}' "$container_name" 2>/dev/null || printf '1')" + if [[ "$exit_code" != "0" ]]; then + print_service_logs "$service" + printf '%s failed with exit code %s\n' "$service" "$exit_code" >&2 + exit 1 + fi +} + +require_docker() { + if ! command -v docker >/dev/null 2>&1; then + die "docker is not installed or not in PATH" + fi + if ! docker compose version >/dev/null 2>&1; then + die "docker compose plugin is not available. On Ubuntu: apt install docker-compose-plugin" + fi + if ! docker info >/dev/null 2>&1; then + die "docker daemon is not reachable. Run: sudo systemctl enable --now docker" + fi +} + +ensure_host_permissions() { + local target_uid="${DOCKER_USER_ID:-1000}" + local target_gid="${DOCKER_GROUP_ID:-1000}" + local dirs=( + "$ROOT_DIR/env/dist/etc" + "$ROOT_DIR/env/dist/logs" + "$ROOT_DIR/env/dist/temp" + ) + + mkdir -p "${dirs[@]}" + + if [[ "$(id -u)" -eq 0 ]]; then + chown -R "${target_uid}:${target_gid}" "${dirs[@]}" + return 0 + fi + + local dir + for dir in "${dirs[@]}"; do + if [[ ! -w "$dir" ]]; then + printf 'host directory is not writable: %s\n' "$dir" >&2 + printf 'Run: sudo chown -R %s:%s %q %q %q\n' \ + "$target_uid" "$target_gid" \ + "${dirs[0]}" "${dirs[1]}" "${dirs[2]}" >&2 + exit 1 + fi + done +} + +apply_sysctl() { + log "applying sysctl settings..." + sysctl -w net.core.somaxconn=4096 2>/dev/null || true + sysctl -w net.ipv4.tcp_max_syn_backlog=4096 2>/dev/null || true + sysctl -w fs.file-max=100000 2>/dev/null || true + # Prevent Docker conntrack table overflow (causes sudden disconnects) + sysctl -w net.netfilter.nf_conntrack_max=131072 2>/dev/null || true + sysctl -w net.ipv4.tcp_keepalive_time=120 2>/dev/null || true + sysctl -w net.ipv4.tcp_keepalive_intvl=30 2>/dev/null || true + sysctl -w net.ipv4.tcp_keepalive_probes=5 2>/dev/null || true +} + +# ── argument parsing ────────────────────────────────────────────────────────── + +usage() { + cat <&2; usage >&2; exit 1 ;; + esac + shift +done + +# ── pre-flight ──────────────────────────────────────────────────────────────── + +if [[ ! -f "$ENV_FILE" ]]; then + if [[ -f "$ROOT_DIR/.env.production" ]]; then + log ".env not found — copying from .env.production" + cp "$ROOT_DIR/.env.production" "$ENV_FILE" + die "Fill in $ENV_FILE (at minimum DOCKER_DB_ROOT_PASSWORD) and re-run." + else + die "$ENV_FILE not found. Create it based on .env.production." + fi +fi + +if grep -q "CHANGE_ME" "$ENV_FILE"; then + die "Change DOCKER_DB_ROOT_PASSWORD in $ENV_FILE before deploying to production." +fi + +# Source env so ensure_host_permissions picks up DOCKER_USER_ID / DOCKER_GROUP_ID +set -a +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a + +cd "$ROOT_DIR" + +require_docker + +if [[ $EUID -eq 0 ]]; then + apply_sysctl +else + log "not root — skipping sysctl (run with sudo to apply kernel settings)" +fi + +# ── startup sequence (mirrors start-server.sh) ─────────────────────────────── + +log "applying module configuration" +bash "$ROOT_DIR/setup-modules.sh" + +log "ensuring host directory permissions" +ensure_host_permissions + +log "starting database" +if [[ -n "$BUILD_FLAG" ]]; then + $COMPOSE up -d --build ac-database +else + $COMPOSE up -d ac-database +fi + +if ! wait_for_healthy "ac-database" 180; then + print_service_logs "ac-database" + die "ac-database did not become healthy" +fi + +log "running database import" +run_one_shot_service "ac-db-import" "ac-db-import" + +log "importing custom SQL" +bash "$ROOT_DIR/import-custom-sql.sh" + +log "applying post-database module setup" +bash "$ROOT_DIR/setup-modules.sh" + +log "initializing client data" +run_one_shot_service "ac-client-data-init" "ac-client-data-init" + +log "starting authserver and worldserver" +if [[ -n "$BUILD_FLAG" ]]; then + $COMPOSE up -d --build ac-authserver ac-worldserver +else + $COMPOSE up -d ac-authserver ac-worldserver +fi + +log "synchronizing realmlist" +bash "$ROOT_DIR/sync-realmlist.sh" + +log "current service status" +$COMPOSE ps + +printf '\n' +printf 'Realm: %s:%s\n' "${ACORE_REALMLIST_PUBLIC_ADDRESS:-127.0.0.1}" "${ACORE_REALMLIST_PORT:-${DOCKER_WORLD_EXTERNAL_PORT:-8085}}" +printf 'World: 127.0.0.1:%s\n' "${DOCKER_WORLD_EXTERNAL_PORT:-8085}" +printf 'Auth: 127.0.0.1:%s\n' "${DOCKER_AUTH_EXTERNAL_PORT:-3724}" +printf 'SOAP: 127.0.0.1:%s\n' "${DOCKER_SOAP_EXTERNAL_PORT:-7878}" +printf 'Account: ./create-account.sh admin your_password\n' +printf 'Logs: %s logs -f ac-worldserver ac-authserver\n' "$COMPOSE" From 83262368ac19895528ee1b911db08161711e9ffb Mon Sep 17 00:00:00 2001 From: sindoring Date: Thu, 2 Apr 2026 22:28:07 +0400 Subject: [PATCH 2/6] 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" From 119b2a500bb9fa5261635a605e5decc1965fc02c Mon Sep 17 00:00:00 2001 From: sindoring Date: Thu, 2 Apr 2026 22:46:02 +0400 Subject: [PATCH 3/6] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D1=80=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/db-import.sh | 91 +++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 61 deletions(-) diff --git a/scripts/db-import.sh b/scripts/db-import.sh index 8064dcb..f794a35 100644 --- a/scripts/db-import.sh +++ b/scripts/db-import.sh @@ -5,10 +5,6 @@ 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 @@ -19,62 +15,47 @@ if [[ -f "$ENV_FILE" ]]; then 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 \ - "$@" +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 +} + +mysql_exec() { + docker compose exec -T ac-database bash -lc \ + "mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\"" } # ── 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 ;; + --from) DUMP_FILE="$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 @@ -84,11 +65,6 @@ done [[ -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" @@ -97,9 +73,8 @@ cd "$ROOT_DIR" 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" +printf ' Source : %s (%s)\n' "$DUMP_FILE" "$file_size" +printf ' Target : ac-database container\n\n' if [[ "$DRY_RUN" -eq 1 ]]; then log "dry-run mode — nothing imported" @@ -112,27 +87,21 @@ if [[ "$FORCE" -eq 0 ]]; then [[ "$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}..." +require_running_db + +log "restoring $DUMP_FILE into ac-database..." log "this may take several minutes..." if [[ "$DUMP_FILE" == *.gz ]]; then - zcat "$DUMP_FILE" | mysql_cmd + zcat "$DUMP_FILE" | mysql_exec else - mysql_cmd < "$DUMP_FILE" + mysql_exec < "$DUMP_FILE" fi log "import complete" log "" -log "next steps on the new server:" -log " 1. Update realmlist: bash scripts/sync-realmlist.sh" +log "next steps:" +log " 1. Update realmlist: bash sync-realmlist.sh" log " 2. Start the server: bash scripts/prod-deploy.sh --no-build" From b608754d36f15e0983cf8f5ead8c390d58e6d761 Mon Sep 17 00:00:00 2001 From: sindoring Date: Thu, 2 Apr 2026 22:48:40 +0400 Subject: [PATCH 4/6] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BD=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=B1=D0=B0=D0=B7=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.prod.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index df79697..2518cbb 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -2,12 +2,10 @@ services: ac-database: command: > --innodb_buffer_pool_size=4G - --innodb_log_file_size=512M + --innodb_redo_log_capacity=1G --innodb_flush_log_at_trx_commit=2 --innodb_flush_method=O_DIRECT --max_connections=300 - --query_cache_size=0 - --query_cache_type=0 --slow_query_log=1 --long_query_time=2 deploy: From c9b1100e3a5a4f580c9dd014abb15f7dd43265ce Mon Sep 17 00:00:00 2001 From: sindoring Date: Thu, 2 Apr 2026 22:52:48 +0400 Subject: [PATCH 5/6] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D1=80=D1=82=D0=B0=20=D0=B1=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/db-import.sh | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/db-import.sh b/scripts/db-import.sh index f794a35..a307360 100644 --- a/scripts/db-import.sh +++ b/scripts/db-import.sh @@ -37,15 +37,28 @@ EOF log() { printf '[db-import] %s\n' "$*"; } die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } -require_running_db() { +require_ready_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 + + log "waiting for MySQL to be ready..." + local attempts=0 + until docker compose exec -T ac-database bash -lc \ + "mysql -h 127.0.0.1 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" -e 'SELECT 1;'" \ + >/dev/null 2>&1; do + attempts=$((attempts + 1)) + if [[ $attempts -ge 30 ]]; then + die "MySQL did not become ready in time. Check: docker logs ac-database" + fi + sleep 2 + done + log "MySQL is ready" } mysql_exec() { docker compose exec -T ac-database bash -lc \ - "mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\"" + "mysql -h 127.0.0.1 -uroot -p\"\$MYSQL_ROOT_PASSWORD\"" } # ── argument parsing ────────────────────────────────────────────────────────── @@ -89,7 +102,7 @@ fi # ── import ──────────────────────────────────────────────────────────────────── -require_running_db +require_ready_db log "restoring $DUMP_FILE into ac-database..." log "this may take several minutes..." From 0a3d3b6afafd45a369326747d9658d01d2ed43f6 Mon Sep 17 00:00:00 2001 From: sindoring Date: Fri, 3 Apr 2026 00:10:19 +0400 Subject: [PATCH 6/6] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20db-export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/db-export.sh | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/scripts/db-export.sh b/scripts/db-export.sh index 1159b9c..c245ef2 100644 --- a/scripts/db-export.sh +++ b/scripts/db-export.sh @@ -43,6 +43,15 @@ require_running_db() { fi } +# Returns space-separated list of user databases (excludes MySQL system schemas) +get_user_databases() { + docker compose exec -T ac-database bash -lc \ + "mysql -h 127.0.0.1 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" -N -s information_schema" \ + <<<"SELECT schema_name FROM schemata + WHERE schema_name NOT IN ('mysql','information_schema','performance_schema','sys') + ORDER BY schema_name;" +} + # ── argument parsing ────────────────────────────────────────────────────────── while (($#)); do @@ -73,13 +82,17 @@ fi mkdir -p "$(dirname "$OUTPUT_FILE")" -log "exporting all databases → $OUTPUT_FILE" +log "discovering user databases..." +databases="$(get_user_databases | tr '\n' ' ' | xargs)" +[[ -z "$databases" ]] && die "no user databases found" +log "databases: $databases" +log "exporting → $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 \ + "mysqldump -h 127.0.0.1 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \ + --databases $databases \ --add-drop-database \ --single-transaction \ --quick \ @@ -89,8 +102,8 @@ if [[ "$COMPRESS" -eq 1 ]]; then | gzip -6 > "$OUTPUT_FILE" else docker compose exec -T ac-database bash -lc \ - "mysqldump -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \ - --all-databases \ + "mysqldump -h 127.0.0.1 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \ + --databases $databases \ --add-drop-database \ --single-transaction \ --quick \