commit 67b9b2dde5fd3694e9fcb08b07d5de46f1527ab6 Author: AzerothCore Installer Date: Fri Aug 21 14:54:40 2026 +0000 Initial commit: full unattended AzerothCore + Playerbots installer - Builds mod-playerbots/azerothcore-wotlk (Playerbot branch) from source - Optional modules: mod-ah-bot, mod-ollama-chat, mod-quest-loot-party, mod-breaking-news-override - Interactive prompts for all secrets/settings, then fully unattended - systemd services, ufw rules, MySQL tuning, client data via bin/acore - Automated GM account creation via expect-driven console session - Resumable via a state-marker directory if a step fails partway through - Generates restart-servers.sh and soap-cmd.sh helper scripts - Writes a chmod 600 summary file with all credentials/next steps diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e99c735 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Generated at install time — never commit real credentials or install state +azerothcore-server-info.txt +.azerothcore-installer-state/ + +# OS/editor noise +.DS_Store +*.swp +*~ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..14fac91 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5511d7b --- /dev/null +++ b/README.md @@ -0,0 +1,206 @@ +# AzerothCore + Playerbots Installer + +A single script that takes a clean Ubuntu Server VM to a running AzerothCore +3.3.5a (Wrath of the Lich King) private server with the [Playerbots][playerbots] +fork, plus a handful of optional community modules — fully unattended after +you answer a short set of setup questions. + +[playerbots]: https://github.com/mod-playerbots/azerothcore-wotlk + +## What it installs + +- **AzerothCore**, built from the [`mod-playerbots/azerothcore-wotlk`][pb-core] + fork (`Playerbot` branch) — required for Playerbots support; the standard + AzerothCore repo will not compile the module. +- **mod-playerbots** — populates the world with AI-controlled bot characters + you can group with, fight alongside, or just watch go about their business. +- **mod-ah-bot** *(optional)* — keeps the auction house stocked and active. +- **mod-ollama-chat** *(optional)* — gives bots LLM-generated chat via an + [Ollama](https://ollama.com) server, either installed locally by this + script or pointed at one you already run elsewhere on your network. +- **mod-quest-loot-party** *(optional)* — lets every eligible party member + receive a quest item drop, not just whoever loots first. +- **mod-breaking-news-override** *(optional)* — an announcement panel on the + character-select screen, driven by a small HTML file you can edit later. + +It also sets up: + +- `authserver` / `worldserver` as **systemd services** (auto-start on boot, + auto-restart on crash) +- Basic **MySQL tuning** sized to the VM's RAM +- **ufw firewall rules** for the game ports (optional, on by default) +- **Client data** (maps/vmaps/mmaps/dbc), downloaded pre-extracted via + AzerothCore's own `bin/acore client-data` command — no WoW client needed + for this step +- A **GM (admin) account**, created automatically +- Helper scripts: `restart-servers.sh` and (if you want it later) `soap-cmd.sh` + for remote console commands +- A **summary file** in your home directory with every credential, path, + and next step you'll need + +[pb-core]: https://github.com/mod-playerbots/azerothcore-wotlk + +## Requirements + +- **Ubuntu Server 24.04** (other Ubuntu versions will likely work; nothing + else is tested) +- A regular user with **sudo** access (not root) +- Recommended VM spec: **8 vCPU / 16GB RAM / 60GB+ disk** — see + [Hardware sizing](#hardware-sizing) below for smaller/larger setups +- Outbound internet access (cloning repos, `apt`, downloading client data) + +## Usage + +```bash +git clone +cd azerothcore-playerbots-installer +chmod +x install.sh +``` + +**Run it inside `screen` or `tmux`.** The compile step alone can take +30–90+ minutes; if your SSH session drops, a bare foreground script dies +with it. + +```bash +screen -S install +./install.sh +``` + +You'll be asked for: + +- A MySQL password for the database user the server will use +- Build/thread counts (sensible defaults are suggested based on your CPU) +- Which optional modules to install +- Playerbots population size (min/max bots) +- Ollama connection details, if you want bot chat +- A GM account username/password +- Whether to open the game ports in `ufw` + +Then it runs unattended. Detach from `screen` any time with `Ctrl+A`, `D`, +and reattach later with `screen -r install`. + +When it finishes, everything you need is in: + +``` +~/azerothcore-server-info.txt +``` + +This file is `chmod 600`'d automatically since it contains real passwords. +**Treat it like a password file** — don't copy it somewhere world-readable. + +## Resuming after a failure + +Every step records its own completion in `~/.azerothcore-installer-state/`. +If the script dies partway through (compiler error, disconnected SSH, +out of disk, etc.), fix the underlying problem and just run it again: + +```bash +./install.sh +``` + +Completed steps are skipped; the run picks up wherever it left off. You'll +be asked the setup questions again each time (they're cheap to answer), but +slow steps like the build or client-data download won't redo unnecessarily. + +To force a specific step to redo, delete its marker: + +```bash +rm ~/.azerothcore-installer-state/build +./install.sh +``` + +## Hardware sizing + +| Bot count / use case | vCPU | RAM | Disk | +|---|---|---|---| +| Solo / small friends group, ~50-100 bots | 4 | 8GB | 40GB | +| Medium, ~200-300 bots | 6-8 | 16GB | 60GB | +| Large bot population / real player traffic | 8+ | 16-24GB | 80GB+ | + +`worldserver`'s main loop is heavily single-core-performance dependent — +a modern CPU with fewer, faster cores tends to outperform more, slower +cores. `MapUpdate.Threads` (set automatically from your detected core +count, adjustable) generally shouldn't exceed cores minus 2. + +## After install: things worth doing + +**Confirm the realm address is right for how people will connect.** The +summary file fills in your best-guess public IP, but if you're behind a +home router (NAT), players outside your LAN need port forwarding for +3724/8085 on the router itself, and the `realmlist` table's `address` +needs to be the address actually reachable from where they're connecting +from: + +```bash +mysql -u acore -p acore_auth -e "UPDATE realmlist SET address = 'YOUR_IP' WHERE id = 1;" +``` + +**AH Bot needs one manual step.** Because its buyer/seller acts through a +real character, and character creation requires an actual client login, +this can't be scripted headlessly. The summary file has the exact steps — +short version: create an account for it, log into the client once to make +a throwaway character, then point `mod_ahbot.conf` at that account's ID +and give the character some starting gold. + +**SOAP remote admin is installed but disabled by default.** The +`soap-cmd.sh` helper script is generated either way, but `SOAP.Enabled` +is left at `0` in `worldserver.conf` — enabling a remote admin interface +by default felt like the wrong call for an unattended script to make on +your behalf. Flip it on in `worldserver.conf` when you actually want it, +and restrict the port at your firewall to trusted IPs rather than opening +it broadly (SOAP auth sends credentials unencrypted over plain HTTP). + +**Consider restricting SSH, and MySQL/SOAP if you enable them, to known +IPs** rather than "Anywhere" in `ufw`, especially if this VM has a public +IP. The installer only opens the two game ports by default for this +reason. + +## Why some things work the way they do + +A few non-obvious lessons are baked into this script, worth knowing if +you're debugging or extending it: + +- **`-DTOOLS_BUILD=db-only`, never `none`.** AzerothCore's `dbimport` tool + is bundled under the same cmake flag as the map/vmap/mmap extractors. + Setting it to `none` to skip the (slow, client-required) extractors also + silently skips building `dbimport` itself — which then makes the + database-import step fail with a confusing missing-file error several + steps later, with no obvious link back to the build flag. `db-only` + builds just the database tool; client data comes from `bin/acore + client-data` instead (pre-extracted, no client needed). +- **Module SQL is never imported by hand.** Every module here relies on + AzerothCore's own auto-updater to apply its SQL on first `worldserver` + start, using a `updates` tracking table it manages itself. Manually + running a module's `.sql` files against MySQL — even when a module's own + README suggests it — desyncs that tracking table from actual database + state, which shows up later as baffling "table already exists" or + "table doesn't exist" crashes. Let the updater own its own bookkeeping. +- **Module config filenames are not consistent.** Some use hyphens + (`mod-quest-loot-party.conf`), some underscores (`mod_ahbot.conf`, + `mod_ollama_chat.conf`), one is just lowercase with neither + (`breakingnews.conf`) — and none of these necessarily match the module's + repository name. The filenames in this script were confirmed against + real installs; if a module updates and renames its config, the relevant + step will need updating too. +- **GM account creation needs the console, not raw SQL.** Account + passwords are stored as an SRP6 salt+verifier pair, not a simple hash, + so there's no safe direct-SQL way to create one. This script briefly + stops the systemd-managed `worldserver`, drives an interactive console + session via `expect`, and hands control back to systemd afterward. + +## What this script won't do for you + +- **Won't source a WoW client.** Not needed anyway — client data comes + pre-extracted via `bin/acore client-data`. +- **Won't create the AH Bot's character** — see above, needs a real + client login. +- **Won't harden the VM beyond basic firewall rules** — no fail2ban, + no automatic security updates, no TLS in front of SOAP. Reasonable + next steps for anything facing the open internet. +- **Won't set up backups.** Worth adding a periodic `mysqldump` of all + databases once the server's running — bot activity and real player + progress both live only in MySQL. + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..6614239 --- /dev/null +++ b/install.sh @@ -0,0 +1,933 @@ +#!/usr/bin/env bash +# +# ============================================================================== +# AzerothCore + Playerbots — Full Automated Installer +# ============================================================================== +# +# Installs, from a clean Ubuntu Server VM, all the way to running services: +# +# - AzerothCore (Playerbot fork: mod-playerbots/azerothcore-wotlk) +# - mod-playerbots (bot army) +# - mod-ah-bot (auction house bot) [optional] +# - mod-ollama-chat (LLM-powered bot chat) [optional] +# - mod-quest-loot-party (shared quest item loot) [optional] +# - mod-breaking-news-override (character-select notice) [optional] +# +# It asks for every password/setting it needs up front, then runs +# unattended. When finished it writes a summary file to your home +# directory with everything you'll need to connect and manage the +# server (chmod 600 — it contains real passwords). +# +# USAGE: +# 1. Run this on a CLEAN Ubuntu Server 24.04 VM as a regular sudo user +# (not root). +# 2. Run it inside `screen` or `tmux` — the build step alone can take +# 30-90+ minutes, and this script does NOT survive an SSH disconnect +# on its own: +# screen -S install +# chmod +x install.sh +# ./install.sh +# 3. If it stops partway (build error, disconnect, etc.), just run it +# again. Every step records completion in a state directory and is +# skipped on re-run once done — see "Resuming" in the README. +# +# This script is intentionally verbose about what it's doing and why. +# Read the comments if something goes wrong — they explain several +# non-obvious gotchas this project's tooling has (inconsistent config +# filenames between modules, a cmake flag that silently skips the +# database-import tool, etc). +# +# ============================================================================== + +set -euo pipefail + +# ------------------------------------------------------------------------------ +# Globals +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INSTALL_DIR="${INSTALL_DIR:-$HOME/azeroth-server}" +STATE_DIR="$HOME/.azerothcore-installer-state" +SUMMARY_FILE="$HOME/azerothcore-server-info.txt" + +CORE_REPO="https://github.com/mod-playerbots/azerothcore-wotlk.git" +CORE_BRANCH="Playerbot" +PLAYERBOTS_MODULE_REPO="https://github.com/mod-playerbots/mod-playerbots.git" +PLAYERBOTS_MODULE_BRANCH="master" +AHBOT_MODULE_REPO="https://github.com/azerothcore/mod-ah-bot.git" +OLLAMA_MODULE_REPO="https://github.com/DustinHendrickson/mod-ollama-chat.git" +QUESTPARTY_MODULE_REPO="https://github.com/pangolp/mod-quest-loot-party.git" +BREAKINGNEWS_MODULE_REPO="https://github.com/azerothcore/mod-breaking-news-override.git" + +mkdir -p "$STATE_DIR" + +# ------------------------------------------------------------------------------ +# Logging helpers +# ------------------------------------------------------------------------------ + +log() { echo -e "\n\033[1;32m==> $1\033[0m\n"; } +info() { echo -e "\033[0;36m $1\033[0m"; } +warn() { echo -e "\n\033[1;33m!! $1\033[0m\n"; } +fail() { echo -e "\n\033[1;31mXX $1\033[0m\n" >&2; exit 1; } + +# ------------------------------------------------------------------------------ +# State tracking — lets the script be re-run safely after a partial failure. +# Each completed step writes a marker file; steps check for their marker +# before doing (potentially slow / non-idempotent) work. +# ------------------------------------------------------------------------------ + +step_done() { [ -f "$STATE_DIR/$1" ]; } +mark_done() { touch "$STATE_DIR/$1"; } + +run_step() { + local step_name="$1" + local step_fn="$2" + if step_done "$step_name"; then + info "Skipping '$step_name' (already completed — remove $STATE_DIR/$step_name to redo it)" + else + log "Step: $step_name" + "$step_fn" + mark_done "$step_name" + fi +} + +# ------------------------------------------------------------------------------ +# Prompt helpers +# ------------------------------------------------------------------------------ + +# prompt_text +prompt_text() { + local __var="$1" __q="$2" __default="${3:-}" + local __answer + if [ -n "$__default" ]; then + read -r -p "$__q [$__default]: " __answer + __answer="${__answer:-$__default}" + else + read -r -p "$__q: " __answer + fi + printf -v "$__var" '%s' "$__answer" +} + +# prompt_yes_no -- returns 0 for yes, 1 for no +prompt_yes_no() { + local __q="$1" __default="${2:-y}" + local __suffix="[y/N]" + [ "$__default" = "y" ] && __suffix="[Y/n]" + local __answer + read -r -p "$__q $__suffix: " __answer + __answer="${__answer:-$__default}" + [[ "$__answer" =~ ^[Yy] ]] +} + +# prompt_password_confirm +# Prompts twice (hidden input) and insists they match before returning. +prompt_password_confirm() { + local __var="$1" __q="$2" + local __p1 __p2 + while true; do + read -r -s -p "$__q: " __p1; echo + read -r -s -p "Confirm: " __p2; echo + if [ "$__p1" != "$__p2" ]; then + echo "Passwords did not match — try again." + continue + fi + if [ -z "$__p1" ]; then + echo "Password cannot be empty — try again." + continue + fi + printf -v "$__var" '%s' "$__p1" + break + done +} + +# ------------------------------------------------------------------------------ +# Preflight checks +# ------------------------------------------------------------------------------ + +preflight_checks() { + if [ "$EUID" -eq 0 ]; then + fail "Do not run this script as root. Run as a regular sudo-capable user." + fi + + if ! sudo -v; then + fail "This user needs sudo access." + fi + + if ! grep -qi "ubuntu" /etc/os-release 2>/dev/null; then + warn "This doesn't look like Ubuntu. The script assumes apt/systemd/ufw are available — proceed at your own risk." + fi + + local ram_gb + ram_gb=$(awk '/MemTotal/ {printf "%.0f", $2/1024/1024}' /proc/meminfo) + if [ "$ram_gb" -lt 8 ]; then + warn "Detected ~${ram_gb}GB RAM. AzerothCore + Playerbots wants 8GB minimum, 16GB comfortable. Proceeding, but the build or a large bot population may struggle." + fi + + local disk_free_gb + disk_free_gb=$(df --output=avail -BG "$HOME" | tail -1 | tr -dc '0-9') + if [ "$disk_free_gb" -lt 40 ]; then + warn "Detected ~${disk_free_gb}GB free disk under \$HOME. 40GB+ is recommended (client data + DB growth)." + fi +} + +# ------------------------------------------------------------------------------ +# Configuration collection — everything the script needs to know is +# gathered up front so the rest of the run is unattended. +# ------------------------------------------------------------------------------ + +collect_config() { + echo "==============================================================" + echo " AzerothCore + Playerbots installer — configuration" + echo "==============================================================" + echo "This asks a series of questions, then runs unattended." + echo "Press Enter to accept a default shown in [brackets]." + echo + + # --- Database --- + prompt_password_confirm ACORE_DB_PASSWORD "MySQL password to set for the 'acore' database user" + + # --- Build sizing --- + local nproc_val + nproc_val=$(nproc) + local default_threads=$(( nproc_val > 2 ? nproc_val - 2 : 1 )) + prompt_text BUILD_THREADS "Parallel build threads (compile job count)" "$default_threads" + prompt_text WORLD_UPDATE_THREADS "worldserver MapUpdate.Threads" "$default_threads" + + # --- Realm --- + prompt_text REALM_NAME "Realm name (shown in the client realm list)" "AzerothCore" + + # --- Playerbots --- + if prompt_yes_no "Install mod-playerbots (bot army)?" "y"; then + INSTALL_PLAYERBOTS="1" + prompt_text MIN_RANDOM_BOTS "Minimum random bots" "50" + prompt_text MAX_RANDOM_BOTS "Maximum random bots" "200" + else + INSTALL_PLAYERBOTS="0" + MIN_RANDOM_BOTS="0" + MAX_RANDOM_BOTS="0" + fi + + # --- AH Bot --- + if prompt_yes_no "Install mod-ah-bot (auction house bot)?" "y"; then + INSTALL_AHBOT="1" + else + INSTALL_AHBOT="0" + fi + + # --- Ollama chat --- + if prompt_yes_no "Install mod-ollama-chat (LLM-powered bot chat via Ollama)?" "n"; then + INSTALL_OLLAMA="1" + echo + echo " mod-ollama-chat needs a running Ollama server. It can be:" + echo " - on THIS machine (the script will install Ollama + pull a model)" + echo " - on ANOTHER machine on your network (you just give the address)" + if prompt_yes_no " Install Ollama locally on this VM?" "n"; then + OLLAMA_LOCAL="1" + prompt_text OLLAMA_MODEL "Ollama model to pull (small models recommended on a shared VM)" "llama3.2:1b" + OLLAMA_ENDPOINT="http://localhost:11434" + else + OLLAMA_LOCAL="0" + prompt_text OLLAMA_HOST "IP address of the existing Ollama server" "" + prompt_text OLLAMA_PORT "Ollama port" "11434" + OLLAMA_ENDPOINT="http://${OLLAMA_HOST}:${OLLAMA_PORT}" + prompt_text OLLAMA_MODEL "Exact model name/tag to use (must already be pulled on that server — check with: curl http://${OLLAMA_HOST}:${OLLAMA_PORT}/api/tags)" "" + fi + else + INSTALL_OLLAMA="0" + OLLAMA_LOCAL="0" + OLLAMA_ENDPOINT="" + OLLAMA_MODEL="" + fi + + # --- Quest Loot Party --- + if prompt_yes_no "Install mod-quest-loot-party (shared quest-item loot for groups)?" "y"; then + INSTALL_QUESTPARTY="1" + else + INSTALL_QUESTPARTY="0" + fi + + # --- Breaking News --- + if prompt_yes_no "Install mod-breaking-news-override (character-select announcement panel)?" "y"; then + INSTALL_BREAKINGNEWS="1" + prompt_text BREAKINGNEWS_TITLE "Title for the announcement panel" "$REALM_NAME" + else + INSTALL_BREAKINGNEWS="0" + BREAKINGNEWS_TITLE="" + fi + + # --- GM account --- + echo + echo " A GM (admin) account will be created for you once the server is up." + prompt_text GM_ACCOUNT_NAME "GM account username" "admin" + prompt_password_confirm GM_ACCOUNT_PASSWORD "GM account password" + + # --- Firewall --- + echo + if prompt_yes_no "Configure ufw firewall for game ports (3724, 8085)?" "y"; then + CONFIGURE_FIREWALL="1" + else + CONFIGURE_FIREWALL="0" + fi + + echo + echo "==============================================================" + echo " Configuration collected. Starting unattended install." + echo " Safe to detach/reattach your screen/tmux session from here." + echo "==============================================================" + sleep 2 +} + +# ------------------------------------------------------------------------------ +# Step: system dependencies +# ------------------------------------------------------------------------------ + +step_install_dependencies() { + sudo apt update && sudo apt upgrade -y + sudo apt install -y git cmake make gcc g++ clang \ + libssl-dev libbz2-dev libreadline-dev libncurses-dev \ + libboost-all-dev libmysqlclient-dev mysql-server \ + unzip curl screen lsb-release gnupg wget expect +} + +# ------------------------------------------------------------------------------ +# Step: MySQL user setup +# ------------------------------------------------------------------------------ + +step_setup_mysql() { + sudo systemctl enable --now mysql + sudo mysql -u root <> playerbots.conf <> playerbots.conf < "$bin_dir/breakingnews.html" < + +

