10 Commits

Author SHA1 Message Date
sindoring 0a3d3b6afa доработка db-export 2026-04-03 00:10:19 +04:00
sindoring c9b1100e3a фикс импорта бд 2026-04-02 22:52:48 +04:00
sindoring b608754d36 фикс настройки прод базы 2026-04-02 22:48:40 +04:00
sindoring 119b2a500b фикс импорта 2026-04-02 22:46:02 +04:00
sindoring 83262368ac backups 2026-04-02 22:28:07 +04:00
sindoring 0f2ec7c1b3 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 <noreply@anthropic.com>
2026-04-02 21:47:39 +04:00
Maxim Kalistratov b0d62064ba Merge pull request #1 from sindoring/mod-progressive-quest-xp-config
множители опыта от квестов с шагом в 10 уровней
2026-03-21 17:47:54 +04:00
sindoring ba63751269 множители опыта от квестов с шагом в 10 уровней 2026-03-21 16:29:01 +04:00
sindoring dc835fa9f8 эльфы крови и дренеи создаются без учета individual progress 2026-03-21 15:53:01 +04:00
sindoring d03e96cc9b AGENTS.md 2026-03-21 11:21:14 +04:00
12 changed files with 1136 additions and 1 deletions
+29 -1
View File
@@ -261,7 +261,7 @@ ACORE_PROGRESSION_LIMIT=0
ACORE_PROGRESSION_ENFORCE_GROUP_RULES=1 ACORE_PROGRESSION_ENFORCE_GROUP_RULES=1
# На какой стадии открываются расы TBC. # На какой стадии открываются расы TBC.
ACORE_PROGRESSION_TBC_RACES_UNLOCK=8 ACORE_PROGRESSION_TBC_RACES_UNLOCK=0
# На какой стадии открывается создание Death Knight. # На какой стадии открывается создание Death Knight.
ACORE_PROGRESSION_DK_UNLOCK=13 ACORE_PROGRESSION_DK_UNLOCK=13
@@ -276,6 +276,34 @@ ACORE_PROGRESSION_MC_AQUAL_COOLDOWN_REDUCTION=60
# 0 = отключены, 1 = включены. # 0 = отключены, 1 = включены.
ACORE_PROGRESSION_PVP_GEAR_REQUIREMENTS=0 ACORE_PROGRESSION_PVP_GEAR_REQUIREMENTS=0
# ------------------------------------------------------------------------------
# mod-progressive-quest-xp
# ------------------------------------------------------------------------------
# 1 = включает прогрессивный множитель XP за квесты.
ACORE_DYNAMIC_QUEST_XP_ENABLE=1
# Множитель квестового XP для уровней 1-20.
ACORE_DYNAMIC_QUEST_XP_LEVEL_1_20=1.5
# Множитель квестового XP для уровней 21-30.
ACORE_DYNAMIC_QUEST_XP_LEVEL_21_30=1.3
# Множитель квестового XP для уровней 31-40.
ACORE_DYNAMIC_QUEST_XP_LEVEL_31_40=1.1
# Множитель квестового XP для уровней 41-50.
ACORE_DYNAMIC_QUEST_XP_LEVEL_41_50=1.0
# Множитель квестового XP для уровней 51-60.
ACORE_DYNAMIC_QUEST_XP_LEVEL_51_60=1.0
# Множитель квестового XP для уровней 61-70.
ACORE_DYNAMIC_QUEST_XP_LEVEL_61_70=1.0
# Множитель квестового XP для уровней 70-80.
ACORE_DYNAMIC_QUEST_XP_LEVEL_70_PLUS=1.0
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# mod-aoe-loot # mod-aoe-loot
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
+401
View File
@@ -0,0 +1,401 @@
# AGENTS.md
Project-specific instructions for coding agents working in this repository.
## Scope
These instructions apply to the whole repository rooted at this directory.
## Project Shape
- This repo is an AzerothCore 3.3.5a server with local custom deployment glue.
- It is run primarily through Docker Compose, not through direct host binaries.
- The important local customizations live in:
- `.env` and `.env.example`
- `setup-modules.sh`
- `start-server.sh`
- `create-account.sh`
- `sync-realmlist.sh`
- `import-custom-sql.sh`
- `lua_scripts/`
- `sql/`
- vendored modules in `modules/`
## Source Of Truth
- Treat `.env` plus the helper scripts as the source of truth for deployment behavior.
- Do not hand-edit `env/dist/etc/*` as a permanent fix. Those files are generated runtime config.
- If a change must persist, make it in `.env`, `.env.example`, or the generating shell script.
- Keep `.env.example` aligned with `.env` defaults when changing deployment settings.
## Generated And Ignored Paths
- `env/dist/*` is generated and gitignored.
- `var/*` is generated and gitignored.
- `.env` is local and gitignored.
- `src/server/scripts/Custom/*` is ignored except for:
- `src/server/scripts/Custom/README.md`
- `src/server/scripts/Custom/custom_script_loader.cpp`
- Do not rely on generated files being committed.
## Docker Workflow
Prefer the local wrapper scripts over raw compose commands when they already encode repo-specific logic.
### Primary commands
- Full startup:
- `./start-server.sh`
- `./start-server.sh --no-build`
- `./start-server.sh --logs`
- Regenerate module and world configs:
- `./setup-modules.sh`
- `./setup-modules.sh --dry-run`
- Sync realm address and gamebuild into auth DB:
- `./sync-realmlist.sh`
- Create or update an account:
- `./create-account.sh <username> <password> --gmlevel 0`
- Import custom SQL that is outside the normal module update path:
- `./import-custom-sql.sh`
### Useful raw compose commands
- `docker compose ps`
- `docker compose logs -f ac-worldserver ac-authserver`
- `docker compose stop`
- `docker compose down`
## Compose And Runtime Conventions
- `docker-compose.yml` is upstream-style base config. Prefer local changes in `docker-compose.override.yml`.
- Service names used by local scripts:
- `ac-database`
- `ac-db-import`
- `ac-client-data-init`
- `ac-authserver`
- `ac-worldserver`
- The deployment expects Docker Compose v2 (`docker compose`, not `docker-compose`).
## Config Layering Rules
### World config
- `setup-modules.sh` manages selected keys in `env/dist/etc/worldserver.conf`.
- If you need a persistent world config change, add it to `setup-modules.sh` and usually expose it through `.env`.
### Module config
- This repo must write effective module overrides to `env/dist/etc/modules/*.conf.dist`.
- Do not introduce a parallel `*.conf` override layer for modules here.
- AzerothCore loads module config files from the compiled module config list, and in this repo that list resolves to `*.conf.dist`.
- If you change module defaults, update the corresponding writer function in `setup-modules.sh`.
### Playerbots
- `mod-playerbots` is supported here, but it is a fragile area.
- Keep the repo default conservative:
- no random bot autologin
- no random bot startup population
- small addclass pool
- Do not accidentally reintroduce the old broken pattern where only `playerbots.conf` was written.
- Be careful with Docker environment overrides that map to `AiPlayerbot.*`; they can silently override file config.
## Active Module Map
Use this as the fast path before opening upstream module READMEs.
### `mod-autobalance`
- Purpose:
- scales dungeon and raid mobs for solo, duo, and undersized groups
- Source config:
- `modules/mod-autobalance/conf/AutoBalance.conf.dist`
- Effective generated config:
- `env/dist/etc/modules/AutoBalance.conf.dist`
- Managed by:
- `setup-modules.sh`
- Current repo intent:
- solo-friendly PvE tuning
- `MinPlayers = 1` for dungeons and raids
- softened raid boss health and damage for incomplete groups
- Useful diagnostics:
- `.ab mapstat`
- `.ab creaturestat`
### `mod-individual-progression`
- Purpose:
- per-character expansion and tier progression on a WotLK client
- Source config:
- `modules/mod-individual-progression/conf/individualProgression.conf.dist`
- Effective generated config:
- `env/dist/etc/modules/individualProgression.conf.dist`
- Managed by:
- `setup-modules.sh`
- Hard prerequisites already encoded in this repo:
- `EnablePlayerSettings = 1`
- `DBC.EnforceItemAttributes = 0`
- Important behavior:
- touches visibility, quest flow, content gating, item stats, restored legacy content, and progression unlocks
- Caveat:
- optional SQL under this module is not automatically imported unless explicitly wired in
### `mod-playerbots`
- Purpose:
- addclass bots, alt bots, and optional random world bots
- Source config:
- `modules/mod-playerbots/conf/playerbots.conf.dist`
- Effective generated config:
- `env/dist/etc/modules/playerbots.conf.dist`
- Managed by:
- `setup-modules.sh`
- Separate database:
- `acore_playerbots`
- Repo-specific caveat:
- upstream documents a custom `Playerbot` AzerothCore branch, but this repo carries local compatibility work on top of the current tree
- treat changes here as high risk
- Current repo intent:
- playerbots available for solo/small-group support
- random bot population disabled by default
- small addclass pool
- Fragile points:
- Docker environment overrides can silently beat file config
- avoid reintroducing large random bot defaults
- if startup crashes occur around bot creation, inspect effective `playerbots.conf.dist` first
### `mod-eluna`
- Purpose:
- Lua scripting engine used by local custom packages in `lua_scripts/`
- Source config:
- `modules/mod-eluna/conf/mod_eluna.conf.dist`
- Effective generated config:
- `env/dist/etc/modules/mod_eluna.conf.dist`
- Managed by:
- `setup-modules.sh`
- Important note:
- this repo uses AzerothCore `mod-eluna`, not original Eluna compatibility mode
- do not assume third-party Eluna scripts are portable
### `mod-aoe-loot`
- Purpose:
- area looting QoL
- Source config:
- `modules/mod-aoe-loot/conf/mod_aoe_loot.conf.dist`
- Effective generated config:
- `env/dist/etc/modules/mod_aoe_loot.conf.dist`
- Managed by:
- `setup-modules.sh`
- Extra SQL:
- module strings are imported by `import-custom-sql.sh`
- Caveat:
- if `module_string` rows are missing, world logs show `Module string module mod-aoe-loot id 1 not found in DB`
### `mod-quest-loot-party`
- Purpose:
- grouped quest-loot quality of life
- Source config:
- `modules/mod-quest-loot-party/conf/mod-quest-loot-party.conf.dist`
- Effective generated config:
- `env/dist/etc/modules/mod-quest-loot-party.conf.dist`
- Managed by:
- `setup-modules.sh`
### `mod-learnspells`
- Purpose:
- auto-grants class spells, talents, proficiencies, and optional riding/quest spells
- Source config:
- `modules/mod-learnspells/conf/mod_learnspells.conf.dist`
- Effective generated config:
- `env/dist/etc/modules/mod_learnspells.conf.dist`
- Managed by:
- `setup-modules.sh`
- Current repo intent:
- class/proficiency/quest spell automation enabled
- riding automation deliberately conservative in the local `.env`
## Local Lua Package Map
These packages are part of the active customization layer and are more important than generic upstream documentation.
### `lua_scripts/AIO_Server`
- Purpose:
- shared AIO transport/UI layer for Lua client-server addons
- Consumers in this repo:
- `MythicPlus`
- `Store_System`
- If AIO breaks, those packages effectively break too
### `lua_scripts/MythicPlus`
- Purpose:
- Mythic+ gameplay, scoring, keys, affixes, vault, AIO UI
- Data dependencies:
- character tables:
- `character_mythic_history`
- `character_mythic_keys`
- `character_mythic_rating`
- `character_mythic_vault`
- `character_mythic_weekly_affixes`
- world tables:
- `world_mythic_loot`
- `world_vault_loot`
- Bootstrap:
- handled by `import-custom-sql.sh`
- Extra world bootstrap:
- repairs creature/item/gameobject entries around `900001`, `900100`, `900000`
- Caveat:
- missing bootstrap often shows up as `Couldn't find a creature with (ID: 900001)!`
- empty `world_vault_loot` is non-fatal but means no vault rewards
### `lua_scripts/Store_System`
- Purpose:
- AIO-based in-game store UI and purchase handling
- Data dependencies:
- separate MySQL schema `store`
- Bootstrap:
- handled by `import-custom-sql.sh`
- imports `sql/Store.sql`
- Caveat:
- if `store` database is missing, Lua throws SQL errors and the store becomes unusable
### `lua_scripts/StartZoneRaceTeleporters`
- Purpose:
- race-specific start-zone teleporter NPCs
- Files:
- Lua logic:
- `lua_scripts/StartZoneRaceTeleporters/start_zone_race_teleporters.lua`
- base SQL:
- `lua_scripts/StartZoneRaceTeleporters/sql/world/start_zone_race_teleporters.sql`
- Bootstrap:
- imported automatically by `setup-modules.sh` when `ac-database` is already running
- Caveat:
- base SQL does not create all world spawns automatically
- example/manual spawn SQL still needs to be added per start zone
### `lua_scripts/Battlepass`
- Current state:
- present in tree but effectively empty
- Guidance:
- do not assume it is an active, wired gameplay system until code and bootstrap are actually added
### `lua_scripts/hello_world.lua`
- Current state:
- simple local script/example
- Guidance:
- low-risk when testing Eluna loading, not a core gameplay system
## Operational Feature Map
This repo is not just “AzerothCore plus random modules”. The intended gameplay stack is:
- solo/small-group PvE:
- `mod-autobalance`
- softened elite HP/damage in `worldserver.conf`
- progression playthrough:
- `mod-individual-progression`
- optional bot-assisted party filling:
- `mod-playerbots`
- QoL:
- `mod-aoe-loot`
- `mod-quest-loot-party`
- `mod-learnspells`
- custom Lua systems:
- `MythicPlus`
- `Store_System`
- `StartZoneRaceTeleporters`
When making a change, identify which layer you are actually modifying:
- core C++ behavior
- module config generation
- custom SQL bootstrap
- Eluna Lua logic
- Docker/deployment behavior
## Custom SQL Rules
- Normal AzerothCore/module SQL is handled by `ac-db-import`.
- This repo also has custom SQL outside standard module paths. That logic lives in `import-custom-sql.sh`.
- At the time of writing, this includes bootstrap/import logic for:
- MythicPlus SQL under `sql/Mythic+/`
- Store SQL under `sql/Store.sql`
- `mod-aoe-loot` module strings
- If you add a new Lua package or custom SQL tree outside the standard module import layout, extend `import-custom-sql.sh`.
## Local Lua And Custom Content
- `lua_scripts/` contains active server-side custom Lua packages.
- Some Lua features depend on SQL bootstrap outside the normal module importer.
- If you add or rename a Lua package that requires DB state, update both:
- the file tree
- `import-custom-sql.sh`
## Build And Edit Guidance
- Do not rebuild the whole core unless the change actually requires it.
- For shell-script changes, validate with:
- `bash -n <script>`
- For compose changes, validate with:
- `docker compose config`
- For config-generation changes, validate by running:
- `./setup-modules.sh --dry-run`
- then `./setup-modules.sh`
- If you change realm-related settings, run:
- `./sync-realmlist.sh`
- If you change custom SQL bootstrap, run:
- `./import-custom-sql.sh`
- with `ac-database` already running
## C++ And Core Notes
- This is still a normal AzerothCore source tree under `src/`.
- `authserver` and `worldserver` are the two runtime executables.
- `modules/` is vendored into this repo. Do not treat it as git submodules.
- Keep `src/server/scripts/Custom/custom_script_loader.cpp` present and tracked; removing it breaks linking of `AddCustomScripts()`.
## What To Change For Common Tasks
### Change deployment defaults
- Edit `.env.example`
- If the repo default should also change for the current working copy, edit `.env`
- Update any helper script that derives behavior from those variables
### Change module behavior
- Edit `setup-modules.sh`
- Regenerate configs with `./setup-modules.sh`
### Change auth/world exposure or realm address
- Edit `.env`
- Run `./sync-realmlist.sh`
### Change account bootstrap behavior
- Edit `create-account.sh`
- Keep SRP/account logic self-contained there
### Change first-run database bootstrap
- Edit `import-custom-sql.sh`
- Keep imports idempotent
## Practical Safety Notes
- Prefer additive, idempotent changes in shell scripts.
- Preserve current local server assumptions unless the task explicitly changes them:
- solo/small-group friendly tuning
- generated config flow
- Docker-first operation
- If a fix only works by editing generated files under `env/dist`, it is incomplete.
+29
View File
@@ -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
+43
View File
@@ -0,0 +1,43 @@
services:
ac-database:
command: >
--innodb_buffer_pool_size=4G
--innodb_redo_log_capacity=1G
--innodb_flush_log_at_trx_commit=2
--innodb_flush_method=O_DIRECT
--max_connections=300
--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"
@@ -0,0 +1,72 @@
#
# Progressive Quest XP module configuration
#
########################################
# DynamicQuestXP settings
########################################
#
# DynamicQuestXP.Enable
# Description: Enable progressive quest XP multiplier
# Default: 1 - (Enabled)
# 0 - (Disabled)
#
DynamicQuestXP.Enable = 1
#
# DynamicQuestXP.Level_1_20
# Description: XP multiplier for quest rewards at levels 1-20
# Default: 1.5
#
DynamicQuestXP.Level_1_20 = 1.5
#
# DynamicQuestXP.Level_21_30
# Description: XP multiplier for quest rewards at levels 21-30
# Default: 1.3
#
DynamicQuestXP.Level_21_30 = 1.3
#
# DynamicQuestXP.Level_31_40
# Description: XP multiplier for quest rewards at levels 31-40
# Default: 1.1
#
DynamicQuestXP.Level_31_40 = 1.1
#
# DynamicQuestXP.Level_41_50
# Description: XP multiplier for quest rewards at levels 41-50
# Default: 1.0
#
DynamicQuestXP.Level_41_50 = 1.0
#
# DynamicQuestXP.Level_51_60
# Description: XP multiplier for quest rewards at levels 51-60
# Default: 1.0
#
DynamicQuestXP.Level_51_60 = 1.0
#
# DynamicQuestXP.Level_61_70
# Description: XP multiplier for quest rewards at levels 61-70
# Default: 1.0
#
DynamicQuestXP.Level_61_70 = 1.0
#
# DynamicQuestXP.Level_70_plus
# Description: XP multiplier for quest rewards at levels 70-80
# Default: 1.0
#
DynamicQuestXP.Level_70_plus = 1.0
@@ -0,0 +1 @@
@@ -0,0 +1,59 @@
/*
* Progressive Quest XP Module for AzerothCore
*
* Applies a level-based XP multiplier to quest rewards only.
* Does not affect kill XP, exploration XP, or PvP XP.
*/
#include "Config.h"
#include "Player.h"
#include "ScriptMgr.h"
class ProgressiveQuestXPPlayer : public PlayerScript
{
public:
ProgressiveQuestXPPlayer() : PlayerScript("ProgressiveQuestXPPlayer",
{PLAYERHOOK_ON_QUEST_COMPUTE_EXP}) { }
void OnPlayerQuestComputeXP(Player* player, Quest const* /*quest*/,
uint32& xpValue) override
{
if (!sConfigMgr->GetOption<bool>("DynamicQuestXP.Enable", true))
return;
if (!xpValue)
return;
uint8 level = player->GetLevel();
float multiplier;
if (level <= 20)
multiplier = sConfigMgr->GetOption<float>(
"DynamicQuestXP.Level_1_20", 1.5f);
else if (level <= 30)
multiplier = sConfigMgr->GetOption<float>(
"DynamicQuestXP.Level_21_30", 1.3f);
else if (level <= 40)
multiplier = sConfigMgr->GetOption<float>(
"DynamicQuestXP.Level_31_40", 1.1f);
else if (level <= 50)
multiplier = sConfigMgr->GetOption<float>(
"DynamicQuestXP.Level_41_50", 1.0f);
else if (level <= 60)
multiplier = sConfigMgr->GetOption<float>(
"DynamicQuestXP.Level_51_60", 1.0f);
else if (level <= 70)
multiplier = sConfigMgr->GetOption<float>(
"DynamicQuestXP.Level_61_70", 1.0f);
else
multiplier = sConfigMgr->GetOption<float>(
"DynamicQuestXP.Level_70_plus", 1.0f);
xpValue = static_cast<uint32>(xpValue * multiplier);
}
};
void AddSC_ProgressiveQuestXP()
{
new ProgressiveQuestXPPlayer();
}
@@ -0,0 +1,6 @@
void AddSC_ProgressiveQuestXP();
void Addmod_progressive_quest_xpScripts()
{
AddSC_ProgressiveQuestXP();
}
+123
View File
@@ -0,0 +1,123 @@
#!/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 <<EOF
Usage: $(basename "$0") [--output FILE] [--no-compress]
Exports all AzerothCore databases from the running ac-database container
into a single dump file suitable for migration to another server.
Options:
--output FILE Output file path
(default: ./backups/YYYY-MM-DD_HH-MM-SS.sql.gz)
--no-compress Write plain .sql instead of .sql.gz
-h, --help Show this help
Examples:
$(basename "$0")
$(basename "$0") --output /mnt/backup/moonwell.sql.gz
$(basename "$0") --no-compress --output /tmp/dump.sql
EOF
}
log() { printf '[db-export] %s\n' "$*"; }
die() { printf 'ERROR: %s\n' "$*" >&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
}
# 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
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 "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 -h 127.0.0.1 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \
--databases $databases \
--add-drop-database \
--single-transaction \
--quick \
--routines \
--triggers \
--events" \
| gzip -6 > "$OUTPUT_FILE"
else
docker compose exec -T ac-database bash -lc \
"mysqldump -h 127.0.0.1 -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \
--databases $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
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
DUMP_FILE=""
DRY_RUN=0
FORCE=0
if [[ -f "$ENV_FILE" ]]; then
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
fi
usage() {
cat <<EOF
Usage: $(basename "$0") --from FILE [--dry-run] [--force]
Restores a full database dump (produced by db-export.sh) into the
ac-database Docker container. The container must be running.
Options:
--from FILE Dump file to restore (.sql or .sql.gz) (required)
--dry-run Show what would happen without executing
--force Skip the confirmation prompt
-h, --help Show this help
Examples:
$(basename "$0") --from backups/2025-01-01_12-00-00.sql.gz
$(basename "$0") --from /mnt/backup/dump.sql.gz --force
EOF
}
log() { printf '[db-import] %s\n' "$*"; }
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
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 -h 127.0.0.1 -uroot -p\"\$MYSQL_ROOT_PASSWORD\""
}
# ── argument parsing ──────────────────────────────────────────────────────────
while (($#)); do
case "$1" in
--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
done
# ── validation ────────────────────────────────────────────────────────────────
[[ -z "$DUMP_FILE" ]] && { usage >&2; die "--from FILE is required"; }
[[ -f "$DUMP_FILE" ]] || die "file not found: $DUMP_FILE"
cd "$ROOT_DIR"
# ── preview ───────────────────────────────────────────────────────────────────
file_size="$(du -sh "$DUMP_FILE" | cut -f1)"
printf '\nMigration plan:\n'
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"
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
# ── import ────────────────────────────────────────────────────────────────────
require_ready_db
log "restoring $DUMP_FILE into ac-database..."
log "this may take several minutes..."
if [[ "$DUMP_FILE" == *.gz ]]; then
zcat "$DUMP_FILE" | mysql_exec
else
mysql_exec < "$DUMP_FILE"
fi
log "import complete"
log ""
log "next steps:"
log " 1. Update realmlist: bash sync-realmlist.sh"
log " 2. Start the server: bash scripts/prod-deploy.sh --no-build"
+226
View File
@@ -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 <<EOF
Usage: $(basename "$0") [--no-build]
Starts the AzerothCore production docker stack.
Options:
--no-build Start containers without rebuilding images
-h, --help Show this help
EOF
}
while (($#)); do
case "$1" in
--no-build) BUILD_FLAG="" ;;
-h|--help) usage; exit 0 ;;
*) printf 'Unknown option: %s\n\n' "$1" >&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"
+27
View File
@@ -65,6 +65,15 @@ FORCE_PLAYERBOTS="${FORCE_PLAYERBOTS:-${ACORE_PLAYERBOTS_FORCE:-0}}"
: "${ACORE_PROGRESSION_MC_AQUAL_COOLDOWN_REDUCTION:=60}" : "${ACORE_PROGRESSION_MC_AQUAL_COOLDOWN_REDUCTION:=60}"
: "${ACORE_PROGRESSION_PVP_GEAR_REQUIREMENTS:=0}" : "${ACORE_PROGRESSION_PVP_GEAR_REQUIREMENTS:=0}"
: "${ACORE_DYNAMIC_QUEST_XP_ENABLE:=1}"
: "${ACORE_DYNAMIC_QUEST_XP_LEVEL_1_20:=1.5}"
: "${ACORE_DYNAMIC_QUEST_XP_LEVEL_21_30:=1.3}"
: "${ACORE_DYNAMIC_QUEST_XP_LEVEL_31_40:=1.1}"
: "${ACORE_DYNAMIC_QUEST_XP_LEVEL_41_50:=1.0}"
: "${ACORE_DYNAMIC_QUEST_XP_LEVEL_51_60:=1.0}"
: "${ACORE_DYNAMIC_QUEST_XP_LEVEL_61_70:=1.0}"
: "${ACORE_DYNAMIC_QUEST_XP_LEVEL_70_PLUS:=1.0}"
: "${ACORE_AOE_LOOT_ENABLE:=1}" : "${ACORE_AOE_LOOT_ENABLE:=1}"
: "${ACORE_AOE_LOOT_MESSAGE:=0}" : "${ACORE_AOE_LOOT_MESSAGE:=0}"
: "${ACORE_AOE_LOOT_RANGE:=55.0}" : "${ACORE_AOE_LOOT_RANGE:=55.0}"
@@ -316,6 +325,8 @@ ensure_world_conf() {
} }
sync_module_dists() { sync_module_dists() {
sync_file "$ROOT_DIR/modules/mod-progressive-quest-xp/conf/mod_progressive_quest_xp.conf.dist" \
"$MODULES_ETC_DIR/mod_progressive_quest_xp.conf.dist"
sync_file "$ROOT_DIR/modules/mod-aoe-loot/conf/mod_aoe_loot.conf.dist" \ sync_file "$ROOT_DIR/modules/mod-aoe-loot/conf/mod_aoe_loot.conf.dist" \
"$MODULES_ETC_DIR/mod_aoe_loot.conf.dist" "$MODULES_ETC_DIR/mod_aoe_loot.conf.dist"
sync_file "$ROOT_DIR/modules/mod-autobalance/conf/AutoBalance.conf.dist" \ sync_file "$ROOT_DIR/modules/mod-autobalance/conf/AutoBalance.conf.dist" \
@@ -393,6 +404,20 @@ IndividualProgression.PvPGearRequirements = ${ACORE_PROGRESSION_PVP_GEAR_REQUIRE
EOF EOF
} }
write_progressive_quest_xp_conf() {
write_file "$MODULES_ETC_DIR/mod_progressive_quest_xp.conf.dist" <<EOF
# Managed by ./setup-modules.sh
DynamicQuestXP.Enable = ${ACORE_DYNAMIC_QUEST_XP_ENABLE}
DynamicQuestXP.Level_1_20 = ${ACORE_DYNAMIC_QUEST_XP_LEVEL_1_20}
DynamicQuestXP.Level_21_30 = ${ACORE_DYNAMIC_QUEST_XP_LEVEL_21_30}
DynamicQuestXP.Level_31_40 = ${ACORE_DYNAMIC_QUEST_XP_LEVEL_31_40}
DynamicQuestXP.Level_41_50 = ${ACORE_DYNAMIC_QUEST_XP_LEVEL_41_50}
DynamicQuestXP.Level_51_60 = ${ACORE_DYNAMIC_QUEST_XP_LEVEL_51_60}
DynamicQuestXP.Level_61_70 = ${ACORE_DYNAMIC_QUEST_XP_LEVEL_61_70}
DynamicQuestXP.Level_70_plus = ${ACORE_DYNAMIC_QUEST_XP_LEVEL_70_PLUS}
EOF
}
write_aoe_loot_conf() { write_aoe_loot_conf() {
write_file "$MODULES_ETC_DIR/mod_aoe_loot.conf.dist" <<EOF write_file "$MODULES_ETC_DIR/mod_aoe_loot.conf.dist" <<EOF
# Managed by ./setup-modules.sh # Managed by ./setup-modules.sh
@@ -538,6 +563,7 @@ ensure_runtime_dirs() {
cleanup_legacy_module_overrides() { cleanup_legacy_module_overrides() {
local legacy_files=( local legacy_files=(
"$MODULES_ETC_DIR/mod_progressive_quest_xp.conf"
"$MODULES_ETC_DIR/AutoBalance.conf" "$MODULES_ETC_DIR/AutoBalance.conf"
"$MODULES_ETC_DIR/individualProgression.conf" "$MODULES_ETC_DIR/individualProgression.conf"
"$MODULES_ETC_DIR/mod_aoe_loot.conf" "$MODULES_ETC_DIR/mod_aoe_loot.conf"
@@ -595,6 +621,7 @@ main() {
configure_worldserver configure_worldserver
write_autobalance_conf write_autobalance_conf
write_individual_progression_conf write_individual_progression_conf
write_progressive_quest_xp_conf
write_aoe_loot_conf write_aoe_loot_conf
write_quest_loot_party_conf write_quest_loot_party_conf
write_eluna_conf write_eluna_conf