Welcome to ${BREAKINGNEWS_TITLE}!

+
+

Edit $bin_dir/breakingnews.html to customize this message, then restart worldserver.

+ + +EOF + fi + fi +} + +# ------------------------------------------------------------------------------ +# Step: install Ollama locally (only if the user chose that option) +# ------------------------------------------------------------------------------ + +step_install_ollama_local() { + [ "$INSTALL_OLLAMA" = "1" ] && [ "$OLLAMA_LOCAL" = "1" ] || return 0 + + curl -fsSL https://ollama.com/install.sh | sh + sudo systemctl enable --now ollama + sleep 3 + ollama pull "$OLLAMA_MODEL" +} + +# ------------------------------------------------------------------------------ +# Step: MySQL tuning +# ------------------------------------------------------------------------------ + +step_tune_mysql() { + local mysql_cnf="/etc/mysql/mysql.conf.d/mysqld.cnf" + local ram_gb + ram_gb=$(awk '/MemTotal/ {printf "%.0f", $2/1024/1024}' /proc/meminfo) + local buffer_pool_gb=$(( ram_gb / 2 )) + [ "$buffer_pool_gb" -lt 1 ] && buffer_pool_gb=1 + + if ! grep -q "# --- AzerothCore installer tuning ---" "$mysql_cnf" 2>/dev/null; then + sudo tee -a "$mysql_cnf" > /dev/null </dev/null 2>&1 || { warn "ufw not found, skipping firewall configuration."; return 0; } + + sudo ufw allow 22/tcp comment 'SSH' + sudo ufw allow 3724/tcp comment 'AzerothCore auth' + sudo ufw allow 8085/tcp comment 'AzerothCore world' + sudo ufw --force enable +} + +# ------------------------------------------------------------------------------ +# Step: client data +# ------------------------------------------------------------------------------ + +step_client_data() { + cd "$INSTALL_DIR/azerothcore-wotlk" + local bin_dir="env/dist/bin" + + if [ -f "$bin_dir/maps" ] || [ -d "$bin_dir/maps" ]; then + return 0 + fi + + if [ -x "bin/acore" ]; then + bin/acore client-data || warn "bin/acore client-data failed. Run it manually later: cd $INSTALL_DIR/azerothcore-wotlk && bin/acore client-data" + else + warn "bin/acore not found — fetch client data manually. See: https://github.com/wowgaming/client-data" + fi +} + +# ------------------------------------------------------------------------------ +# Step: systemd services +# ------------------------------------------------------------------------------ + +step_systemd_services() { + local bin_dir="$INSTALL_DIR/azerothcore-wotlk/env/dist/bin" + local user + user="$(whoami)" + + sudo tee /etc/systemd/system/azerothcore-authserver.service > /dev/null < /dev/null </dev/null | grep -q "Update time diff"; then + info "worldserver is up and running its main loop." + return 0 + fi + if sudo systemctl is-failed --quiet azerothcore-worldserver; then + fail "worldserver service failed to start. Check: sudo journalctl -u azerothcore-worldserver -n 100 --no-pager" + fi + sleep 5 + waited=$((waited + 5)) + done + warn "worldserver did not report a fully-loaded state within ${max_wait}s. It may still be starting — check: sudo journalctl -u azerothcore-worldserver -f" +} + +# ------------------------------------------------------------------------------ +# Step: create the GM account +# +# This is the one step that genuinely can't be done with a simple SQL +# INSERT — AzerothCore stores account credentials as an SRP6 salt+verifier +# pair, not a plain hash, so account creation has to go through the +# worldserver console command itself. Since the server is already running +# as a systemd service (no attached console), this step briefly stops the +# service, runs worldserver in the foreground under `expect` so we can +# type the account-creation commands the moment the console is ready, then +# hands control back to systemd. +# +# If this step fails for any reason, it does NOT fail the whole install — +# it warns and leaves clear manual instructions in the summary file, +# since account creation only takes a few seconds to do by hand. +# ------------------------------------------------------------------------------ + +step_create_gm_account() { + local bin_dir="$INSTALL_DIR/azerothcore-wotlk/env/dist/bin" + + sudo systemctl stop azerothcore-worldserver + sleep 2 + + local expect_script + expect_script=$(mktemp) + cat > "$expect_script" < to stop" { } + timeout { puts "TIMEOUT_WAITING_FOR_READY"; exit 1 } +} +# Give the command processor a moment to fully attach after the banner. +sleep 15 +send "account create ${GM_ACCOUNT_NAME} ${GM_ACCOUNT_PASSWORD}\r" +sleep 3 +send "account set gmlevel ${GM_ACCOUNT_NAME} 3 -1\r" +sleep 3 +send "\003" +expect eof +EXPECT_EOF + + if expect "$expect_script"; then + GM_ACCOUNT_CREATED="1" + else + GM_ACCOUNT_CREATED="0" + warn "Automatic GM account creation did not complete cleanly. You can create it by hand — instructions are in $SUMMARY_FILE." + fi + rm -f "$expect_script" + + sudo systemctl start azerothcore-worldserver +} + +# ------------------------------------------------------------------------------ +# Step: generate helper scripts (restart, SOAP) +# ------------------------------------------------------------------------------ + +step_generate_helper_scripts() { + cat > "$INSTALL_DIR/restart-servers.sh" <<'EOF' +#!/usr/bin/env bash +# Restart AzerothCore services after a config change. +# Usage: ./restart-servers.sh [auth|world|both] +set -e +TARGET="${1:-both}" +case "$TARGET" in + auth) sudo systemctl restart azerothcore-authserver ;; + world) sudo systemctl restart azerothcore-worldserver ;; + both) sudo systemctl restart azerothcore-authserver + sudo systemctl restart azerothcore-worldserver ;; + *) echo "Usage: $0 [auth|world|both]"; exit 1 ;; +esac +sleep 3 +echo "--- Status ---" +sudo systemctl status azerothcore-authserver --no-pager | head -5 +echo "" +sudo systemctl status azerothcore-worldserver --no-pager | head -5 +echo "" +echo "Tailing worldserver logs (Ctrl+C to stop watching, server keeps running)..." +sudo journalctl -u azerothcore-worldserver -f +EOF + chmod +x "$INSTALL_DIR/restart-servers.sh" + + if [ "$SOAP_ENABLED" = "1" ]; then + cat > "$INSTALL_DIR/soap-cmd.sh" <\"" + exit 1 +fi + +curl -s -u "\${SOAP_USER}:\${SOAP_PASS}" \\ + -H "Content-Type: text/xml" \\ + -d " + + + + \${CMD} + + +" \\ + "http://\${SOAP_HOST}:\${SOAP_PORT}/" +echo "" +EOF + chmod 700 "$INSTALL_DIR/soap-cmd.sh" + fi +} + +# ------------------------------------------------------------------------------ +# Step: write the summary file +# ------------------------------------------------------------------------------ + +step_write_summary() { + local etc_dir="$INSTALL_DIR/azerothcore-wotlk/env/dist/etc" + local public_ip + public_ip=$(curl -s ifconfig.me 2>/dev/null || echo "") + local local_ip + local_ip=$(hostname -I 2>/dev/null | awk '{print $1}') + + { + echo "==============================================================" + echo " AzerothCore + Playerbots — Install Summary" + echo " Generated: $(date)" + echo "==============================================================" + echo + echo "KEEP THIS FILE PRIVATE — it contains real passwords." + echo " chmod 600 $SUMMARY_FILE (already applied)" + echo + echo "--- Server ---" + echo "Install directory: $INSTALL_DIR/azerothcore-wotlk" + echo "Local IP: ${local_ip:-unknown}" + echo "Public IP (best guess): $public_ip" + echo "Realm name: $REALM_NAME" + echo + echo "--- MySQL ---" + echo "User: acore" + echo "Password: $ACORE_DB_PASSWORD" + echo "Databases: acore_auth, acore_characters, acore_world" + [ "$INSTALL_PLAYERBOTS" = "1" ] && echo " acore_playerbots" + echo + echo "--- GM account ---" + echo "Username: $GM_ACCOUNT_NAME" + echo "Password: $GM_ACCOUNT_PASSWORD" + echo "GM level: 3" + if [ "${GM_ACCOUNT_CREATED:-0}" = "0" ]; then + echo + echo " !! Automatic account creation did NOT complete. Create it by hand:" + echo " sudo systemctl stop azerothcore-worldserver" + echo " cd $INSTALL_DIR/azerothcore-wotlk/env/dist/bin && ./worldserver" + echo " (wait for it to fully load, then type:)" + echo " account create $GM_ACCOUNT_NAME $GM_ACCOUNT_PASSWORD" + echo " account set gmlevel $GM_ACCOUNT_NAME 3 -1" + echo " (Ctrl+C, then:)" + echo " sudo systemctl start azerothcore-worldserver" + fi + echo + echo "--- Client setup ---" + echo "In realmlist.wtf: set realmlist ${public_ip}" + echo " (use the local IP instead if connecting from the same LAN)" + echo + echo "--- Realm DB row (fix if the address above is wrong for your network) ---" + echo " mysql -u acore -p acore_auth -e \"UPDATE realmlist SET address = 'YOUR_IP' WHERE id = 1;\"" + echo + echo "--- Services ---" + echo " sudo systemctl status azerothcore-authserver" + echo " sudo systemctl status azerothcore-worldserver" + echo " sudo journalctl -u azerothcore-worldserver -f" + echo " $INSTALL_DIR/restart-servers.sh [auth|world|both]" + echo + echo "--- Modules installed ---" + [ "$INSTALL_PLAYERBOTS" = "1" ] && echo " - Playerbots (min $MIN_RANDOM_BOTS / max $MAX_RANDOM_BOTS bots)" + [ "$INSTALL_AHBOT" = "1" ] && echo " - AH Bot" + [ "$INSTALL_OLLAMA" = "1" ] && echo " - Ollama Chat (endpoint: $OLLAMA_ENDPOINT, model: $OLLAMA_MODEL)" + [ "$INSTALL_QUESTPARTY" = "1" ] && echo " - Quest Loot Party" + [ "$INSTALL_BREAKINGNEWS" = "1" ] && echo " - Breaking News Override (edit: $INSTALL_DIR/azerothcore-wotlk/env/dist/bin/breakingnews.html)" + echo + if [ "$INSTALL_AHBOT" = "1" ]; then + echo "--- AH Bot: manual follow-up required ---" + echo " 1. Create a dedicated account for the bot to use:" + echo " (via SOAP, once GM account works) ./soap-cmd.sh \"account create ahbot \"" + echo " 2. Log into the client ONCE with that account and create a throwaway" + echo " character — AH Bot needs a real character to act through, and this" + echo " step genuinely requires a client login (can't be scripted headlessly)." + echo " 3. Find its account ID:" + echo " mysql -u acore -p acore_auth -e \"SELECT id, username FROM account WHERE username='AHBOT';\"" + echo " 4. Put that ID into:" + echo " $etc_dir/modules/mod_ahbot.conf" + echo " 5. Give the character starting gold (money is stored in copper, 1g = 10000):" + echo " mysql -u acore -p acore_characters -e \"UPDATE characters SET money = 10000000 WHERE account = ;\"" + echo " 6. $INSTALL_DIR/restart-servers.sh world" + echo + fi + echo "--- Config file locations ---" + echo " $etc_dir/authserver.conf" + echo " $etc_dir/worldserver.conf" + echo " $etc_dir/modules/ (per-module .conf files)" + echo + echo "--- Client data ---" + echo " Downloaded via 'bin/acore client-data' into env/dist/bin/" + echo " (maps, vmaps, mmaps, dbc — pre-extracted, sourced from" + echo " github.com/wowgaming/client-data)" + echo + if [ "$SOAP_ENABLED" = "1" ]; then + echo "--- SOAP remote admin ---" + echo " Disabled by default in worldserver.conf (SOAP.Enabled = 0)." + echo " To enable: set SOAP.Enabled = 1 and SOAP.IP as needed in worldserver.conf," + echo " restart worldserver, then use: $INSTALL_DIR/soap-cmd.sh \"\"" + echo " soap-cmd.sh contains your GM password in plaintext — keep it private" + echo " (already chmod 700)." + echo + fi + echo "==============================================================" + } > "$SUMMARY_FILE" + + chmod 600 "$SUMMARY_FILE" +} + +# ------------------------------------------------------------------------------ +# main +# ------------------------------------------------------------------------------ + +main() { + SOAP_ENABLED="1" # helper script is always generated; SOAP itself stays + # disabled in worldserver.conf until the user opts in + # (see summary file) — safer default than exposing + # an admin interface automatically. + + preflight_checks + + # Configuration is re-collected on every run (it only takes a minute + # to answer) since shell variables don't persist between invocations. + # Completed install STEPS are still skipped via the state directory + # markers below, regardless of re-answering these questions the same + # way — so re-running after a partial failure is still fast. + collect_config + + run_step "dependencies" step_install_dependencies + run_step "mysql_user" step_setup_mysql + run_step "clone_source" step_clone_source + run_step "build" step_build + run_step "import_databases" step_import_databases + run_step "configure_core" step_configure_core + run_step "configure_modules" step_configure_modules + run_step "install_ollama_local" step_install_ollama_local + run_step "tune_mysql" step_tune_mysql + run_step "firewall" step_configure_firewall + run_step "client_data" step_client_data + run_step "systemd_services" step_systemd_services + run_step "start_services" step_start_services + run_step "gm_account" step_create_gm_account + run_step "helper_scripts" step_generate_helper_scripts + step_write_summary # always regenerate — cheap, and reflects latest state + + log "Install complete." + echo "Summary written to: $SUMMARY_FILE (chmod 600 — contains real passwords)" + echo + echo "Quick checks:" + echo " sudo systemctl status azerothcore-worldserver" + echo " sudo journalctl -u azerothcore-worldserver -f" + echo + echo "Read $SUMMARY_FILE for connection details, the GM account, and any" + echo "manual follow-up steps (AH Bot needs one)." +} + +main "$@"