diff options
40 files changed, 4126 insertions, 385 deletions
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index cdf6fc887c..06b65aafdd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,24 +17,29 @@ "workspaceFolder": "/azerothcore", // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": null + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": null + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-vscode-remote.remote-containers", + "notskm.clang-tidy", + "xaver.clang-format", + "bbenoist.doxygen", + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "mhutchie.git-graph", + "github.vscode-pull-request-github", + "eamodio.gitlens", + "cschlosser.doxdocgen", + "sanaajani.taskrunnercode", + "mads-hartmann.bash-ide-vscode" + ] + } }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "notskm.clang-tidy", - "xaver.clang-format", - "bbenoist.doxygen", - "ms-vscode.cpptools", - "ms-vscode.cmake-tools", - "mhutchie.git-graph", - "github.vscode-pull-request-github", - "eamodio.gitlens", - "cschlosser.doxdocgen", - "sanaajani.taskrunnercode" - ], - // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], diff --git a/.github/workflows/dashboard-ci.yml b/.github/workflows/dashboard-ci.yml new file mode 100644 index 0000000000..5e56cb1c87 --- /dev/null +++ b/.github/workflows/dashboard-ci.yml @@ -0,0 +1,82 @@ +name: Dashboard CI +description: | + This workflow runs tests and builds for the AzerothCore dashboard. + It includes testing of bash scripts and integration testing of the AzerothCore server. + Do not remove this if something is broken here and you don't know how to fix it, ping Yehonal instead. + +on: + push: + branches: + - 'master' + pull_request: + types: + - opened + - reopened + - synchronize + workflow_dispatch: + +concurrency: + group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }}) + cancel-in-progress: true + +env: + CONTINUOUS_INTEGRATION: true + MYSQL_ROOT_PASSWORD: root + +jobs: + test-bash-scripts: + name: Test Bash Scripts + runs-on: ubuntu-24.04 + if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install requirements + run: | + sudo apt install -y bats + ./acore.sh install-deps + + - name: Run bash script tests for ${{ matrix.test-module }} + env: + TERM: xterm-256color + run: | + cd apps/test-framework + ./run-tests.sh --tap + + build-and-test: + name: Build and Integration Test + runs-on: ubuntu-24.04 + if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Configure AzerothCore settings + run: | + # Create basic configuration + cp conf/dist/config.sh conf/config.sh + # Configure dashboard + sed -i 's/MTHREADS=.*/MTHREADS="4"/' conf/config.sh + + - name: Run complete installation (deps, compile, database, client-data) + run: | + # This runs: install-deps, compile, database setup, client-data download + ./acore.sh init + timeout-minutes: 120 + + - name: Test authserver dry-run + run: | + cd env/dist/bin + timeout 5m ./authserver -dry-run + continue-on-error: false + + - name: Test worldserver dry-run + run: | + cd env/dist/bin + timeout 5m ./worldserver -dry-run + continue-on-error: false diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2b4b837a24..f5e39be64e 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -10,6 +10,9 @@ "github.vscode-pull-request-github", "eamodio.gitlens", "cschlosser.doxdocgen", - "sanaajani.taskrunnercode" + "sanaajani.taskrunnercode", + "mads-hartmann.bash-ide-vscode", + "jetmartin.bats", + "ms-vscode.makefile-tools", ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 70f526afc6..dcc292e7a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -115,5 +115,12 @@ "xutility": "cpp", "*.ipp": "cpp", "resumable": "cpp" - } + }, + "deno.enable": true, + "deno.path": "deps/deno/bin/deno", + "deno.lint": true, + "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json", + "C_Cpp.default.cppStandard": "c++17", + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "C_Cpp.default.compilerPath": "/usr/bin/clang" } diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d9d4902a1..494109cc6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,9 @@ set(CMAKE_BUILD_WITH_INSTALL_RPATH 0) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH 1) +# Export compile commands for IDE support +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + set(AC_PATH_ROOT "${CMAKE_SOURCE_DIR}") # set macro-directory diff --git a/apps/compiler/includes/functions.sh b/apps/compiler/includes/functions.sh index 2e7299905c..2299918187 100644 --- a/apps/compiler/includes/functions.sh +++ b/apps/compiler/includes/functions.sh @@ -1,3 +1,7 @@ +#!/usr/bin/env bash + +# Set SUDO variable - one liner +SUDO=$([ "$EUID" -ne 0 ] && echo "sudo" || echo "") function comp_clean() { DIRTOCLEAN=${BUILDPATH:-var/build/obj} @@ -134,23 +138,21 @@ function comp_compile() { mkdir -p "$confDir" echo "Cmake install..." - sudo cmake --install . --config $CTYPE + $SUDO cmake --install . --config $CTYPE popd >> /dev/null || exit 1 # set all aplications SUID bit echo "Setting permissions on binary files" - find "$AC_BINPATH_FULL" -mindepth 1 -maxdepth 1 -type f -exec sudo chown root:root -- {} + - find "$AC_BINPATH_FULL" -mindepth 1 -maxdepth 1 -type f -exec sudo chmod u+s -- {} + - - if [[ -n "$DOCKER" ]]; then - [[ -f "$confDir/worldserver.conf.dist" ]] && \ - cp -nv "$confDir/worldserver.conf.dist" "$confDir/worldserver.conf" - [[ -f "$confDir/authserver.conf.dist" ]] && \ - cp -nv "$confDir/authserver.conf.dist" "$confDir/authserver.conf" - [[ -f "$confDir/dbimport.conf.dist" ]] && \ - cp -nv "$confDir/dbimport.conf.dist" "$confDir/dbimport.conf" - fi + find "$AC_BINPATH_FULL" -mindepth 1 -maxdepth 1 -type f -exec $SUDO chown root:root -- {} + + find "$AC_BINPATH_FULL" -mindepth 1 -maxdepth 1 -type f -exec $SUDO chmod u+s -- {} + + + [[ -f "$confDir/worldserver.conf.dist" ]] && \ + cp -v --update=none "$confDir/worldserver.conf.dist" "$confDir/worldserver.conf" + [[ -f "$confDir/authserver.conf.dist" ]] && \ + cp -v --update=none "$confDir/authserver.conf.dist" "$confDir/authserver.conf" + [[ -f "$confDir/dbimport.conf.dist" ]] && \ + cp -v --update=none "$confDir/dbimport.conf.dist" "$confDir/dbimport.conf" echo "Done" ;; diff --git a/apps/compiler/includes/includes.sh b/apps/compiler/includes/includes.sh index c425160dc0..4f697947a7 100644 --- a/apps/compiler/includes/includes.sh +++ b/apps/compiler/includes/includes.sh @@ -10,7 +10,7 @@ fi function ac_on_after_build() { # move the run engine - cp -rvf "$AC_PATH_APPS/startup-scripts/"* "$BINPATH" + cp -rvf "$AC_PATH_APPS/startup-scripts/src/"* "$BINPATH" } registerHooks "ON_AFTER_BUILD" ac_on_after_build diff --git a/apps/installer/includes/functions.sh b/apps/installer/includes/functions.sh index 7e95f78fca..b4bd14caf5 100644 --- a/apps/installer/includes/functions.sh +++ b/apps/installer/includes/functions.sh @@ -1,3 +1,8 @@ +#!/usr/bin/env bash + +# Set SUDO variable - one liner +SUDO=$([ "$EUID" -ne 0 ] && echo "sudo" || echo "") + function inst_configureOS() { echo "Platform: $OSTYPE" case "$OSTYPE" in @@ -45,6 +50,42 @@ function inst_configureOS() { esac } +# Use the data/sql/create/create_mysql.sql to initialize the database +function inst_dbCreate() { + echo "Creating database..." + + # Attempt to connect with MYSQL_ROOT_PASSWORD + if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then + if $SUDO mysql -u root -p"$MYSQL_ROOT_PASSWORD" < "$AC_PATH_ROOT/data/sql/create/create_mysql.sql" 2>/dev/null; then + echo "Database created successfully." + return 0 + else + echo "Failed to connect with provided password, falling back to interactive mode..." + fi + fi + + # In CI environments or when no password is set, try without password first + if [[ "$CONTINUOUS_INTEGRATION" == "true" ]]; then + echo "CI environment detected, attempting connection without password..." + + if $SUDO mysql -u root < "$AC_PATH_ROOT/data/sql/create/create_mysql.sql" 2>/dev/null; then + echo "Database created successfully." + return 0 + else + echo "Failed to connect without password, falling back to interactive mode..." + fi + fi + + # Try with password (interactive mode) + echo "Please enter your sudo and your MySQL root password if prompted." + $SUDO mysql -u root -p < "$AC_PATH_ROOT/data/sql/create/create_mysql.sql" + if [ $? -ne 0 ]; then + echo "Database creation failed. Please check your MySQL server and credentials." + exit 1 + fi + echo "Database created successfully." +} + function inst_updateRepo() { cd "$AC_PATH_ROOT" if [ ! -z $INSTALLER_PULL_FROM ]; then @@ -73,7 +114,8 @@ function inst_cleanCompile() { function inst_allInOne() { inst_configureOS inst_compile - dbasm_import true true true + inst_dbCreate + inst_download_client_data } function inst_getVersionBranch() { @@ -215,7 +257,7 @@ function inst_module_remove { function inst_simple_restarter { echo "Running $1 ..." - bash "$AC_PATH_APPS/startup-scripts/simple-restarter" "$AC_BINPATH_FULL" "$1" + bash "$AC_PATH_APPS/startup-scripts/src/simple-restarter" "$AC_BINPATH_FULL" "$1" echo #disown -a #jobs -l diff --git a/apps/installer/includes/os_configs/debian.sh b/apps/installer/includes/os_configs/debian.sh index 4183925651..0eb2b3b1e4 100644 --- a/apps/installer/includes/os_configs/debian.sh +++ b/apps/installer/includes/os_configs/debian.sh @@ -2,8 +2,11 @@ CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# Set SUDO variable - one liner +SUDO=$([ "$EUID" -ne 0 ] && echo "sudo" || echo "") + if ! command -v lsb_release &>/dev/null ; then - sudo apt-get install -y lsb-release + $SUDO apt-get install -y lsb-release fi DEBIAN_VERSION=$(lsb_release -sr) @@ -18,18 +21,18 @@ if [[ $DEBIAN_VERSION -lt $DEBIAN_VERSION_MIN ]]; then echo "########## ########## ##########" fi -sudo apt-get update -y +$SUDO apt-get update -y -sudo apt-get install -y gdbserver gdb unzip curl \ +$SUDO apt-get install -y gdbserver gdb unzip curl \ libncurses-dev libreadline-dev clang g++ \ gcc git cmake make ccache \ libssl-dev libbz2-dev \ - libboost-all-dev gnupg wget + libboost-all-dev gnupg wget jq screen tmux VAR_PATH="$CURRENT_PATH/../../../../var" # run noninteractive install for MYSQL 8.4 LTS wget https://dev.mysql.com/get/mysql-apt-config_0.8.32-1_all.deb -P "$VAR_PATH" -sudo DEBIAN_FRONTEND="noninteractive" dpkg -i "$VAR_PATH/mysql-apt-config_0.8.32-1_all.deb" -sudo apt-get update -sudo DEBIAN_FRONTEND="noninteractive" apt-get install -y mysql-server libmysqlclient-dev +DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.32-1_all.deb" +$SUDO apt-get update +DEBIAN_FRONTEND="noninteractive" $SUDO apt-get install -y mysql-server libmysqlclient-dev diff --git a/apps/installer/includes/os_configs/ubuntu.sh b/apps/installer/includes/os_configs/ubuntu.sh index 02e6997ccd..9b45b35c04 100644 --- a/apps/installer/includes/os_configs/ubuntu.sh +++ b/apps/installer/includes/os_configs/ubuntu.sh @@ -2,8 +2,11 @@ CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# Set SUDO variable - one liner +SUDO=$([ "$EUID" -ne 0 ] && echo "sudo" || echo "") + if ! command -v lsb_release &>/dev/null ; then - sudo apt-get install -y lsb-release + $SUDO apt-get install -y lsb-release fi UBUNTU_VERSION=$(lsb_release -sr); @@ -23,28 +26,29 @@ case $UBUNTU_VERSION in ;; esac -sudo apt update +$SUDO apt update # shared deps -sudo DEBIAN_FRONTEND="noninteractive" \ -apt-get -y install ccache clang cmake curl google-perftools libmysqlclient-dev make unzip - -if [[ $CONTINUOUS_INTEGRATION || $DOCKER ]]; then - # TODO: update CI / Docker section for Ubuntu 22.04+ - sudo add-apt-repository -y ppa:mhier/libboost-latest && sudo apt update && sudo apt-get -y install build-essential cmake-data \ - libboost1.74-dev libbz2-dev libncurses5-dev libmysql++-dev libgoogle-perftools-dev libreadline6-dev libssl-dev libtool \ - openssl zlib1g-dev -else - sudo DEBIAN_FRONTEND="noninteractive" \ - apt-get install -y g++ gdb gdbserver gcc git \ - libboost-all-dev libbz2-dev libncurses-dev libreadline-dev \ - libssl-dev +DEBIAN_FRONTEND="noninteractive" $SUDO \ +apt-get -y install ccache clang cmake curl google-perftools libmysqlclient-dev make unzip jq screen tmux \ + libreadline-dev libncurses5-dev libncursesw5-dev libbz2-dev git gcc g++ libssl-dev \ + libncurses-dev libboost-all-dev gdb gdbserver VAR_PATH="$CURRENT_PATH/../../../../var" + +# Do not install MySQL if we are in docker (It will be used a docker container instead) or we are explicitly skipping it. +if [[ $DOCKER != 1 && $SKIP_MYSQL_INSTALL != 1 ]]; then # run noninteractive install for MYSQL 8.4 LTS wget https://dev.mysql.com/get/mysql-apt-config_0.8.32-1_all.deb -P "$VAR_PATH" - sudo DEBIAN_FRONTEND="noninteractive" dpkg -i "$VAR_PATH/mysql-apt-config_0.8.32-1_all.deb" - sudo apt-get update - sudo DEBIAN_FRONTEND="noninteractive" apt-get install -y mysql-server + DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.32-1_all.deb" + $SUDO apt-get update + DEBIAN_FRONTEND="noninteractive" $SUDO apt-get install -y mysql-server +fi + + +if [[ $CONTINUOUS_INTEGRATION ]]; then + $SUDO systemctl enable mysql.service + $SUDO systemctl start mysql.service fi + diff --git a/apps/installer/main.sh b/apps/installer/main.sh index eb0eb4f894..396dafaad5 100644 --- a/apps/installer/main.sh +++ b/apps/installer/main.sh @@ -19,7 +19,9 @@ options=( "run-worldserver (rw): execute a simple restarter for worldserver" # 11 "run-authserver (ra): execute a simple restarter for authserver" # 12 "docker (dr): Run docker tools" # 13 - "quit: Exit from this menu" # 14 + "version (v): Show AzerothCore version" # 14 + "service-manager (sm): Run service manager to run authserver and worldserver in background" # 15 + "quit: Exit from this menu" # 16 ) function _switch() { @@ -72,7 +74,11 @@ function _switch() { printf "AzerothCore Rev. %s\n" "$ACORE_VERSION" exit ;; - ""|"quit"|"15") + ""|"sm"|"service-manager"|"15") + bash "$AC_PATH_APPS/startup-scripts/src/service-manager.sh" "${@:2}" + exit + ;; + ""|"quit"|"16") echo "Goodbye!" exit ;; diff --git a/apps/startup-scripts/README.md b/apps/startup-scripts/README.md new file mode 100644 index 0000000000..9a408dd6c0 --- /dev/null +++ b/apps/startup-scripts/README.md @@ -0,0 +1,497 @@ +# AzerothCore Startup Scripts + +A comprehensive suite of scripts for managing AzerothCore server instances with advanced session management, automatic restart capabilities, and production-ready service management. + +## 📋 Table of Contents + +- [Overview](#overview) +- [Components](#components) +- [Quick Start](#quick-start) +- [Configuration](#configuration) +- [Detailed Usage](#detailed-usage) +- [Multiple Realms Setup](#multiple-realms-setup) +- [Service Management](#service-management) +- [Troubleshooting](#troubleshooting) + +## 🎯 Overview + +The AzerothCore startup scripts provide multiple approaches to running server instances: + +1. **Development/Testing**: Simple execution for debugging and development +2. **Production with Restarts**: Automatic restart on crashes with crash detection +3. **Background Services**: Production-ready service management with PM2 or systemd +4. **Session Management**: Interactive console access via tmux/screen + +All scripts are integrated into the `acore.sh` dashboard for easy access. + +### 📦 Automatic Deployment + +**Important**: When you compile AzerothCore using the acore dashboard (`./acore.sh compiler build`), all startup scripts are automatically copied from `apps/startup-scripts/src/` to your `bin/` folder. This means: + +- ✅ **Portable Deployment**: You can copy the entire `bin/` folder to different servers +- ✅ **Self-Contained**: All restart and service management tools travel with your binaries +- ✅ **No Additional Setup**: Scripts work immediately after deployment +- ✅ **Production Ready**: Deploy to production servers without needing the full source code + +This makes it easy to deploy your compiled binaries along with the management scripts to production environments where you may not have the full AzerothCore source code. + +## 🔧 Components + +### Core Scripts + +- **`run-engine`**: Advanced script with session management and configuration priority +- **`simple-restarter`**: Wrapper around starter with restart functionality (legacy compatibility) +- **`starter`**: Basic binary execution with optional GDB support +- **`service-manager.sh`**: Production service management with PM2/systemd + +### Configuration + +- **`conf.sh.dist`**: Default configuration template +- **`conf.sh`**: User configuration (create from .dist) +- **`gdb.conf`**: GDB debugging configuration + +### Examples + +- **`restarter-auth.sh`**: Auth server restart example +- **`restarter-world.sh`**: World server restart example +- **`starter-auth.sh`**: Auth server basic start example +- **`starter-world.sh`**: World server basic start example + +## 🚀 Quick Start + +### 1. Basic Server Start (Development) + +```bash +# Start authserver directly +./starter /path/to/bin authserver + +# Start worldserver with config +./starter /path/to/bin worldserver "" /path/to/worldserver.conf +``` + +### 2. Start with Auto-Restart + +```bash +# Using simple-restarter (legacy) +./simple-restarter /path/to/bin authserver + +# Using run-engine (recommended) +./run-engine restart authserver --bin-path /path/to/bin +``` + +### 3. Production Service Management + +```bash +# Create and start a service +./service-manager.sh create auth authserver --bin-path /path/to/bin + +# List all services +./service-manager.sh list + +# Stop a service +./service-manager.sh stop auth +``` + +### 4. Using acore.sh Dashboard + +```bash +# Interactive dashboard +./acore.sh + +# Direct commands +./acore.sh run-authserver # Start authserver with restart +./acore.sh run-worldserver # Start worldserver with restart +./acore.sh service-manager # Access service manager +``` + +## ⚙️ Configuration + +### Configuration Priority (Highest to Lowest) + +1. **`conf.sh`** - User configuration file +2. **Command line arguments** - Runtime parameters +3. **Environment variables** - `RUN_ENGINE_*` variables +4. **`conf.sh.dist`** - Default configuration + +### Creating Configuration + +```bash +# Copy default configuration +cp scripts/conf.sh.dist scripts/conf.sh + +# Edit your configuration +nano scripts/conf.sh +``` + +### Key Configuration Options + +```bash +# Binary settings +export BINPATH="/path/to/azerothcore/bin" +export SERVERBIN="worldserver" # or "authserver" +export CONFIG="/path/to/worldserver.conf" + +# Session management +export SESSION_MANAGER="tmux" # none|auto|tmux|screen +export SESSION_NAME="ac-world" + +# Debugging +export GDB_ENABLED="1" # 0 or 1 +export GDB="/path/to/gdb.conf" + +# Logging +export LOGS_PATH="/path/to/logs" +export CRASHES_PATH="/path/to/crashes" +export LOG_PREFIX_NAME="realm1" +``` + +## 📖 Detailed Usage + +### 1. Run Engine + +The `run-engine` is the most advanced script with multiple operation modes: + +#### Basic Execution +```bash +# Start server once +./run-engine start worldserver --bin-path /path/to/bin + +# Start with configuration file +./run-engine start worldserver --config ./conf-world.sh + +# Start with specific server config +./run-engine start worldserver --server-config /path/to/worldserver.conf +``` + +#### Restart Mode +```bash +# Automatic restart on crash +./run-engine restart worldserver --bin-path /path/to/bin + +# Restart with session management +./run-engine restart worldserver --session-manager tmux +``` + +#### Session Management +```bash +# Start in tmux session +./run-engine start worldserver --session-manager tmux + +# Attach to existing session +tmux attach-session -t worldserver + +# Start in screen session +./run-engine start worldserver --session-manager screen + +# Attach to screen session +screen -r worldserver +``` + +#### Configuration Options +```bash +./run-engine restart worldserver \ + --bin-path /path/to/bin \ + --server-config /path/to/worldserver.conf \ + --session-manager tmux \ + --gdb-enabled 1 \ + --logs-path /path/to/logs \ + --crashes-path /path/to/crashes +``` + +### 2. Simple Restarter + +Legacy-compatible wrapper with restart functionality: + +```bash +# Basic restart +./simple-restarter /path/to/bin worldserver + +# With full parameters +./simple-restarter \ + /path/to/bin \ + worldserver \ + ./gdb.conf \ + /path/to/worldserver.conf \ + /path/to/system.log \ + /path/to/system.err \ + 1 \ + /path/to/crashes +``` + +**Parameters:** +1. Binary path (required) +2. Binary name (required) +3. GDB configuration file (optional) +4. Server configuration file (optional) +5. System log file (optional) +6. System error file (optional) +7. GDB enabled flag (0/1, optional) +8. Crashes directory path (optional) + +### 3. Starter + +Basic execution script without restart functionality: + +```bash +# Simple start +./starter /path/to/bin worldserver + +# With GDB debugging +./starter /path/to/bin worldserver ./gdb.conf /path/to/worldserver.conf "" "" 1 +``` + +### 4. Service Manager + +Production-ready service management: + +#### Creating Services +```bash +# Auto-detect provider (PM2 or systemd) +./service-manager.sh create auth authserver --bin-path /path/to/bin + +# Force PM2 +./service-manager.sh create world worldserver --provider pm2 --bin-path /path/to/bin + +# Force systemd +./service-manager.sh create world worldserver --provider systemd --bin-path /path/to/bin +``` + +#### Service Operations +```bash +# Start/stop services +./service-manager.sh start auth +./service-manager.sh stop world +./service-manager.sh restart auth + +# View logs +./service-manager.sh logs world +./service-manager.sh logs world --follow + +# Attach to console (interactive) +./service-manager.sh attach world + +# List services +./service-manager.sh list +./service-manager.sh list pm2 +./service-manager.sh list systemd + +# Delete service +./service-manager.sh delete auth +``` + +#### Service Configuration +```bash +# Update service settings +./service-manager.sh update world --session-manager screen --gdb-enabled 1 + +# Edit configuration +./service-manager.sh edit world +``` + +## 🌍 Multiple Realms Setup + +### Method 1: Using Service Manager (Recommended) + +```bash +# Create multiple world server instances +./service-manager.sh create world1 worldserver \ + --bin-path /path/to/bin \ + --server-config /path/to/worldserver-realm1.conf + +./service-manager.sh create world2 worldserver \ + --bin-path /path/to/bin \ + --server-config /path/to/worldserver-realm2.conf + +# Single auth server for all realms +./service-manager.sh create auth authserver \ + --bin-path /path/to/bin \ + --server-config /path/to/authserver.conf +``` + +### Method 2: Using Run Engine with Different Configurations + +Create separate configuration files for each realm: + +**conf-realm1.sh:** +```bash +export BINPATH="/path/to/bin" +export SERVERBIN="worldserver" +export CONFIG="/path/to/worldserver-realm1.conf" +export SESSION_NAME="ac-realm1" +export LOG_PREFIX_NAME="realm1" +export LOGS_PATH="/path/to/logs/realm1" +``` + +**conf-realm2.sh:** +```bash +export BINPATH="/path/to/bin" +export SERVERBIN="worldserver" +export CONFIG="/path/to/worldserver-realm2.conf" +export SESSION_NAME="ac-realm2" +export LOG_PREFIX_NAME="realm2" +export LOGS_PATH="/path/to/logs/realm2" +``` + +Start each realm: +```bash +./run-engine restart worldserver --config ./conf-realm1.sh +./run-engine restart worldserver --config ./conf-realm2.sh +``` + +### Method 3: Using Examples with Custom Configurations + +Copy and modify the example scripts: + +```bash +# Copy examples +cp examples/restarter-world.sh restarter-realm1.sh +cp examples/restarter-world.sh restarter-realm2.sh + +# Edit each script to point to different configuration files +# Then run: +./restarter-realm1.sh +./restarter-realm2.sh +``` + +## 🛠️ Service Management + +### PM2 Services + +When using PM2 as the service provider: + +```bash +# PM2-specific commands +pm2 list # List all PM2 processes +pm2 logs auth # View logs +pm2 monit # Real-time monitoring +pm2 restart auth # Restart service +pm2 delete auth # Remove service + +# Save PM2 configuration +pm2 save +pm2 startup # Auto-start on boot +``` + +### Systemd Services + +When using systemd as the service provider: + +```bash +# Systemd commands +systemctl --user status acore-auth # Check status +systemctl --user logs acore-auth # View logs +systemctl --user restart acore-auth # Restart +systemctl --user enable acore-auth # Enable auto-start + +# For system services (requires sudo) +sudo systemctl status acore-auth +sudo systemctl enable acore-auth +``` + +### Session Management in Services + +Services can be configured with session managers for interactive access: + +```bash +# Create service with tmux +./service-manager.sh create world worldserver \ + --bin-path /path/to/bin \ + --session-manager tmux + +# Attach to the session +./service-manager.sh attach world +# or directly: +tmux attach-session -t worldserver +``` + +## 🎮 Integration with acore.sh Dashboard + +The startup scripts are fully integrated into the AzerothCore dashboard: + +### Direct Commands + +```bash +# Run servers with simple restart (development/testing) +./acore.sh run-worldserver # Option 11 or 'rw' +./acore.sh run-authserver # Option 12 or 'ra' + +# Access service manager (production) +./acore.sh service-manager # Option 15 or 'sm' + +# Examples: +./acore.sh rw # Quick worldserver start +./acore.sh ra # Quick authserver start +./acore.sh sm create auth authserver --bin-path /path/to/bin +``` + +### What Happens Behind the Scenes + +- **run-worldserver/run-authserver**: Calls `simple-restarter` with appropriate binary +- **service-manager**: Provides full access to the service management interface +- Scripts automatically use the correct binary path from your build configuration + +## 🐛 Troubleshooting + +### Common Issues + +#### 1. Binary Not Found +```bash +Error: Binary '/path/to/bin/worldserver' not found +``` +**Solution**: Check binary path and ensure servers are compiled +```bash +# Check if binary exists +ls -la /path/to/bin/worldserver + +# Compile if needed +./acore.sh compiler build +``` + +#### 2. Configuration File Issues +```bash +Error: Configuration file not found +``` +**Solution**: Create configuration from template +```bash +cp scripts/conf.sh.dist scripts/conf.sh +# Edit conf.sh with correct paths +``` + +#### 3. Session Manager Not Available +```bash +Warning: tmux not found, falling back to direct execution +``` +**Solution**: Install required session manager +```bash +# Ubuntu/Debian +sudo apt install tmux screen + +# CentOS/RHEL +sudo yum install tmux screen +``` + +#### 4. Permission Issues (systemd) +```bash +Failed to create systemd service +``` +**Solution**: Check user permissions or use --system flag +```bash +# For user services (no sudo required) +./service-manager.sh create auth authserver --bin-path /path/to/bin + +# For system services (requires sudo) +./service-manager.sh create auth authserver --bin-path /path/to/bin --system +``` + +#### 5. PM2 Not Found +```bash +Error: PM2 is not installed +``` +**Solution**: Install PM2 +```bash +npm install -g pm2 +# or +sudo npm install -g pm2 +``` + + diff --git a/apps/startup-scripts/conf.sh.dist b/apps/startup-scripts/conf.sh.dist deleted file mode 100644 index 3a2c395782..0000000000 --- a/apps/startup-scripts/conf.sh.dist +++ /dev/null @@ -1,50 +0,0 @@ -# enable/disable GDB execution -export GDB_ENABLED=0 - -# [optional] gdb file -# default: gdb.conf -export GDB="" - -# directory where binary are stored -export BINPATH="" - -# Put here the pid you configured on your worldserver.conf file -# needed when GDB_ENABLED=1 -export SERVERPID="" - -# path to configuration file (including the file name) -# ex: /home/user/azerothcore/etc/worldserver.conf -export CONFIG="" - -# path of log files -# needed by restarter to store its logs -export LOGS_PATH=""; - -# exec name -# ex: worldserver -export SERVERBIN="" - -# prefix name for log files -# to avoid collision with other restarters -export LOG_PREFIX_NAME="" - -# [optional] name of screen service -# if no specified, screen util won't be used -export SCREEN_NAME="" - -# [optional] overwrite default screen options: -A -m -d -S -# WARNING: if you are running it under a systemd service -# please do not remove -m -d arguments from screen if are you using it, -# or keep WITH_CONSOLE=0 . -# otherwise the journald-logging system will take 100% of CPU slowing -# down the whole machine. It's because a systemd service should have -# low console output. -export SCREEN_OPTIONS="" - -# enable/disable it to show the output -# within console, if disable the output will be redirect to -# logging files -# -export WITH_CONSOLE=0 - - diff --git a/apps/startup-scripts/examples/restarter-auth.sh b/apps/startup-scripts/examples/restarter-auth.sh deleted file mode 100644 index 61ea8b9cfa..0000000000 --- a/apps/startup-scripts/examples/restarter-auth.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -PATH_RUNENGINE="./" - -source "$PATH_RUNENGINE/run-engine" - -# you must create your conf -# copying conf.sh.dist -# and renaming as below -source "./conf-auth.sh" - -restarter - - diff --git a/apps/startup-scripts/examples/restarter-world.sh b/apps/startup-scripts/examples/restarter-world.sh deleted file mode 100644 index 9b34e114e0..0000000000 --- a/apps/startup-scripts/examples/restarter-world.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -PATH_RUNENGINE="./" - -source "$PATH_RUNENGINE/run-engine" - -# you must create your conf -# copying conf.sh.dist -# and renaming as below -source "./conf-world.sh" - -restarter - - diff --git a/apps/startup-scripts/examples/starter-auth.sh b/apps/startup-scripts/examples/starter-auth.sh deleted file mode 100644 index 734cfb5a22..0000000000 --- a/apps/startup-scripts/examples/starter-auth.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -PATH_RUNENGINE="./" - -source "$PATH_RUNENGINE/run-engine" - -# you must create your conf -# copying conf.sh.dist -# and renaming as below -source "./conf-auth.sh" - -starter - diff --git a/apps/startup-scripts/examples/starter-world.sh b/apps/startup-scripts/examples/starter-world.sh deleted file mode 100644 index 697a2c85ac..0000000000 --- a/apps/startup-scripts/examples/starter-world.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -PATH_RUNENGINE="./" - -source "$PATH_RUNENGINE/run-engine" - -# you must create your conf -# copying conf.sh.dist -# and renaming as below -source "./conf-world.sh" - -starter - - diff --git a/apps/startup-scripts/run-engine b/apps/startup-scripts/run-engine deleted file mode 100644 index bc1c04a022..0000000000 --- a/apps/startup-scripts/run-engine +++ /dev/null @@ -1,115 +0,0 @@ -export RUN_ENGINE_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -# load default conf -if [ -e "$RUN_ENGINE_PATH/conf.dist" ]; then - source "$RUN_ENGINE_PATH/conf.sh.dist" -fi - -function finish { - if [ ! -z "$SCREEN_NAME" ]; then - screen -X -S "$SCREEN_NAME" quit - fi -} - -# disabled for now, but could be useful if we want -# shutdown the process if restarter crashes for some reason -# trap finish EXIT - -function configureFiles() { - TRACE_BEGIN_STRING="SIGSEGV" - TRACE_FILE="$LOGS_PATH/"$LOG_PREFIX_NAME"_trace.log" - ERR_FILE="$LOGS_PATH/"$LOG_PREFIX_NAME"_error.log" - SYSLOG="$LOGS_PATH/"$LOG_PREFIX_NAME"_system.log" - SYSERR="$LOGS_PATH/"$LOG_PREFIX_NAME"_system.err" - LINKS_FILE="$LOGS_PATH/"$LOG_PREFIX_NAME"_crash_links.link" -} - -function checkStatus() { - local ret=1 - # wipe do : destroy old screens + ls - #screen -wipe - #if screen -ls $1 | grep -q "No Sockets found" - #then - # return 0 - #fi - - local gdbres=$(pgrep -f "gdb -x $GDB --batch $SERVERBIN") - if [[ $GDB_ENABLED -eq 1 && ! -z $gdbres ]]; then - return 1 - fi - - # - # This is a specific check for Azeroth Core in case of screen failure - # It is possible since same binary file cannot be launched with same configuration file - # This is an extra check - # - local binres=$(pgrep -f "$SERVERBIN -c $CONFIG") - if [ ! -z $binres ]; then - return 1 - fi - - return 0 -} - -function run() { - echo $1 - if [ ! -z $1 ]; then - local OPTIONS="-A -m -d -S" - if [ ! -z "$SCREEN_OPTIONS" ]; then - OPTIONS=$SCREEN_OPTIONS - fi - - echo "> Starting with screen ( screen $OPTIONS )" - - screen $OPTIONS $1 "$RUN_ENGINE_PATH/starter" $2 $3 "$4" "$5" "$6" $7 "$BINPATH/crashes" - else - $RUN_ENGINE_PATH/starter $2 $3 "$4" "$5" "$6" $7 "$BINPATH/crashes" - fi -} - -function starter() { - cd $BINPATH - - mkdir -p "$LOGS_PATH" - mkdir -p "$BINPATH"/crashes - - configureFiles - - run "$SCREEN_NAME" "$SERVERBIN" "$GDB" "$CONFIG" "$SYSLOG" "$SYSERR" "$GDB_ENABLED" -} - - -function restarter() { - cd $BINPATH - - mkdir -p "$LOGS_PATH" - mkdir -p "$BINPATH"/crashes - - configureFiles - - if [ ! -f $TRACE_FILE ]; then - touch $TRACE_FILE - fi - - while : - do - if checkStatus $SCREEN_NAME; then - DATE=$(date) - echo "Restarting $SCREEN_NAME Core blizz($DATE)" - if [ $GDB_ENABLED -eq 1 ]; then - echo "GDB enabled" - grep -B 10 -A 1800 "$TRACE_BEGIN_STRING" "$SYSLOG" >> "$TRACE_FILE" - cat "$SYSERR" > "$ERR_FILE" - run "$SCREEN_NAME" "$SERVERBIN" "$GDB" "$CONFIG" "$SYSLOG" "$SYSERR" 1 - fi - - if [ $GDB_ENABLED -eq 0 ]; then - echo "GDB disabled" - run "$SCREEN_NAME" "$SERVERBIN" null "$CONFIG" null null 0 - fi - fi - - sleep 10 - done -} - diff --git a/apps/startup-scripts/simple-restarter b/apps/startup-scripts/simple-restarter deleted file mode 100755 index 6415c0a449..0000000000 --- a/apps/startup-scripts/simple-restarter +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash - -#PARAMETER 1: directory -#PARAMETER 2: binary file -#PARAMETER 3: gdb on/off - -bin_path="${1:-$AC_RESTARTER_BINPATH}" -bin_file="${2:-$AC_RESTARTER_BINFILE}" -with_gdb="${3:-$AC_RESTARTER_WITHGDB}" - -CURRENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd ) - -_instant_crash_count=0 -_restart_count=0 - -if [ "$#" -ne 2 ]; then - echo "Usage: $0 path filename" - echo "Example: $0 $HOME/azerothcore/bin worldserver" - exit 1 -fi - -while true -do - if [ ! -f "$bin_path/$bin_file" ]; then - echo "$bin_path/$bin_file doesn't exists!" - exit 1 - fi - - STARTING_TIME=$(date +%s) - - cd "$bin_path"; - - if [ "$with_gdb" = true ]; then - echo "Running with GDB enabled" - gdb -x "$CURRENT_PATH/gdb.conf" --batch "./$bin_file" - else - echo "Running without GDB" - "./$bin_file" - fi - - _exit_code=$? - - echo "exit code: $_exit_code" - # stop restarter on SIGKILL (disabled for now) - # 128 + 9 (SIGKILL) - #if [ $_exit_code -eq 137 ]; then - # echo "$bin_file has been killed" - # exit 0 - #fi - - echo "$bin_file terminated, restarting..." - - ENDING_TIME=$(date +%s) - DIFFERENCE=$(( $ENDING_TIME - $STARTING_TIME )) - - ((_restart_count++)) - echo "$bin_file Terminated after $DIFFERENCE seconds, termination count: : $_restart_count" - - if [ $DIFFERENCE -lt 10 ]; then - # increment instant crash if runtime is lower than 10 seconds - ((_instant_crash_count++)) - else - _instant_crash_count=0 # reset count - fi - - if [ $_instant_crash_count -gt 5 ]; then - echo "$bin_file Restarter exited. Infinite crash loop prevented. Please check your system" - exit 1 - fi -done diff --git a/apps/startup-scripts/src/.gitignore b/apps/startup-scripts/src/.gitignore new file mode 100644 index 0000000000..cd3d225360 --- /dev/null +++ b/apps/startup-scripts/src/.gitignore @@ -0,0 +1 @@ +logs
\ No newline at end of file diff --git a/apps/startup-scripts/src/conf.sh.dist b/apps/startup-scripts/src/conf.sh.dist new file mode 100644 index 0000000000..69fbeadb9b --- /dev/null +++ b/apps/startup-scripts/src/conf.sh.dist @@ -0,0 +1,57 @@ +# AzerothCore Run Engine Default Configuration +# This file contains default values that can be overridden by environment variables +# Priority order: conf.sh > environment variables > conf.sh.dist (this file) + +# Enable/disable GDB execution +export GDB_ENABLED="${RUN_ENGINE_GDB_ENABLED:-0}" + +# [optional] GDB configuration file +# default: gdb.conf +export GDB="${RUN_ENGINE_GDB:-}" + +# Directory where binaries are stored +export BINPATH="${RUN_ENGINE_BINPATH:-}" + +# Server binary name (e.g., worldserver, authserver) +export SERVERBIN="${RUN_ENGINE_SERVERBIN:-}" + +# Path to server configuration file (including the file name) +# ex: /home/user/azerothcore/etc/worldserver.conf +export CONFIG="${RUN_ENGINE_CONFIG:-}" + +# Session manager to use: none|auto|tmux|screen +# auto will detect the best available option +export SESSION_MANAGER="${RUN_ENGINE_SESSION_MANAGER:-none}" + +# Default session manager (fallback when SESSION_MANAGER is not set) +export DEFAULT_SESSION_MANAGER="${RUN_ENGINE_DEFAULT_SESSION_MANAGER:-none}" + +# Path of the crashes directory +# If not specified, it will be created in the same directory as logs named "crashes" +export CRASHES_PATH="${RUN_ENGINE_CRASHES_PATH:-}" + +# Path of log files directory +export LOGS_PATH="${RUN_ENGINE_LOGS_PATH:-}" + +# Prefix name for log files to avoid collision with other instances +export LOG_PREFIX_NAME="${RUN_ENGINE_LOG_PREFIX_NAME:-}" + +# [optional] Name of session (tmux session or screen session) +# If not specified, a default name will be generated based on server binary +export SESSION_NAME="${RUN_ENGINE_SESSION_NAME:-}" + +# [optional] Screen-specific options: -A -m -d -S +# WARNING: if you are running it under a systemd service +# please do not remove -m -d arguments from screen if you are using it, +# or keep WITH_CONSOLE=0. Otherwise the journald-logging system will take +# 100% of CPU slowing down the whole machine. +export SCREEN_OPTIONS="${RUN_ENGINE_SCREEN_OPTIONS:-}" + +# Enable/disable console output +# If disabled, output will be redirected to logging files +export WITH_CONSOLE="${RUN_ENGINE_WITH_CONSOLE:-0}" + +# Server PID (needed when GDB_ENABLED=1) +export SERVERPID="${RUN_ENGINE_SERVERPID:-}" + + diff --git a/apps/startup-scripts/src/examples/restarter-auth.sh b/apps/startup-scripts/src/examples/restarter-auth.sh new file mode 100755 index 0000000000..9557ebdf37 --- /dev/null +++ b/apps/startup-scripts/src/examples/restarter-auth.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# AzerothCore Auth Server Restarter Example +# This example shows how to use the run-engine with restart functionality for authserver + +PATH_RUNENGINE="./" +CONFIG_FILE="./conf-auth.sh" + +# Method 1: Using configuration file (recommended) +if [ -f "$CONFIG_FILE" ]; then + echo "Starting authserver with restart loop using config file: $CONFIG_FILE" + source "$CONFIG_FILE" + "$PATH_RUNENGINE/run-engine" restart "$SERVERBIN" --config "$CONFIG_FILE" +else + echo "Error: Configuration file not found: $CONFIG_FILE" + echo "Please create $CONFIG_FILE by copying and modifying conf.sh.dist" + echo "Make sure to set: export SERVERBIN=\"authserver\"" + echo "" + echo "Alternative: Start with binary path directly" + echo "Example: $PATH_RUNENGINE/run-engine restart /path/to/bin/authserver" + echo "Example: $PATH_RUNENGINE/run-engine restart authserver # if in PATH" + exit 1 +fi + +# Method 2: Direct binary path (full path) +# Uncomment the line below to start with full binary path +# +# "$PATH_RUNENGINE/run-engine" restart /home/user/azerothcore/bin/authserver --server-config /path/to/authserver.conf + +# Method 3: Binary name only (system PATH) +# Uncomment the line below if authserver is in your system PATH +# +# "$PATH_RUNENGINE/run-engine" restart authserver --server-config /path/to/authserver.conf + +# Method 4: With session manager (tmux/screen) +# Uncomment the line below to use tmux session +# +# "$PATH_RUNENGINE/run-engine" restart authserver --session-manager tmux --server-config /path/to/authserver.conf + +# Method 5: Environment variables only +# Uncomment the lines below for environment variable configuration +# +# export RUN_ENGINE_BINPATH="/path/to/your/bin" +# export RUN_ENGINE_SERVERBIN="authserver" +# export RUN_ENGINE_CONFIG="/path/to/authserver.conf" +# "$PATH_RUNENGINE/run-engine" restart authserver + + diff --git a/apps/startup-scripts/src/examples/restarter-world.sh b/apps/startup-scripts/src/examples/restarter-world.sh new file mode 100755 index 0000000000..2649b3f84d --- /dev/null +++ b/apps/startup-scripts/src/examples/restarter-world.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# AzerothCore World Server Restarter Example +# This example shows how to use the run-engine with restart functionality for worldserver + +PATH_RUNENGINE="./" +CONFIG_FILE="./conf-world.sh" + +# Method 1: Using configuration file (recommended) +if [ -f "$CONFIG_FILE" ]; then + echo "Starting worldserver with restart loop using config file: $CONFIG_FILE" + "$PATH_RUNENGINE/run-engine" restart "$SERVERBIN" --config "$CONFIG_FILE" +else + echo "Error: Configuration file not found: $CONFIG_FILE" + echo "Please create $CONFIG_FILE by copying and modifying conf.sh.dist" + echo "Make sure to set: export SERVERBIN=\"worldserver\"" + echo "" + echo "Alternative: Start with binary path directly" + echo "Example: $PATH_RUNENGINE/run-engine restart /path/to/bin/worldserver" + echo "Example: $PATH_RUNENGINE/run-engine restart worldserver # if in PATH" + exit 1 +fi + +# Method 2: Direct binary path (full path) +# Uncomment the line below to start with full binary path +# +# "$PATH_RUNENGINE/run-engine" restart /home/user/azerothcore/bin/worldserver --server-config /path/to/worldserver.conf + +# Method 3: Binary name only (system PATH) +# Uncomment the line below if worldserver is in your system PATH +# +# "$PATH_RUNENGINE/run-engine" restart worldserver --server-config /path/to/worldserver.conf + +# Method 4: With session manager (tmux/screen) +# Uncomment the line below to use tmux session +# +# "$PATH_RUNENGINE/run-engine" restart worldserver --session-manager tmux --server-config /path/to/worldserver.conf + +# Method 5: Environment variables only +# Uncomment the lines below for environment variable configuration +# +# export RUN_ENGINE_BINPATH="/path/to/your/bin" +# export RUN_ENGINE_SERVERBIN="worldserver" +# export RUN_ENGINE_CONFIG="/path/to/worldserver.conf" +# "$PATH_RUNENGINE/run-engine" restart worldserver + + diff --git a/apps/startup-scripts/src/examples/starter-auth.sh b/apps/startup-scripts/src/examples/starter-auth.sh new file mode 100755 index 0000000000..52fcb384c8 --- /dev/null +++ b/apps/startup-scripts/src/examples/starter-auth.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# AzerothCore Auth Server Starter Example +# This example shows how to use the run-engine to start authserver without restart loop + +PATH_RUNENGINE="./" +CONFIG_FILE="./conf-auth.sh" + +# Method 1: Using configuration file (recommended) +if [ -f "$CONFIG_FILE" ]; then + echo "Starting authserver (single run) with config file: $CONFIG_FILE" + "$PATH_RUNENGINE/run-engine" start "$SERVERBIN" --config "$CONFIG_FILE" +else + echo "Error: Configuration file not found: $CONFIG_FILE" + echo "Please create $CONFIG_FILE by copying and modifying conf.sh.dist" + echo "Make sure to set: export SERVERBIN=\"authserver\"" + echo "" + echo "Alternative: Start with binary path directly" + echo "Example: $PATH_RUNENGINE/run-engine start /path/to/bin/authserver" + echo "Example: $PATH_RUNENGINE/run-engine start authserver # if in PATH" + exit 1 +fi + +# Method 2: Direct binary path (full path) +# Uncomment the line below to start with full binary path +# +# "$PATH_RUNENGINE/run-engine" start /home/user/azerothcore/bin/authserver --server-config /path/to/authserver.conf + +# Method 3: Binary name only (system PATH) +# Uncomment the line below if authserver is in your system PATH +# +# "$PATH_RUNENGINE/run-engine" start authserver --server-config /path/to/authserver.conf + +# Method 4: With session manager (tmux/screen) +# Uncomment the line below to use tmux session +# +# "$PATH_RUNENGINE/run-engine" start authserver --session-manager tmux --server-config /path/to/authserver.conf + +# Method 5: Environment variables only +# Uncomment the lines below for environment variable configuration +# +# export RUN_ENGINE_BINPATH="/path/to/your/bin" +# export RUN_ENGINE_SERVERBIN="authserver" +# export RUN_ENGINE_CONFIG="/path/to/authserver.conf" +# "$PATH_RUNENGINE/run-engine" start authserver + diff --git a/apps/startup-scripts/src/examples/starter-world.sh b/apps/startup-scripts/src/examples/starter-world.sh new file mode 100755 index 0000000000..1eb1c4d32a --- /dev/null +++ b/apps/startup-scripts/src/examples/starter-world.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# AzerothCore World Server Starter Example +# This example shows how to use the run-engine to start worldserver without restart loop + +PATH_RUNENGINE="./" +CONFIG_FILE="./conf-world.sh" + +# Method 1: Using configuration file (recommended) +if [ -f "$CONFIG_FILE" ]; then + echo "Starting worldserver (single run) with config file: $CONFIG_FILE" + "$PATH_RUNENGINE/run-engine" start "$SERVERBIN" --config "$CONFIG_FILE" +else + echo "Error: Configuration file not found: $CONFIG_FILE" + echo "Please create $CONFIG_FILE by copying and modifying conf.sh.dist" + echo "Make sure to set: export SERVERBIN=\"worldserver\"" + echo "" + echo "Alternative: Start with binary path directly" + echo "Example: $PATH_RUNENGINE/run-engine start /path/to/bin/worldserver" + echo "Example: $PATH_RUNENGINE/run-engine start worldserver # if in PATH" + exit 1 +fi + +# Method 2: Direct binary path (full path) +# Uncomment the line below to start with full binary path +# +# "$PATH_RUNENGINE/run-engine" start /home/user/azerothcore/bin/worldserver --server-config /path/to/worldserver.conf + +# Method 3: Binary name only (system PATH) +# Uncomment the line below if worldserver is in your system PATH +# +# "$PATH_RUNENGINE/run-engine" start worldserver --server-config /path/to/worldserver.conf + +# Method 4: With session manager (tmux/screen) +# Uncomment the line below to use tmux session +# +# "$PATH_RUNENGINE/run-engine" start worldserver --session-manager tmux --server-config /path/to/worldserver.conf + +# Method 5: Environment variables only +# Uncomment the lines below for environment variable configuration +# +# export RUN_ENGINE_BINPATH="/path/to/your/bin" +# export RUN_ENGINE_SERVERBIN="worldserver" +# export RUN_ENGINE_CONFIG="/path/to/worldserver.conf" +# "$PATH_RUNENGINE/run-engine" start worldserver + + diff --git a/apps/startup-scripts/gdb.conf b/apps/startup-scripts/src/gdb.conf index d6802a56b6..d6802a56b6 100644 --- a/apps/startup-scripts/gdb.conf +++ b/apps/startup-scripts/src/gdb.conf diff --git a/apps/startup-scripts/src/run-engine b/apps/startup-scripts/src/run-engine new file mode 100755 index 0000000000..2860339a5e --- /dev/null +++ b/apps/startup-scripts/src/run-engine @@ -0,0 +1,467 @@ +#!/usr/bin/env bash + +# AzerothCore Run Engine +# Advanced script for running AzerothCore services with session management and restart capabilities +# +# This script can be sourced to provide functions or executed directly with parameters +# +# Configuration Priority Order (highest to lowest): +# 1. conf.sh - User configuration file (highest priority) +# 2. Command line arguments (--config, --server-config, etc.) +# 3. Environment variables (RUN_ENGINE_*) +# 4. conf.sh.dist - Default configuration (lowest priority) +# +# Environment Variables: +# RUN_ENGINE_CONFIG_FILE - Path to temporary configuration file (optional) +# RUN_ENGINE_SESSION_MANAGER - Session manager (none|auto|tmux|screen, default: auto) +# RUN_ENGINE_BINPATH - Binary directory path +# RUN_ENGINE_SERVERBIN - Server binary name (worldserver|authserver) +# RUN_ENGINE_CONFIG - Server configuration file path +# RUN_ENGINE_LOGS_PATH - Directory for log files +# RUN_ENGINE_CRASHES_PATH - Directory for crash dumps +# RUN_ENGINE_SESSION_NAME - Session name for tmux/screen + +export RUN_ENGINE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Configuration priority order: +# 1. conf.sh (highest priority - user overrides) +# 2. Environment variables (RUN_ENGINE_*) +# 3. conf.sh.dist (lowest priority - defaults) + +# Load default configuration first (sets defaults from environment variables) +if [ -e "$RUN_ENGINE_PATH/conf.sh.dist" ]; then + source "$RUN_ENGINE_PATH/conf.sh.dist" +fi + +# Load user configuration if exists (this takes priority over everything) +if [ -e "$RUN_ENGINE_PATH/conf.sh" ]; then + source "$RUN_ENGINE_PATH/conf.sh" +fi + +# Load configuration +function load_config() { + local config_file="$1" + + # If a specific config file is provided via command line, load it + # This allows temporary overrides for specific runs + if [ -n "$config_file" ] && [ -e "$config_file" ]; then + echo "Loading configuration from: $config_file" + source "$config_file" + elif [ -n "$RUN_ENGINE_CONFIG_FILE" ] && [ -e "$RUN_ENGINE_CONFIG_FILE" ]; then + echo "Loading configuration from environment: $RUN_ENGINE_CONFIG_FILE" + source "$RUN_ENGINE_CONFIG_FILE" + fi + + # Final override with any remaining environment variables + # This ensures that even after loading config files, environment variables take precedence + BINPATH="${RUN_ENGINE_BINPATH:-$BINPATH}" + SERVERBIN="${RUN_ENGINE_SERVERBIN:-$SERVERBIN}" + CONFIG="${RUN_ENGINE_CONFIG:-$CONFIG}" + SESSION_MANAGER="${RUN_ENGINE_SESSION_MANAGER:-$SESSION_MANAGER}" + LOGS_PATH="${RUN_ENGINE_LOGS_PATH:-$LOGS_PATH}" + CRASHES_PATH="${RUN_ENGINE_CRASHES_PATH:-$CRASHES_PATH}" +} + +# Detect available session manager +function detect_session_manager() { + if command -v tmux >/dev/null 2>&1; then + echo "tmux" + elif command -v screen >/dev/null 2>&1; then + echo "screen" + else + echo "none" + fi +} + +# Determine which session manager to use +function get_session_manager() { + local requested="$1" + + case "$requested" in + "none") + echo "none" + ;; + "auto") + detect_session_manager + ;; + "tmux") + if command -v tmux >/dev/null 2>&1; then + echo "tmux" + else + echo "error" + fi + ;; + "screen") + if command -v screen >/dev/null 2>&1; then + echo "screen" + else + echo "error" + fi + ;; + *) + echo "none" + ;; + esac +} + +# Configure log files +function configure_files() { + TRACE_BEGIN_STRING="SIGSEGV" + TRACE_FILE="$LOGS_PATH/${LOG_PREFIX_NAME}_trace.log" + ERR_FILE="$LOGS_PATH/${LOG_PREFIX_NAME}_error.log" + SYSLOG="$LOGS_PATH/${LOG_PREFIX_NAME}_system.log" + SYSERR="$LOGS_PATH/${LOG_PREFIX_NAME}_system.err" + LINKS_FILE="$LOGS_PATH/${LOG_PREFIX_NAME}_crash_links.link" +} + +# Check if service is running +function check_status() { + local session_name="$1" + local ret=1 + + # Check for GDB process + local gdbres=$(pgrep -f "gdb.*--batch.*$SERVERBIN") + if [[ "$GDB_ENABLED" -eq 1 && -n "$gdbres" ]]; then + return 1 + fi + + # Check for binary process + local binres=$(pgrep -f "$SERVERBIN -c $CONFIG") + if [ -n "$binres" ]; then + return 1 + fi + + # Check session manager + if [ -n "$session_name" ]; then + case "$(get_session_manager "${SESSION_MANAGER:-auto}")" in + "tmux") + tmux has-session -t "$session_name" 2>/dev/null && return 1 + ;; + "screen") + screen -ls "$session_name" 2>/dev/null | grep -q "$session_name" && return 1 + ;; + esac + fi + + return 0 +} + +# Run with session manager +function run_with_session() { + local session_manager="$1" + local session_name="$2" + local wrapper="$3" + shift 3 + local args=("$@") + + if [ "$wrapper" = "simple-restarter" ]; then + script_path="$RUN_ENGINE_PATH/simple-restarter" + else + script_path="$RUN_ENGINE_PATH/starter" + fi + + case "$session_manager" in + "tmux") + echo "> Starting with tmux session: $session_name - attach with 'tmux attach -t $session_name'" + tmux new-session -d -s "$session_name" -- "$script_path" "${args[@]}" + ;; + "screen") + local OPTIONS="-A -m -d -S" + if [ -n "$SCREEN_OPTIONS" ]; then + OPTIONS="$SCREEN_OPTIONS" + fi + echo "> Starting with screen session: $session_name (options: $OPTIONS) - attach with 'screen -r $session_name'" + echo "screen $OPTIONS \"$session_name\" -- \"$script_path\" ${args[*]}" + screen $OPTIONS "$session_name" -- "$script_path" "${args[@]}" + ;; + "none"|*) + echo "> Starting without session manager" + "$script_path" "${args[@]}" + ;; + esac +} + +# Parse command line arguments +function parse_arguments() { + local mode="$1" + local serverbin="$2" + shift 2 + + local config_file="" + local serverconfig="" + local session_manager="" + + # Parse named arguments + while [[ $# -gt 0 ]]; do + case $1 in + --config) + config_file="$2" + shift 2 + ;; + --server-config) + serverconfig="$2" + shift 2 + ;; + --session-manager) + session_manager="$2" + shift 2 + ;; + *) + echo "Unknown argument: $1" + return 1 + ;; + esac + done + + # Export parsed values for use by start_service + export PARSED_MODE="$mode" + export PARSED_SERVERBIN="$serverbin" + export PARSED_CONFIG_FILE="$config_file" + export PARSED_SERVERCONFIG="$serverconfig" + export PARSED_SESSION_MANAGER="$session_manager" +} + +# Start service (single run or with simple-restarter) +function start_service() { + local config_file="$1" + local serverbin_path="$2" + local serverconfig="$3" + local use_restarter="${4:-false}" + local session_manager_choice="$5" + + # Load configuration first + load_config "$config_file" + + # if no session manager is specified, get it from config + if [ -z "$session_manager_choice" ]; then + session_manager_choice="$SESSION_MANAGER" + fi + + + # Parse serverbin_path to extract BINPATH and SERVERBIN + if [ -n "$serverbin_path" ]; then + # If it's a full path, extract directory and binary name + if [[ "$serverbin_path" == */* ]]; then + BINPATH="$(dirname "$serverbin_path")" + SERVERBIN="$(basename "$serverbin_path")" + else + # If it's just a binary name, use it as-is (system PATH) + SERVERBIN="$serverbin_path" + BINPATH="${BINPATH:-""}" # Empty means use current directory or system PATH + fi + fi + + # Use environment/config values if not set from command line + BINPATH="${BINPATH:-$RUN_ENGINE_BINPATH}" + SERVERBIN="${SERVERBIN:-$RUN_ENGINE_SERVERBIN}" + CONFIG="${serverconfig:-$RUN_ENGINE_CONFIG}" + + echo "SERVERBIN: $SERVERBIN" + + # Validate required parameters + if [ -z "$SERVERBIN" ]; then + echo "Error: SERVERBIN is required" + echo "Could not determine server binary from: $serverbin_path" + echo "Provide it as:" + echo " - Full path: $0 <mode> /path/to/bin/worldserver" + echo " - Binary name: $0 <mode> worldserver" + echo " - Environment variables: RUN_ENGINE_SERVERBIN" + echo " - Configuration file with SERVERBIN variable" + return 1 + fi + + # If BINPATH is set, validate binary exists and create log paths + if [ -n "$BINPATH" ]; then + if [ ! -d "$BINPATH" ]; then + echo "Error: BINPATH not found: $BINPATH" + return 1 + fi + + # Set up directories and logging relative to BINPATH + LOGS_PATH="${LOGS_PATH:-"$BINPATH/logs"}" + mkdir -p "$LOGS_PATH" + mkdir -p "$LOGS_PATH/crashes" + else + # For system binaries, try to detect binary location and create logs accordingly + local detected_binpath="" + + # Try to find binary in system PATH + local binary_location=$(which "$SERVERBIN" 2>/dev/null) + if [ -n "$binary_location" ]; then + detected_binpath="$(dirname "$binary_location")" + echo "Binary found in system PATH: $binary_location" + # Set BINPATH to the detected location so starter script can find the binary + BINPATH="$detected_binpath" + fi + + # Set up log paths based on detected or fallback location + if [ -n "$detected_binpath" ]; then + LOGS_PATH="${LOGS_PATH:-"$detected_binpath/logs"}" + else + # Fallback to current directory for logs + LOGS_PATH="${LOGS_PATH:-./logs}" + fi + + CRASHES_PATH="${CRASHES_PATH:-"$LOGS_PATH/crashes"}" + + mkdir -p "$LOGS_PATH" + mkdir -p "$CRASHES_PATH" + fi + + # Set up logging names + LOG_PREFIX_NAME="${LOG_PREFIX_NAME:-${SERVERBIN%server}}" + + # Set up session name (with backward compatibility for SCREEN_NAME) + SESSION_NAME="${SESSION_NAME:-$SCREEN_NAME}" + SESSION_NAME="${SESSION_NAME:-AC-${SERVERBIN%server}}" + + configure_files + + local session_manager=$(get_session_manager "$session_manager_choice") + + if [ "$session_manager" = "error" ]; then + echo "Error: Invalid session manager specified: $session_manager_choice, is it installed?" + exit 1 + fi + + echo "Using session manager: $session_manager" + echo "Starting server: $SERVERBIN" + + if [ -n "$CONFIG" ]; then + echo "Server config: $CONFIG" + else + echo "Server config: default (not specified)" + fi + + if [ "$use_restarter" = "true" ]; then + # Use simple-restarter for restart functionality + local gdb_enabled="${GDB_ENABLED:-0}" + run_with_session "$session_manager" "$SESSION_NAME" "simple-restarter" "$BINPATH" "$SERVERBIN" "$GDB" "$CONFIG" "$SYSLOG" "$SYSERR" "$gdb_enabled" "$CRASHES_PATH" + else + # Single run using starter + local gdb_enabled="${GDB_ENABLED:-0}" + run_with_session "$session_manager" "$SESSION_NAME" "starter" "$BINPATH" "$SERVERBIN" "$GDB" "$CONFIG" "$SYSLOG" "$SYSERR" "$gdb_enabled" "$CRASHES_PATH" + fi +} + +# Cleanup function +function finish() { + local session_manager=$(get_session_manager "${SESSION_MANAGER:-auto}") + if [ -n "$SESSION_NAME" ]; then + case "$session_manager" in + "tmux") + tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true + ;; + "screen") + screen -X -S "$SESSION_NAME" quit 2>/dev/null || true + ;; + esac + fi +} + +# Legacy compatibility functions for old examples +function restarter() { + echo "Legacy function 'restarter' called - redirecting to new API" + start_service "" "" "" "true" "${SESSION_MANAGER:-auto}" +} + +function starter() { + echo "Legacy function 'starter' called - redirecting to new API" + start_service "" "" "" "false" "${SESSION_MANAGER:-auto}" +} + +# Set trap for cleanup (currently disabled to avoid interfering with systemd) +# trap finish EXIT + +# Main execution when script is run directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + case "${1:-help}" in + "start"|"restart") + if [ $# -lt 2 ]; then + echo "Error: Missing required arguments" + echo "Usage: $0 <mode> <serverbin> [options]" + echo "Example: $0 start worldserver --config ./conf-world.sh --server-config worldserver.conf" + exit 1 + fi + + # Parse arguments + if ! parse_arguments "$@"; then + exit 1 + fi + + # Determine restart mode + use_restarter="false" + if [ "$PARSED_MODE" = "restart" ]; then + use_restarter="true" + fi + + # Start service with parsed arguments + start_service "$PARSED_CONFIG_FILE" "$PARSED_SERVERBIN" "$PARSED_SERVERCONFIG" "$use_restarter" "$PARSED_SESSION_MANAGER" + ;; + "help"|*) + echo "AzerothCore Run Engine" + echo "" + echo "Usage: $0 <mode> <serverbin> [options]" + echo "" + echo "Modes:" + echo " start - Start service once (no restart on crash)" + echo " restart - Start service with restart on crash (uses simple-restarter)" + echo "" + echo "Required Parameters:" + echo " serverbin - Server binary (full path or binary name)" + echo " Full path: /path/to/bin/worldserver" + echo " Binary name: worldserver (uses system PATH)" + echo "" + echo "Options:" + echo " --config <file> - Path to configuration file" + echo " --server-config <file> - Server configuration file (sets -c parameter)" + echo " --session-manager <type> - Session manager: none|auto|tmux|screen (default: auto)" + echo "" + echo "Configuration Priority (highest to lowest):" + echo " 1. conf.sh - User configuration file" + echo " 2. Command line arguments (--config, --server-config, etc.)" + echo " 3. Environment variables (RUN_ENGINE_*)" + echo " 4. conf.sh.dist - Default configuration" + echo "" + echo "Environment Variables:" + echo " RUN_ENGINE_CONFIG_FILE - Config file path" + echo " RUN_ENGINE_SESSION_MANAGER - Session manager (default: auto)" + echo " RUN_ENGINE_BINPATH - Binary directory path" + echo " RUN_ENGINE_SERVERBIN - Server binary name" + echo " RUN_ENGINE_CONFIG - Server configuration file" + echo " RUN_ENGINE_LOGS_PATH - Directory for log files" + echo " RUN_ENGINE_CRASHES_PATH - Directory for crash dumps" + echo " RUN_ENGINE_SESSION_NAME - Session name for tmux/screen" + echo "" + echo "Examples:" + echo "" + echo " # Using full path to binary" + echo " $0 start /home/user/ac/bin/worldserver" + echo "" + echo " # Using binary name (system PATH)" + echo " $0 start worldserver" + echo "" + echo " # With configuration file" + echo " $0 start worldserver --config ./conf-world.sh" + echo "" + echo " # With server configuration (sets -c parameter)" + echo " $0 start /path/to/bin/worldserver --server-config /etc/worldserver.conf" + echo "" + echo " # With session manager" + echo " $0 restart worldserver --session-manager tmux" + echo "" + echo " # Complete example" + echo " $0 restart /home/user/ac/bin/worldserver --config ./conf-world.sh --server-config worldserver.conf --session-manager screen" + echo "" + echo "Binary Resolution:" + echo " - Full path (contains /): Extracts directory and binary name" + echo " - Binary name only: Uses system PATH to find executable" + echo " Auto-detection will check current directory first, then system PATH" + echo "" + echo "Server Config:" + echo " If --server-config is specified, it's passed as -c parameter to the server." + echo " If not specified, the server will use its default configuration." + ;; + esac +fi + diff --git a/apps/startup-scripts/src/service-manager.sh b/apps/startup-scripts/src/service-manager.sh new file mode 100755 index 0000000000..1c44c9c247 --- /dev/null +++ b/apps/startup-scripts/src/service-manager.sh @@ -0,0 +1,1261 @@ +#!/usr/bin/env bash + +# AzerothCore Service Setup +# A unified interface for managing AzerothCore services with PM2 or systemd +# This script provides commands to create, update, delete, and manage server instances + +# Script location +CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +SCRIPT_DIR="$CURRENT_PATH" + +ROOT_DIR="$(cd "$CURRENT_PATH/../../.." && pwd)" + +# Configuration directory +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/azerothcore/services" +REGISTRY_FILE="$CONFIG_DIR/service_registry.json" + +# Colors for output +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Create config directory if it doesn't exist +mkdir -p "$CONFIG_DIR" + +# Initialize registry if it doesn't exist +if [ ! -f "$REGISTRY_FILE" ]; then + echo "[]" > "$REGISTRY_FILE" +fi + +# Check dependencies +check_dependencies() { + command -v jq >/dev/null 2>&1 || { + echo -e "${RED}Error: jq is required but not installed. Please install jq package.${NC}" + exit 1 + } +} + +# Check if PM2 is installed +check_pm2() { + if ! command -v pm2 >/dev/null 2>&1; then + echo -e "${RED}Error: PM2 is not installed. Please install PM2 first:${NC}" + echo " npm install -g pm2" + return 1 + fi +} + +# Check if systemd is available +check_systemd() { + if ! command -v systemctl >/dev/null 2>&1; then + echo -e "${RED}Error: systemd is not available on this system${NC}" + return 1 + fi +} + +# Auto-detect provider based on system availability +auto_detect_provider() { + if check_systemd 2>/dev/null; then + echo "systemd" + elif check_pm2 2>/dev/null; then + echo "pm2" + else + echo -e "${RED}Error: Neither systemd nor PM2 is available on this system${NC}" >&2 + echo -e "${YELLOW}Please install PM2 (npm install -g pm2) or ensure systemd is available${NC}" >&2 + return 1 + fi +} + +# Helper functions +function print_help() { + + local base_name="$(basename $0)" + + echo -e "${BLUE}AzerothCore Service Setup${NC}" + echo "A unified interface for managing AzerothCore services with PM2 or systemd" + echo "" + echo "Usage:" + echo " $base_name create <service-type> <service-name> [options]" + echo " $base_name update <service-name> [options]" + echo " $base_name delete <service-name>" + echo " $base_name list [provider]" + echo " $base_name start|stop|restart|status <service-name>" + echo " $base_name logs <service-name> [--follow]" + echo " $base_name attach <service-name>" + echo " $base_name edit-config <service-name>" + echo "" + echo "Providers:" + echo " pm2 - Use PM2 process manager" + echo " systemd - Use systemd service manager" + echo " auto - Automatically choose systemd or fallback to pm2 (default)" + echo "" + echo "Service Types:" + echo " auth - Authentication server" + echo " world - World server (use different names for multiple realms)" + echo "" + echo "Options:" + echo " --provider <type> - Service provider (pm2|systemd|auto, default: auto)" + echo " --bin-path <path> - Path to the server binary directory (required)" + echo " --server-config <path> - Path to the server configuration file" + echo " --session-manager <type> - Session manager (none|tmux|screen, default: none)" + echo " --gdb-enabled <0|1> - Enable GDB debugging (default: 0)" + echo " --system - Create as system service (systemd only, requires sudo)" + echo " --user - Create as user service (systemd only, default)" + echo " --max-memory <value> - Maximum memory limit (PM2 only)" + echo " --max-restarts <value> - Maximum restart attempts (PM2 only)" + echo " --no-start - Do not start the service after creation" + echo "" + echo "Examples:" + echo " # Create auth server (auto-detects provider)" + echo " $base_name create auth authserver --bin-path /home/user/azerothcore/bin" + echo "" + echo " # Create PM2 auth server explicitly" + echo " $base_name create auth authserver --provider pm2 --bin-path /home/user/azerothcore/bin" + echo "" + echo " # Create systemd world server with debugging enabled" + echo " $base_name create world worldserver-realm1 --provider systemd" + echo " --bin-path /home/user/azerothcore/bin" + echo " --server-config /home/user/azerothcore/etc/worldserver.conf" + echo " --gdb-enabled 1 --session-manager tmux" + echo "" + echo " # Create service without starting it" + echo " $base_name create auth authserver --bin-path /home/user/azerothcore/bin --no-start" + echo "" + echo " # Update run-engine configuration" + echo " $base_name update worldserver-realm1 --session-manager screen --gdb-enabled 0" + echo "" + echo " # Service management" + echo " $base_name start worldserver-realm1" + echo " $base_name logs worldserver-realm1 --follow" + echo " $base_name attach worldserver-realm1" + echo " $base_name list pm2" + echo "" + echo "Notes:" + echo " - Configuration editing modifies run-engine settings (GDB, session manager, etc.)" + echo " - Use --server-config for the actual server configuration file" + echo " - Services use run-engine in 'start' mode for single-shot execution" + echo " - Restart on crash is handled by PM2 or systemd, not by run-engine" + echo " - attach command automatically detects the configured session manager and connects appropriately" + echo " - attach always provides interactive access to the server console" + echo " - Use 'logs' command to view service logs without interaction" +} + +function register_service() { + local service_name="$1" + local provider="$2" + local service_type="$3" + local config_file="$CONFIG_DIR/$service_name.conf" + + # Add to registry + local tmp_file=$(mktemp) + jq --arg name "$service_name" \ + --arg provider "$provider" \ + --arg type "$service_type" \ + --arg config "$config_file" \ + '. += [{"name": $name, "provider": $provider, "type": $type, "config": $config}]' \ + "$REGISTRY_FILE" > "$tmp_file" + mv "$tmp_file" "$REGISTRY_FILE" + + echo -e "${GREEN}Service $service_name registered successfully${NC}" +} + +function validate_service_exists() { + local service_name="$1" + local provider="$2" + + if [ "$provider" = "pm2" ]; then + # Check if service exists in PM2 + if ! pm2 id "$service_name" > /dev/null 2>&1; then + return 1 # Service not found + fi + elif [ "$provider" = "systemd" ]; then + # Check if service exists in systemd + local systemd_type="--user" + if [ -f "/etc/systemd/system/$service_name.service" ]; then + systemd_type="--system" + fi + + if [ "$systemd_type" = "--system" ]; then + if ! systemctl is-active "$service_name.service" >/dev/null 2>&1 && \ + ! systemctl is-enabled "$service_name.service" >/dev/null 2>&1 && \ + ! systemctl is-failed "$service_name.service" >/dev/null 2>&1; then + return 1 # Service not found + fi + else + if ! systemctl --user is-active "$service_name.service" >/dev/null 2>&1 && \ + ! systemctl --user is-enabled "$service_name.service" >/dev/null 2>&1 && \ + ! systemctl --user is-failed "$service_name.service" >/dev/null 2>&1; then + return 1 # Service not found + fi + fi + fi + + return 0 # Service exists +} + +function sync_registry() { + echo -e "${YELLOW}Syncing service registry with actual services...${NC}" + + local services=$(jq -c '.[]' "$REGISTRY_FILE") + local tmp_file=$(mktemp) + + # Initialize with empty array + echo "[]" > "$tmp_file" + + # Check each service in registry + while read -r service_info; do + if [ -n "$service_info" ]; then + local name=$(echo "$service_info" | jq -r '.name') + local provider=$(echo "$service_info" | jq -r '.provider') + + if validate_service_exists "$name" "$provider"; then + # Service exists, add it to the new registry + jq --argjson service "$service_info" '. += [$service]' "$tmp_file" > "$tmp_file.new" + mv "$tmp_file.new" "$tmp_file" + else + echo -e "${YELLOW}Service '$name' no longer exists. Removing from registry.${NC}" + # Don't add to new registry + fi + fi + done <<< "$services" + + # Replace registry with synced version + mv "$tmp_file" "$REGISTRY_FILE" + echo -e "${GREEN}Registry synchronized.${NC}" +} + +function unregister_service() { + local service_name="$1" + + # Remove from registry + local tmp_file=$(mktemp) + jq --arg name "$service_name" '. | map(select(.name != $name))' "$REGISTRY_FILE" > "$tmp_file" + mv "$tmp_file" "$REGISTRY_FILE" + + # Remove configuration file + rm -f "$CONFIG_DIR/$service_name.conf" + + echo -e "${GREEN}Service $service_name unregistered${NC}" +} + +function get_service_info() { + local service_name="$1" + jq --arg name "$service_name" '.[] | select(.name == $name)' "$REGISTRY_FILE" +} + +# PM2 service management functions +function pm2_create_service() { + local service_name="$1" + local command="$2" + shift 2 + + check_pm2 || return 1 + + # Parse additional PM2 options + local max_memory="" + local max_restarts="" + local additional_args="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --max-memory) + max_memory="$2" + shift 2 + ;; + --max-restarts) + max_restarts="$2" + shift 2 + ;; + *) + additional_args+=" $1" + shift + ;; + esac + done + + # Build PM2 start command + local pm2_cmd="pm2 start '$command$additional_args' --name '$service_name'" + + # Add memory limit if specified + if [ -n "$max_memory" ]; then + pm2_cmd+=" --max-memory-restart $max_memory" + fi + + # Add max restarts if specified + if [ -n "$max_restarts" ]; then + pm2_cmd+=" --max-restarts $max_restarts" + fi + + # Execute command + echo -e "${YELLOW}Creating PM2 service: $service_name${NC}" + + if eval "$pm2_cmd"; then + echo -e "${GREEN}PM2 service '$service_name' created successfully${NC}" + pm2 save + return 0 + else + echo -e "${RED}Failed to create PM2 service '$service_name'${NC}" + return 1 + fi +} + +function pm2_remove_service() { + local service_name="$1" + + check_pm2 || return 1 + + echo -e "${YELLOW}Stopping and removing PM2 service: $service_name${NC}" + + # Stop the service if it's running + if pm2 id "$service_name" > /dev/null 2>&1; then + pm2 stop "$service_name" 2>/dev/null || true + pm2 delete "$service_name" 2>/dev/null + + # Verify the service was removed + if pm2 id "$service_name" > /dev/null 2>&1; then + echo -e "${RED}Failed to remove PM2 service '$service_name'${NC}" + return 1 + fi + + pm2 save + echo -e "${GREEN}PM2 service '$service_name' stopped and removed${NC}" + else + echo -e "${YELLOW}PM2 service '$service_name' not found or already removed${NC}" + fi + + return 0 +} + +function pm2_service_action() { + local action="$1" + local service_name="$2" + + check_pm2 || return 1 + + echo -e "${YELLOW}${action^} PM2 service: $service_name${NC}" + pm2 "$action" "$service_name" +} + +function pm2_service_logs() { + local service_name="$1" + local follow="$2" + + check_pm2 || return 1 + + echo -e "${YELLOW}Showing PM2 logs for: $service_name${NC}" + if [ "$follow" = "true" ]; then + pm2 logs "$service_name" --lines 50 + else + pm2 logs "$service_name" --lines 50 --nostream + fi +} + +# Systemd service management functions +function get_systemd_dir() { + local type="$1" + if [ "$type" = "--system" ]; then + echo "/etc/systemd/system" + else + echo "${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user" + fi +} + +function systemd_create_service() { + local service_name="$1" + local command="$2" + local systemd_type="--user" + shift 2 + + check_systemd || return 1 + + # Parse systemd type + while [[ $# -gt 0 ]]; do + case "$1" in + --system|--user) + systemd_type="$1" + shift + ;; + *) + command+=" $1" + shift + ;; + esac + done + + local systemd_dir=$(get_systemd_dir "$systemd_type") + local service_file="$systemd_dir/$service_name.service" + + # Create systemd directory if it doesn't exist + if [ "$systemd_type" = "--system" ]; then + if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Error: System services require root privileges. Use sudo.${NC}" + return 1 + fi + mkdir -p "$systemd_dir" + else + mkdir -p "$systemd_dir" + fi + + # Create service file + echo -e "${YELLOW}Creating systemd service: $service_name${NC}" + + if [ "$systemd_type" = "--system" ]; then + # System service template (with User directive) + cat > "$service_file" << EOF +[Unit] +Description=AzerothCore $service_name +After=network.target + +[Service] +Type=forking +ExecStart=$command +Restart=always +RestartSec=3 +User=$(whoami) +Group=$(id -gn) +WorkingDirectory=$(realpath "$bin_path") +StandardOutput=journal+console +StandardError=journal+console + +[Install] +WantedBy=multi-user.target +EOF + else + # User service template (no User/Group directives) + cat > "$service_file" << EOF +[Unit] +Description=AzerothCore $service_name +After=network.target + +[Service] +Type=forking +ExecStart=$command +Restart=always +RestartSec=3 +WorkingDirectory=$(realpath "$bin_path") +StandardOutput=journal+console +StandardError=journal+console + +[Install] +WantedBy=default.target +EOF + fi + + if [ "$systemd_type" = "--system" ]; then + sed -i 's/WantedBy=default.target/WantedBy=multi-user.target/' "$service_file" + fi + + # Reload systemd and enable service + if [ "$systemd_type" = "--system" ]; then + systemctl daemon-reload + systemctl enable "$service_name.service" + else + systemctl --user daemon-reload + systemctl --user enable "$service_name.service" + fi + + echo -e "${GREEN}Systemd service '$service_name' created successfully${NC}" + return 0 +} + +function systemd_remove_service() { + local service_name="$1" + local systemd_type="--user" + + check_systemd || return 1 + + # Try to determine if it's a system or user service + if [ -f "/etc/systemd/system/$service_name.service" ]; then + systemd_type="--system" + fi + + local systemd_dir=$(get_systemd_dir "$systemd_type") + local service_file="$systemd_dir/$service_name.service" + + echo -e "${YELLOW}Stopping and removing systemd service: $service_name (${systemd_type#--})${NC}" + + # Check if service file exists + if [ ! -f "$service_file" ]; then + echo -e "${YELLOW}Systemd service file '$service_file' not found or already removed${NC}" + return 0 + fi + + # Stop and disable service + local removal_failed=false + if [ "$systemd_type" = "--system" ]; then + if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Error: System services require root privileges. Use sudo.${NC}" + return 1 + fi + systemctl stop "$service_name.service" 2>/dev/null || true + systemctl disable "$service_name.service" 2>/dev/null || true + systemctl daemon-reload + + # Verify service is no longer active + if systemctl is-active "$service_name.service" >/dev/null 2>&1; then + echo -e "${RED}Warning: Failed to stop system service '$service_name'${NC}" + removal_failed=true + fi + else + systemctl --user stop "$service_name.service" 2>/dev/null || true + systemctl --user disable "$service_name.service" 2>/dev/null || true + systemctl --user daemon-reload + + # Verify service is no longer active + if systemctl --user is-active "$service_name.service" >/dev/null 2>&1; then + echo -e "${RED}Warning: Failed to stop user service '$service_name'${NC}" + removal_failed=true + fi + fi + + # Remove service file + if rm -f "$service_file"; then + echo -e "${GREEN}Systemd service '$service_name' stopped and removed${NC}" + if [ "$removal_failed" = "true" ]; then + echo -e "${YELLOW}Note: Service may still be running but configuration was removed${NC}" + fi + return 0 + else + echo -e "${RED}Failed to remove systemd service file '$service_file'${NC}" + return 1 + fi +} + +function systemd_service_action() { + local action="$1" + local service_name="$2" + local systemd_type="--user" + + check_systemd || return 1 + + # Try to determine if it's a system or user service + if [ -f "/etc/systemd/system/$service_name.service" ]; then + systemd_type="--system" + fi + + echo -e "${YELLOW}${action^} systemd service: $service_name${NC}" + + if [ "$systemd_type" = "--system" ]; then + systemctl "$action" "$service_name.service" + else + systemctl --user "$action" "$service_name.service" + fi +} + +function systemd_service_logs() { + local service_name="$1" + local follow="$2" + local systemd_type="--user" + + check_systemd || return 1 + + # Try to determine if it's a system or user service + if [ -f "/etc/systemd/system/$service_name.service" ]; then + systemd_type="--system" + fi + + echo -e "${YELLOW}Showing systemd logs for: $service_name${NC}" + if [ "$follow" = "true" ]; then + if [ "$systemd_type" = "--system" ]; then + journalctl --unit="$service_name.service" -e -f + else + journalctl --user-unit="$service_name.service" -e -f + fi + else + if [ "$systemd_type" = "--system" ]; then + journalctl --unit="$service_name.service" -e --lines 50 --no-pager + else + journalctl --user-unit="$service_name.service" -e --lines 50 --no-pager + fi + fi +} + +function create_service() { + local service_type="$1" + local service_name="$2" + shift 2 + + # Validate service type + if [[ "$service_type" != "auth" && "$service_type" != "world" ]]; then + echo -e "${RED}Error: Invalid service type. Use 'auth' or 'world'${NC}" + return 1 + fi + + # Check if service already exists + if [ -n "$(get_service_info "$service_name")" ]; then + echo -e "${RED}Error: Service '$service_name' already exists${NC}" + return 1 + fi + + # Default values for run-engine configuration + local provider="auto" + local bin_path="$BINPATH/bin" # get from config or environment + local server_config="" + local session_manager="none" + local gdb_enabled="0" + local systemd_type="--user" + local pm2_opts="" + local auto_start="true" + + # Parse options + while [[ $# -gt 0 ]]; do + case "$1" in + --provider) + provider="$2" + shift 2 + ;; + --bin-path) + bin_path="$2" + shift 2 + ;; + --server-config) + server_config="$2" + shift 2 + ;; + --session-manager) + session_manager="$2" + shift 2 + ;; + --gdb-enabled) + gdb_enabled="$2" + shift 2 + ;; + --system) + systemd_type="--system" + shift + ;; + --user) + systemd_type="--user" + shift + ;; + --max-memory|--max-restarts) + pm2_opts="$pm2_opts $1 $2" + shift 2 + ;; + --no-start) + auto_start="false" + shift + ;; + *) + echo -e "${RED}Error: Unknown option: $1${NC}" + return 1 + ;; + esac + done + + # Auto-detect provider if set to auto + if [ "$provider" = "auto" ]; then + if ! provider=$(auto_detect_provider); then + return 1 + fi + echo -e "${BLUE}Auto-detected provider: $provider${NC}" + fi + + # Validate provider + if [[ "$provider" != "pm2" && "$provider" != "systemd" ]]; then + echo -e "${RED}Error: Invalid provider. Use 'pm2', 'systemd', or 'auto'${NC}" + return 1 + fi + + # Determine server binary based on service type + local server_bin="${service_type}server" + local server_binary_path=$(realpath "$bin_path/$server_bin") + + # Check if binary exists + if [ ! -f "$server_binary_path" ]; then + echo -e "${RED}Error: Server binary not found: $server_binary_path${NC}" + return 1 + fi + + # Create run-engine configuration file for this service + local run_engine_config="$CONFIG_DIR/$service_name-run-engine.conf" + cat > "$run_engine_config" << EOF +# run-engine configuration for service: $service_name +# This file contains run-engine specific settings + +# Enable/disable GDB execution +export GDB_ENABLED=$gdb_enabled + +# Session manager (none|auto|tmux|screen) +export SESSION_MANAGER="$session_manager" + +# Session name for tmux/screen (optional) +export SESSION_NAME="${service_name}" + +# Binary directory path +export BINPATH="$bin_path" + +# Server binary name +export SERVERBIN="$server_bin" + +# Server configuration file path +export CONFIG="$server_config" + +# Show console output for easier debugging +export WITH_CONSOLE=1 +EOF + + # Create service configuration file for our registry + cat > "$CONFIG_DIR/$service_name.conf" << EOF +# AzerothCore service configuration for $service_name +# Created: $(date) +# Provider: $provider +# Service Type: $service_type + +# run-engine configuration file +RUN_ENGINE_CONFIG_FILE="$run_engine_config" + +# Provider-specific options +SYSTEMD_TYPE="$systemd_type" +PM2_OPTS="$pm2_opts" +EOF + + # Build run-engine command + local run_engine_cmd="$SCRIPT_DIR/run-engine start $server_binary_path --config $run_engine_config" + + # Create the actual service + local service_creation_success=false + if [ "$provider" = "pm2" ]; then + if [ -n "$pm2_opts" ]; then + if pm2_create_service "$service_name" "$run_engine_cmd" $pm2_opts; then + service_creation_success=true + fi + else + if pm2_create_service "$service_name" "$run_engine_cmd"; then + service_creation_success=true + fi + fi + + elif [ "$provider" = "systemd" ]; then + if systemd_create_service "$service_name" "$run_engine_cmd" "$systemd_type"; then + service_creation_success=true + fi + fi + + # Check if service creation was successful + if [ "$service_creation_success" = "true" ]; then + # Register the service + register_service "$service_name" "$provider" "$service_type" + echo -e "${GREEN}Service '$service_name' created successfully${NC}" + echo -e "${BLUE}Run-engine config: $run_engine_config${NC}" + + # Auto-start the service unless --no-start was specified + if [ "$auto_start" = "true" ]; then + echo -e "${YELLOW}Starting service '$service_name'...${NC}" + if service_action "start" "$service_name"; then + echo -e "${GREEN}Service '$service_name' started successfully${NC}" + else + echo -e "${YELLOW}Warning: Service '$service_name' was created but failed to start${NC}" + echo -e "${BLUE}You can start it manually with: $0 start $service_name${NC}" + fi + else + echo -e "${BLUE}Service created but not started (--no-start specified)${NC}" + echo -e "${BLUE}Start it manually with: $0 start $service_name${NC}" + fi + else + # Remove configuration files if service creation failed + rm -f "$CONFIG_DIR/$service_name.conf" + rm -f "$run_engine_config" + echo -e "${RED}Failed to create service '$service_name'${NC}" + return 1 + fi +} + +function update_service() { + local service_name="$1" + shift + + # Check if service exists + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" + return 1 + fi + + # Extract service information + local provider=$(echo "$service_info" | jq -r '.provider') + local service_type=$(echo "$service_info" | jq -r '.type') + local config_file=$(echo "$service_info" | jq -r '.config') + + # Load current configuration + source "$config_file" + + # Load current run-engine configuration + if [ -f "$RUN_ENGINE_CONFIG_FILE" ]; then + source "$RUN_ENGINE_CONFIG_FILE" + fi + + # Parse options to update + local config_updated=false + while [[ $# -gt 0 ]]; do + case "$1" in + --bin-path) + export BINPATH="$2" + config_updated=true + shift 2 + ;; + --server-config) + export SERVER_CONFIG="$2" + config_updated=true + shift 2 + ;; + --session-manager) + export SESSION_MANAGER="$2" + config_updated=true + shift 2 + ;; + --gdb-enabled) + export GDB_ENABLED="$2" + config_updated=true + shift 2 + ;; + --system) + SYSTEMD_TYPE="--system" + shift + ;; + --user) + SYSTEMD_TYPE="--user" + shift + ;; + --max-memory|--max-restarts) + PM2_OPTS="$PM2_OPTS $1 $2" + shift 2 + ;; + *) + echo -e "${RED}Error: Unknown option: $1${NC}" + return 1 + ;; + esac + done + + if [ "$config_updated" = "true" ]; then + # Update run-engine configuration file + cat > "$RUN_ENGINE_CONFIG_FILE" << EOF +# run-engine configuration for service: $service_name +# Updated: $(date) + +# Enable/disable GDB execution +export GDB_ENABLED=${GDB_ENABLED:-0} + +# Session manager (none|auto|tmux|screen) +export SESSION_MANAGER="${SESSION_MANAGER:-none}" + +# Session name for tmux/screen +export SESSION_NAME="${service_name}" + +# Binary directory path +export BINPATH="${BINPATH}" + +# Server binary name +export SERVERBIN="${SERVERBIN}" + +# Server configuration file path +export CONFIG="${SERVER_CONFIG}" +EOF + + echo -e "${GREEN}Run-engine configuration updated: $RUN_ENGINE_CONFIG_FILE${NC}" + echo -e "${YELLOW}Note: Restart the service to apply changes${NC}" + else + echo -e "${YELLOW}No run-engine configuration changes made${NC}" + fi + + # Update service configuration + cat > "$config_file" << EOF +# AzerothCore service configuration for $service_name +# Updated: $(date) +# Provider: $provider +# Service Type: $service_type + +# run-engine configuration file +RUN_ENGINE_CONFIG_FILE="$RUN_ENGINE_CONFIG_FILE" + +# Provider-specific options +SYSTEMD_TYPE="$SYSTEMD_TYPE" +PM2_OPTS="$PM2_OPTS" +EOF +} + +function delete_service() { + local service_name="$1" + + # Check if service exists + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" + return 1 + fi + + # Extract provider and config + local provider=$(echo "$service_info" | jq -r '.provider') + local config_file=$(echo "$service_info" | jq -r '.config') + + # Load configuration to get run-engine config file + if [ -f "$config_file" ]; then + source "$config_file" + fi + + echo -e "${YELLOW}Deleting service '$service_name' (provider: $provider)...${NC}" + + # Stop and remove the service + local removal_success=false + if [ "$provider" = "pm2" ]; then + if pm2_remove_service "$service_name"; then + removal_success=true + fi + elif [ "$provider" = "systemd" ]; then + if systemd_remove_service "$service_name"; then + removal_success=true + fi + fi + + if [ "$removal_success" = "true" ]; then + # Remove run-engine configuration file + if [ -n "$RUN_ENGINE_CONFIG_FILE" ] && [ -f "$RUN_ENGINE_CONFIG_FILE" ]; then + rm -f "$RUN_ENGINE_CONFIG_FILE" + echo -e "${GREEN}Removed run-engine config: $RUN_ENGINE_CONFIG_FILE${NC}" + fi + + # Unregister service + unregister_service "$service_name" + echo -e "${GREEN}Service '$service_name' deleted successfully${NC}" + else + echo -e "${RED}Failed to remove service '$service_name' from $provider${NC}" + return 1 + fi +} + +function list_services() { + local provider_filter="$1" + + # Sync registry first + sync_registry + + echo -e "${BLUE}AzerothCore Services${NC}" + echo "=====================" + + if [ "$(jq 'length' "$REGISTRY_FILE")" = "0" ]; then + echo "No services registered" + return + fi + + # Show PM2 services + if [ -z "$provider_filter" ] || [ "$provider_filter" = "pm2" ]; then + local pm2_services=$(jq -r '.[] | select(.provider == "pm2") | .name' "$REGISTRY_FILE" 2>/dev/null) + if [ -n "$pm2_services" ] && command -v pm2 >/dev/null 2>&1; then + echo -e "\n${YELLOW}PM2 Services:${NC}" + pm2 list + fi + fi + + # Show systemd services + if [ -z "$provider_filter" ] || [ "$provider_filter" = "systemd" ]; then + local systemd_services=$(jq -r '.[] | select(.provider == "systemd") | .name' "$REGISTRY_FILE" 2>/dev/null) + if [ -n "$systemd_services" ] && command -v systemctl >/dev/null 2>&1; then + echo -e "\n${YELLOW}Systemd User Services:${NC}" + while read -r service_name; do + if [ -n "$service_name" ]; then + systemctl --user status "$service_name.service" --no-pager -l || true + echo "" + fi + done <<< "$systemd_services" + + # Also check for system services + local system_services="" + while read -r service_name; do + if [ -n "$service_name" ] && [ -f "/etc/systemd/system/$service_name.service" ]; then + system_services+="$service_name " + fi + done <<< "$systemd_services" + + if [ -n "$system_services" ]; then + echo -e "${YELLOW}Systemd System Services:${NC}" + for service_name in $system_services; do + systemctl status "$service_name.service" --no-pager -l || true + echo "" + done + fi + fi + fi +} + +function service_action() { + local action="$1" + local service_name="$2" + + # Check if service exists + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" + return 1 + fi + + # Extract provider + local provider=$(echo "$service_info" | jq -r '.provider') + + # Execute action + if [ "$provider" = "pm2" ]; then + pm2_service_action "$action" "$service_name" + elif [ "$provider" = "systemd" ]; then + systemd_service_action "$action" "$service_name" + fi +} + +function service_logs() { + local service_name="$1" + local follow="${2:-false}" + + # Check if service exists + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" + return 1 + fi + + # Extract provider + local provider=$(echo "$service_info" | jq -r '.provider') + + # Show logs + if [ "$provider" = "pm2" ]; then + pm2_service_logs "$service_name" "$follow" + elif [ "$provider" = "systemd" ]; then + systemd_service_logs "$service_name" "$follow" + fi +} + +function edit_config() { + local service_name="$1" + + # Check if service exists + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" + return 1 + fi + + # Get configuration file path + local config_file=$(echo "$service_info" | jq -r '.config') + + # Load configuration to get run-engine config file + source "$config_file" + + # Open run-engine configuration file in editor + echo -e "${YELLOW}Editing run-engine configuration for: $service_name${NC}" + echo -e "${BLUE}File: $RUN_ENGINE_CONFIG_FILE${NC}" + ${EDITOR:-nano} "$RUN_ENGINE_CONFIG_FILE" + + echo -e "${GREEN}Configuration updated. Run '$0 restart $service_name' to apply changes.${NC}" +} + +function attach_to_service() { + local service_name="$1" + + # Check if service exists + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" + return 1 + fi + + # Extract provider + local provider=$(echo "$service_info" | jq -r '.provider') + local config_file=$(echo "$service_info" | jq -r '.config') + + # Load configuration to get run-engine config file + if [ ! -f "$config_file" ]; then + echo -e "${RED}Error: Service configuration file not found: $config_file${NC}" + return 1 + fi + + source "$config_file" + + # Load run-engine configuration + if [ ! -f "$RUN_ENGINE_CONFIG_FILE" ]; then + echo -e "${RED}Error: Run-engine configuration file not found: $RUN_ENGINE_CONFIG_FILE${NC}" + return 1 + fi + + source "$RUN_ENGINE_CONFIG_FILE" + + # Auto-detect session manager and attach accordingly + case "$SESSION_MANAGER" in + "tmux") + attach_tmux_session "$service_name" "$provider" + ;; + "screen") + attach_screen_session "$service_name" "$provider" + ;; + "none"|"auto"|*) + # No session manager - launch interactive shell directly + attach_interactive_shell "$service_name" "$provider" + ;; + esac +} + +function attach_interactive_shell() { + local service_name="$1" + local provider="$2" + + # Get service info again to access configuration + local service_info=$(get_service_info "$service_name") + local config_file=$(echo "$service_info" | jq -r '.config') + + source "$config_file" + source "$RUN_ENGINE_CONFIG_FILE" + + echo -e "${RED}Error: Cannot attach to service '$service_name'${NC} [for now]" + echo -e "${YELLOW}Interactive attachment requires a session manager (tmux or screen).${NC}" + echo "" + echo -e "${BLUE}Current session manager: $SESSION_MANAGER${NC}" + echo "" + echo -e "${YELLOW}To enable interactive attachment:${NC}" + echo " 1. Update the service to use tmux or screen:" + echo " $0 update $service_name --session-manager tmux" + echo " 2. Restart the service:" + echo " $0 restart $service_name" + echo " 3. Then try attach again:" + echo " $0 attach $service_name" + echo "" + echo -e "${BLUE}Alternative: Use 'logs <service_name> --follow' to monitor the service:${NC}" + echo " $0 logs $service_name --follow" + + return 1 +} + +function attach_tmux_session() { + local service_name="$1" + local provider="$2" + + # Check if tmux is available + if ! command -v tmux >/dev/null 2>&1; then + echo -e "${RED}Error: tmux is not installed${NC}" + echo -e "${BLUE}Starting interactive session without tmux...${NC}" + attach_interactive_shell "$service_name" "$provider" + return + fi + + # Try to attach to tmux session + local tmux_session="$service_name" + echo -e "${YELLOW}Attempting to attach to tmux session: $tmux_session${NC}" + + if tmux has-session -t "$tmux_session" 2>/dev/null; then + echo -e "${GREEN}Attaching to tmux session...${NC}" + tmux attach-session -t "$tmux_session" + else + echo -e "${RED}Error: tmux session '$tmux_session' not found${NC}" + echo -e "${YELLOW}Available tmux sessions:${NC}" + tmux list-sessions 2>/dev/null || echo "No active tmux sessions" + echo -e "${BLUE}Starting new interactive session instead...${NC}" + attach_interactive_shell "$service_name" "$provider" + fi +} + +function attach_screen_session() { + local service_name="$1" + local provider="$2" + + # Check if screen is available + if ! command -v screen >/dev/null 2>&1; then + echo -e "${RED}Error: screen is not installed${NC}" + echo -e "${BLUE}Starting interactive session without screen...${NC}" + attach_interactive_shell "$service_name" "$provider" + return + fi + + # Try to attach to screen session + local screen_session="$service_name" + echo -e "${YELLOW}Attempting to attach to screen session: $screen_session${NC}" + + if screen -list | grep -q "$screen_session"; then + echo -e "${GREEN}Attaching to screen session...${NC}" + screen -r "$screen_session" + else + echo -e "${RED}Error: screen session '$screen_session' not found${NC}" + echo -e "${YELLOW}Available screen sessions:${NC}" + screen -list 2>/dev/null || echo "No active screen sessions" + echo -e "${BLUE}Starting new interactive session instead...${NC}" + attach_interactive_shell "$service_name" "$provider" + fi +} + + + + +# Main execution +check_dependencies + +# Main command processing +case "${1:-help}" in + create) + if [ $# -lt 3 ]; then + echo -e "${RED}Error: Not enough arguments for create command${NC}" + print_help + exit 1 + fi + create_service "$2" "$3" "${@:4}" + ;; + update) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for update command${NC}" + print_help + exit 1 + fi + update_service "$2" "${@:3}" + ;; + delete) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for delete command${NC}" + print_help + exit 1 + fi + delete_service "$2" + ;; + list) + list_services "$2" + ;; + start|stop|restart|status) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for $1 command${NC}" + print_help + exit 1 + fi + service_action "$1" "$2" + ;; + logs) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for logs command${NC}" + print_help + exit 1 + fi + if [ "$3" = "--follow" ]; then + service_logs "$2" "true" + else + service_logs "$2" "false" + fi + ;; + edit-config) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for edit-config command${NC}" + print_help + exit 1 + fi + edit_config "$2" + ;; + attach) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for attach command${NC}" + print_help + exit 1 + fi + attach_to_service "$2" + ;; + help|--help|-h) + print_help + ;; + *) + echo -e "${RED}Error: Unknown command: $1${NC}" + print_help + exit 1 + ;; +esac diff --git a/apps/startup-scripts/src/simple-restarter b/apps/startup-scripts/src/simple-restarter new file mode 100755 index 0000000000..c5540e4ab9 --- /dev/null +++ b/apps/startup-scripts/src/simple-restarter @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +# AzerothCore Simple Restarter +# This script is a wrapper around the starter script that provides restart functionality +# and maintains compatibility with the acore dashboard +# +# Usage: simple-restarter <binary> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path] +# +# Parameters (same as starter): +# $1 - Binary to execute (required) +# $2 - GDB configuration file (optional) +# $3 - Configuration file path (optional) +# $4 - System log file (optional) +# $5 - System error file (optional) +# $6 - GDB enabled flag (0/1, optional) +# $7 - Crashes directory path (optional) + +# Get script directory +CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Parameters (same as starter) +BINPATH="$1" +BINFILE="$2" +GDB_FILE="$3" +CONFIG="$4" +SYSLOG="$5" +SYSERR="$6" +GDB_ENABLED="${7:-0}" +CRASHES_PATH="$8" + +BINARY="$BINPATH/$BINFILE" + +# Default values (same as starter) +DEFAULT_CRASHES_PATH="./crashes" +DEFAULT_GDB_FILE="$CURRENT_PATH/gdb.conf" + +# Set defaults if not provided +CRASHES_PATH="${CRASHES_PATH:-$DEFAULT_CRASHES_PATH}" +GDB_FILE="${GDB_FILE:-$DEFAULT_GDB_FILE}" + +# Counters for crash detection +_instant_crash_count=0 +_restart_count=0 + +# Check if starter script exists +STARTER_SCRIPT="$CURRENT_PATH/starter" +if [ ! -f "$STARTER_SCRIPT" ]; then + echo "Error: starter script not found at $STARTER_SCRIPT" + exit 1 +fi + +# Main restart loop +while true; do + STARTING_TIME=$(date +%s) + + # Use starter script to launch the binary with all parameters + "$STARTER_SCRIPT" "$BINPATH" "$BINFILE" "$GDB_FILE" "$CONFIG" "$SYSLOG" "$SYSERR" "$GDB_ENABLED" "$CRASHES_PATH" + + _exit_code=$? + + echo "$(basename "$BINARY") terminated with exit code: $_exit_code" + + # Calculate runtime + ENDING_TIME=$(date +%s) + DIFFERENCE=$((ENDING_TIME - STARTING_TIME)) + + ((_restart_count++)) + echo "$(basename "$BINARY") terminated after $DIFFERENCE seconds, restart count: $_restart_count" + + # Crash loop detection + if [ $DIFFERENCE -lt 10 ]; then + # Increment instant crash count if runtime is lower than 10 seconds + ((_instant_crash_count++)) + echo "Warning: Quick restart detected ($DIFFERENCE seconds) - instant crash count: $_instant_crash_count" + else + # Reset count on successful longer run + _instant_crash_count=0 + fi + + # Prevent infinite crash loops + if [ $_instant_crash_count -gt 5 ]; then + echo "Error: $(basename "$BINARY") restarter exited. Infinite crash loop prevented (6 crashes in under 10 seconds each)" + echo "Please check your system configuration and logs" + exit 1 + fi + + echo "$(basename "$BINARY") will restart in 3 seconds..." + sleep 3 +done diff --git a/apps/startup-scripts/src/starter b/apps/startup-scripts/src/starter new file mode 100755 index 0000000000..967146efad --- /dev/null +++ b/apps/startup-scripts/src/starter @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +# AzerothCore Starter Script +# This script handles the execution of AzerothCore binaries with optional GDB support +# +# Usage: starter <binary> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path] +# +# Parameters: +# $1 - Binary to execute (required) +# $2 - GDB configuration file (optional) +# $3 - Configuration file path (optional) +# $4 - System log file (optional) +# $5 - System error file (optional) +# $6 - GDB enabled flag (0/1, optional) +# $7 - Crashes directory path (optional) + +BINPATH="$1" +BINFILE="$2" +GDB_FILE="$3" +CONFIG="$4" +SYSLOG="$5" +SYSERR="$6" +GDB_ENABLED="${7:-0}" +CRASHES_PATH="$8" + +BINARY=$(realpath "$BINPATH/$BINFILE") + +# Default values +CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEFAULT_CRASHES_PATH="$CURRENT_PATH/logs/crashes" +DEFAULT_GDB_FILE="$CURRENT_PATH/gdb.conf" + +# Set defaults if not provided +CONFIG="${CONFIG:-""}" +CRASHES_PATH="${CRASHES_PATH:-$DEFAULT_CRASHES_PATH}" +GDB_FILE="${GDB_FILE:-$DEFAULT_GDB_FILE}" + +# Validate binary +if [ -z "$BINARY" ]; then + echo "Error: Binary parameter is required" + echo "Usage: $0 <binary> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]" + exit 1 +fi + +if [ ! -f "$BINARY" ]; then + echo "Error: Binary '$BINARY' not found" + exit 1 +fi + +# Create crashes directory if it doesn't exist +mkdir -p "$CRASHES_PATH" + +cd $BINPATH || { + echo "Error: Could not change to binary path '$BINPATH'" + exit 1 +} + +EXECPATH=$(realpath "$BINFILE") + +if [ "$GDB_ENABLED" -eq 1 ]; then + echo "Starting $EXECPATH with GDB enabled" + + # Generate GDB configuration on the fly + TIMESTAMP=$(date +%Y-%m-%d-%H-%M-%S) + GDB_TEMP_FILE="$CRASHES_PATH/gdb-$TIMESTAMP.conf" + GDB_OUTPUT_FILE="$CRASHES_PATH/gdb-$TIMESTAMP.txt" + + # Create GDB configuration + cat > "$GDB_TEMP_FILE" << EOF +set logging file $GDB_OUTPUT_FILE +set logging enabled on +set debug timestamp +EOF + + # Add run command with config if specified + if [ -n "$CONFIG" ]; then + echo "run -c $CONFIG" >> "$GDB_TEMP_FILE" + else + echo "run" >> "$GDB_TEMP_FILE" + fi + + cat >> "$GDB_TEMP_FILE" << EOF +bt +bt full +info thread +thread apply all backtrace full +EOF + + # Create log files if specified + if [ -n "$SYSLOG" ]; then + [ ! -f "$SYSLOG" ] && touch "$SYSLOG" + fi + if [ -n "$SYSERR" ]; then + [ ! -f "$SYSERR" ] && touch "$SYSERR" + fi + + # Execute with GDB + if [ "${WITH_CONSOLE:-0}" -eq 0 ] && [ -n "$SYSLOG" ] && [ -n "$SYSERR" ]; then + gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH" >> "$SYSLOG" 2>> "$SYSERR" + else + echo "> Console enabled" + if [ -n "$SYSLOG" ] && [ -n "$SYSERR" ]; then + gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH" > >(tee "$SYSLOG") 2> >(tee "$SYSERR" >&2) + else + gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH" + fi + fi + + # Cleanup temporary GDB file + rm -f "$GDB_TEMP_FILE" +else + if [ -n "$CONFIG" ]; then + script -q -e -c "$EXECPATH -c \"$CONFIG\"" + else + script -q -e -c "$EXECPATH" + fi +fi diff --git a/apps/startup-scripts/starter b/apps/startup-scripts/starter deleted file mode 100644 index 47bbedce75..0000000000 --- a/apps/startup-scripts/starter +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -GDB_FILE="$2" -CONFIG="$3" -SYSLOG="$4" -SYSERR="$5" -GDB_ENABLED="$6" -CRASHES_PATH="$7" - -if [ $GDB_ENABLED -eq 1 ]; then - echo "set logging file "$CRASHES_PATH"/gdb-$(date +%Y-%m-%d-%H-%M-%S).txt" > "$GDB_FILE" - echo "set logging enabled on" >> "$GDB_FILE" - echo "set debug timestamp" >> "$GDB_FILE" - echo "run -c $3" >> "$GDB_FILE" - echo "bt" >> "$GDB_FILE" - echo "bt full" >> "$GDB_FILE" - echo "info thread" >> "$GDB_FILE" - echo "thread apply all backtrace full" >> "$GDB_FILE" - - [ ! -f "$SYSLOG" ] && touch "$SYSLOG" - [ ! -f "$SYSERR" ] && touch "$SYSERR" - - if [ $WITH_CONSOLE -eq 0 ]; then - gdb -x $GDB_FILE --batch $1 >> "$SYSLOG" 2>> "$SYSERR" - else - echo "> Console enabled" - gdb -x $GDB_FILE --batch $1 > >(tee ${SYSLOG}) 2> >(tee ${SYSERR} >&2) - fi - -elif [ $GDB_ENABLED -eq 0 ]; then - "./$1" -c "$CONFIG" -fi diff --git a/apps/startup-scripts/test/bats.conf b/apps/startup-scripts/test/bats.conf new file mode 100644 index 0000000000..8034254e72 --- /dev/null +++ b/apps/startup-scripts/test/bats.conf @@ -0,0 +1,14 @@ +# BATS Test Configuration + +# Set test timeout (in seconds) +export BATS_TEST_TIMEOUT=30 + +# Enable verbose output for debugging +export BATS_VERBOSE_RUN=1 + +# Test output format +export BATS_FORMATTER=pretty + +# Enable colored output +export BATS_NO_PARALLELIZE_ACROSS_FILES=1 +export BATS_NO_PARALLELIZE_WITHIN_FILE=1 diff --git a/apps/startup-scripts/test/test_startup_scripts.bats b/apps/startup-scripts/test/test_startup_scripts.bats new file mode 100644 index 0000000000..aaf95d1e1e --- /dev/null +++ b/apps/startup-scripts/test/test_startup_scripts.bats @@ -0,0 +1,147 @@ +#!/usr/bin/env bats + +# AzerothCore Startup Scripts Test Suite +# This script tests the basic functionality of the startup scripts using the unified test framework + +# Load the AzerothCore test framework +load '../../test-framework/bats_libs/acore-support' +load '../../test-framework/bats_libs/acore-assert' + +# Setup that runs before each test +setup() { + startup_scripts_setup + export SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/../src" && pwd)" +} + +# Cleanup that runs after each test +teardown() { + acore_test_teardown +} + +# ===== STARTER SCRIPT TESTS ===== + +@test "starter: should fail with missing parameters" { + run timeout 3s "$SCRIPT_DIR/starter" '' '' + [ "$status" -ne 0 ] + [[ "$output" =~ "Error: Binary '/' not found" ]] +} + +@test "starter: should start with valid binary" { + cd "$TEST_DIR" + run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "test-server" "" "$TEST_DIR/test-server.conf" "" "" 0 + debug_on_failure + # The starter might have issues with the script command, so we check for specific behavior + # Either it should succeed or show a specific error we can work with + [[ "$output" =~ "Test server starting" ]] || [[ "$output" =~ "script:" ]] || [[ "$status" -eq 124 ]] +} + +@test "starter: should validate binary path exists" { + run "$SCRIPT_DIR/starter" "/nonexistent/path" "test-server" + [ "$status" -ne 0 ] + [[ "$output" =~ "Binary parameter is required" ]] || [[ "$output" =~ "No such file or directory" ]] +} + +# ===== SIMPLE RESTARTER TESTS ===== + +@test "simple-restarter: should fail with missing parameters" { + run timeout 3s "$SCRIPT_DIR/simple-restarter" '' '' + [ "$status" -ne 0 ] + [[ "$output" =~ "Error: Binary '/' not found" ]] +} + +@test "simple-restarter: should fail with missing binary" { + run timeout 3s "$SCRIPT_DIR/simple-restarter" "$TEST_DIR/bin" 'nonexistent' + [ "$status" -ne 0 ] + [[ "$output" =~ "not found" ]] || [[ "$output" =~ "terminated with exit code" ]] +} + +@test "simple-restarter: should detect starter script" { + # Test that it finds the starter script + run timeout 1s "$SCRIPT_DIR/simple-restarter" '' '' + # Should not fail because starter script is missing + [[ ! "$output" =~ "starter script not found" ]] +} + +# ===== RUN-ENGINE TESTS ===== + +@test "run-engine: should show help" { + run "$SCRIPT_DIR/run-engine" help + [ "$status" -eq 0 ] + [[ "$output" =~ "AzerothCore Run Engine" ]] +} + +@test "run-engine: should validate parameters for start command" { + run "$SCRIPT_DIR/run-engine" start + [ "$status" -ne 0 ] + [[ "$output" =~ "Missing required arguments" ]] +} + +@test "run-engine: should detect binary with full path" { + run timeout 5s "$SCRIPT_DIR/run-engine" start "$TEST_DIR/bin/test-server" --server-config "$TEST_DIR/test-server.conf" + debug_on_failure + [[ "$output" =~ "Starting server: test-server" ]] || [[ "$status" -eq 124 ]] +} + +@test "run-engine: should detect binary in current directory" { + cd "$TEST_DIR/bin" + run timeout 5s "$SCRIPT_DIR/run-engine" start test-server --server-config "$TEST_DIR/test-server.conf" + debug_on_failure + [[ "$output" =~ "Binary found in current directory" ]] || [[ "$output" =~ "Starting server: test-server" ]] || [[ "$status" -eq 124 ]] +} + +@test "run-engine: should support restart mode" { + run timeout 5s "$SCRIPT_DIR/run-engine" restart "$TEST_DIR/bin/test-server" --server-config "$TEST_DIR/test-server.conf" + debug_on_failure + [[ "$output" =~ "Starting server: test-server" ]] || [[ "$status" -eq 124 ]] +} + +# ===== SERVICE MANAGER TESTS ===== + +@test "service-manager: should show help" { + run "$SCRIPT_DIR/service-manager.sh" help + [ "$status" -eq 0 ] + [[ "$output" =~ "AzerothCore Service Setup" ]] +} + +@test "service-manager: should validate create command parameters" { + run "$SCRIPT_DIR/service-manager.sh" create + [ "$status" -ne 0 ] + [[ "$output" =~ "Missing required arguments" ]] || [[ "$output" =~ "Error:" ]] +} + +# ===== EXAMPLE SCRIPTS TESTS ===== + +@test "examples: restarter-world should show configuration error" { + run "$SCRIPT_DIR/examples/restarter-world.sh" + [[ "$output" =~ "Configuration file not found" ]] +} + +@test "examples: starter-auth should show configuration error" { + run "$SCRIPT_DIR/examples/starter-auth.sh" + [[ "$output" =~ "Configuration file not found" ]] +} + +@test "examples: restarter-auth should show configuration error" { + run "$SCRIPT_DIR/examples/restarter-auth.sh" + [[ "$output" =~ "Configuration file not found" ]] +} + +@test "examples: restarter-world should show alternative suggestions" { + run "$SCRIPT_DIR/examples/restarter-world.sh" + [[ "$output" =~ "Alternative: Start with binary path directly" ]] +} + +# ===== INTEGRATION TESTS ===== + +@test "integration: starter and simple-restarter work together" { + # Test that simple-restarter can use starter + run timeout 5s "$SCRIPT_DIR/simple-restarter" "$TEST_DIR/bin" "test-server" + # Should start and then restart at least once + [[ "$output" =~ "terminated with exit code" ]] || [[ "$status" -eq 124 ]] +} + +@test "integration: run-engine can handle missing config gracefully" { + run timeout 3s "$SCRIPT_DIR/run-engine" start "$TEST_DIR/bin/test-server" + # Should either work or give a meaningful error + [[ "$status" -eq 124 ]] || [[ "$status" -eq 0 ]] || [[ "$output" =~ "config" ]] +} diff --git a/apps/test-framework/README.md b/apps/test-framework/README.md new file mode 100644 index 0000000000..dda5237067 --- /dev/null +++ b/apps/test-framework/README.md @@ -0,0 +1,335 @@ +# AzerothCore Test Framework + +This is the centralized test framework for all AzerothCore bash scripts. It provides a unified way to write, run, and manage tests across all modules. + +## Structure + +``` +apps/test-framework/ +├── run-tests.sh # Universal test runner (single entry point) +├── README.md # This documentation +├── bats_libs/ # Custom BATS libraries +│ ├── acore-support.bash # Test setup and helpers +│ └── acore-assert.bash # Custom assertions +└── helpers/ # Test utilities + └── test_common.sh # Common test functions and setup +``` + +## Quick Start + +### From any module directory: +```bash +# Run tests for current module +../test-framework/run-tests.sh --dir . + +``` + +### From test-framework directory: +```bash +# Run all tests in all modules +./run-tests.sh --all + +# Run tests for specific module +./run-tests.sh startup-scripts + +# List available modules +./run-tests.sh --list + +# Run tests with debug info +./run-tests.sh --all --debug +``` + +### From project root: +```bash +# Run all tests +apps/test-framework/run-tests.sh --all + +# Run specific module +apps/test-framework/run-tests.sh startup-scripts + +# Run with verbose output +apps/test-framework/run-tests.sh startup-scripts --verbose +``` + +## Usage + +### Basic Commands + +```bash +# Run all tests +./run-tests.sh --all + +# Run tests for specific module +./run-tests.sh startup-scripts + +# Run tests matching pattern +./run-tests.sh --filter starter + +# Run tests in specific directory +./run-tests.sh --dir apps/docker + +# Show available modules +./run-tests.sh --list + +# Show test count +./run-tests.sh --count +``` + +### Output Formats + +```bash +# Pretty output (default) +./run-tests.sh --pretty + +# TAP output for CI/CD +./run-tests.sh --tap + +# Verbose output with debug info +./run-tests.sh --verbose --debug +``` + +## Writing Tests + +### Basic Test Structure + +```bash +#!/usr/bin/env bats + +# Load the AzerothCore test framework +load '../../test-framework/bats_libs/acore-support' +load '../../test-framework/bats_libs/acore-assert' + +setup() { + acore_test_setup # Standard setup + # or + startup_scripts_setup # For startup scripts + # or + compiler_setup # For compiler tests + # or + docker_setup # For docker tests +} + +teardown() { + acore_test_teardown +} + +@test "my test description" { + run my_command + assert_success + assert_output "expected output" +} +``` + +### Available Setup Functions + +- `acore_test_setup` - Basic setup for all tests +- `startup_scripts_setup` - Setup for startup script tests +- `compiler_setup` - Setup for compiler tests +- `docker_setup` - Setup for docker tests +- `extractor_setup` - Setup for extractor tests + +### Custom Assertions + +```bash +# Assert binary exists and is executable +assert_binary_exists "$TEST_DIR/bin/authserver" + +# Assert server started correctly +assert_acore_server_started "$output" "authserver" + +# Assert config was loaded +assert_config_loaded "$output" "authserver.conf" + +# Assert build success +assert_build_success "$output" + +# Assert timeout occurred (for long-running processes) +assert_timeout "$status" + +# Assert log contains content +assert_log_contains "$log_file" "Server started" +``` + +### Test Environment Variables + +When using the framework, these variables are automatically set: + +- `$TEST_DIR` - Temporary test directory +- `$AC_TEST_ROOT` - Project root directory +- `$AC_TEST_APPS` - Apps directory +- `$BUILDPATH` - Build directory path +- `$SRCPATH` - Source directory path +- `$BINPATH` - Binary directory path +- `$LOGS_PATH` - Logs directory path + +### Helper Functions + +```bash +# Create test binary +create_test_binary "authserver" 0 2 "Server started" + +# Create test config +create_test_config "authserver.conf" "Database.Info = \"127.0.0.1;3306;root;pass;db\"" + +# Create AzerothCore specific binaries and configs +create_acore_binaries +create_acore_configs + +# Run command with timeout +run_with_timeout 5s my_command + +# Wait for condition +wait_for_condition "test -f $TEST_DIR/ready" 10 1 + +# Debug test failure +debug_on_failure +``` + +## Module Integration + +### Adding Tests to a New Module + +1. Create a `test/` directory in your module: + ```bash + mkdir apps/my-module/test + ``` + +2. Create test files (ending in `.bats`): + ```bash + touch apps/my-module/test/test_my_feature.bats + ``` + +3. Write your tests using the framework (see examples above) + +### Running Tests + +From your module directory: +```bash +../test-framework/run-tests.sh --dir . +``` + +From the test framework: +```bash +./run-tests.sh my-module +``` + +From project root: +```bash +apps/test-framework/run-tests.sh my-module +``` + +## CI/CD Integration + +For continuous integration, use TAP output: + +```bash +# In your CI script +cd apps/test-framework +./run-tests.sh --all --tap > test-results.tap + +# Or from project root +apps/test-framework/run-tests.sh --all --tap > test-results.tap +``` + +## Available Commands + +All functionality is available through the single `run-tests.sh` script: + +### Basic Test Execution +- `./run-tests.sh --all` - Run all tests in all modules +- `./run-tests.sh <module>` - Run tests for specific module +- `./run-tests.sh --dir <path>` - Run tests in specific directory +- `./run-tests.sh --list` - List available modules +- `./run-tests.sh --count` - Show test count + +### Output Control +- `./run-tests.sh --verbose` - Verbose output with debug info +- `./run-tests.sh --tap` - TAP output for CI/CD +- `./run-tests.sh --debug` - Debug mode with failure details +- `./run-tests.sh --pretty` - Pretty output (default) + +### Test Filtering +- `./run-tests.sh --filter <pattern>` - Run tests matching pattern +- `./run-tests.sh <module> --filter <pattern>` - Filter within module + +### Utility Functions +- `./run-tests.sh --help` - Show help message +- Install BATS: Use your system package manager (`apt install bats`, `brew install bats-core`, etc.) + + +### Direct Script Usage + +## Examples + +### Running Specific Tests +```bash +# Run only starter-related tests +./run-tests.sh --filter starter + +# Run only tests in startup-scripts module +./run-tests.sh startup-scripts + +# Run all tests with verbose output +./run-tests.sh --all --verbose + +# Run tests in specific directory with debug +./run-tests.sh --dir apps/docker --debug +``` + +### Development Workflow +```bash +# While developing, run tests frequently from module directory +cd apps/my-module +../test-framework/run-tests.sh --dir . + +# Debug failing tests +../test-framework/run-tests.sh --dir . --debug --verbose + +# Run specific test pattern +../test-framework/run-tests.sh --dir . --filter my-feature + +# From project root - run all tests +apps/test-framework/run-tests.sh --all + +# Quick test count check +apps/test-framework/run-tests.sh --count +``` + +## Benefits + +1. **No Boilerplate**: Minimal setup required for new test modules +2. **Consistent Environment**: All tests use the same setup/teardown +3. **Reusable Utilities**: Common functions available across all tests +4. **Centralized Management**: Single place to update test infrastructure +5. **Flexible Execution**: Run tests for one module, multiple modules, or all modules +6. **CI/CD Ready**: TAP output format supported +7. **Easy Debugging**: Built-in debug helpers and verbose output + +## Dependencies + +- [BATS (Bash Automated Testing System)](https://github.com/bats-core/bats-core) +- Standard Unix utilities (find, grep, timeout, etc.) + +Install BATS with your system package manager: +```bash +# Ubuntu/Debian +sudo apt update && sudo apt install bats + +# Fedora/RHEL +sudo dnf install bats + +# macOS +brew install bats-core + +# Arch Linux +sudo pacman -S bats +``` + +## Contributing + +When adding new test utilities: + +1. Add common functions to `helpers/test_common.sh` +2. Add BATS-specific helpers to `bats_libs/acore-support.bash` +3. Add custom assertions to `bats_libs/acore-assert.bash` +4. Update this README with new functionality diff --git a/apps/test-framework/bats_libs/acore-assert.bash b/apps/test-framework/bats_libs/acore-assert.bash new file mode 100644 index 0000000000..ad0e6911ec --- /dev/null +++ b/apps/test-framework/bats_libs/acore-assert.bash @@ -0,0 +1,178 @@ +#!/usr/bin/env bash + +# AzerothCore BATS Assertions Library +# Custom assertions for AzerothCore testing + +# Assert that a binary exists and is executable +assert_binary_exists() { + local binary_path="$1" + local message="${2:-Binary should exist and be executable}" + + if [[ ! -f "$binary_path" ]]; then + echo "Binary not found: $binary_path" + echo "$message" + return 1 + fi + + if [[ ! -x "$binary_path" ]]; then + echo "Binary not executable: $binary_path" + echo "$message" + return 1 + fi +} + +# Assert that output contains specific AzerothCore patterns +assert_acore_server_started() { + local output="$1" + local server_type="$2" + local message="${3:-Server should show startup message}" + + if [[ ! "$output" =~ $server_type.*starting ]]; then + echo "Server start message not found for $server_type" + echo "Expected pattern: '$server_type.*starting'" + echo "Actual output: $output" + echo "$message" + return 1 + fi +} + +# Assert that configuration file was loaded +assert_config_loaded() { + local output="$1" + local config_file="$2" + local message="${3:-Configuration file should be loaded}" + + if [[ ! "$output" =~ config.*$config_file ]] && [[ ! "$output" =~ $config_file ]]; then + echo "Configuration file loading not detected: $config_file" + echo "Expected to find: config.*$config_file OR $config_file" + echo "Actual output: $output" + echo "$message" + return 1 + fi +} + +# Assert that a process exited with expected code +assert_exit_code() { + local actual_code="$1" + local expected_code="$2" + local message="${3:-Process should exit with expected code}" + + if [[ "$actual_code" -ne "$expected_code" ]]; then + echo "Expected exit code: $expected_code" + echo "Actual exit code: $actual_code" + echo "$message" + return 1 + fi +} + +# Assert that output contains specific error pattern +assert_error_message() { + local output="$1" + local error_pattern="$2" + local message="${3:-Output should contain expected error message}" + + if [[ ! "$output" =~ $error_pattern ]]; then + echo "Expected error pattern not found: $error_pattern" + echo "Actual output: $output" + echo "$message" + return 1 + fi +} + +# Assert that a file was created +assert_file_created() { + local file_path="$1" + local message="${2:-File should be created}" + + if [[ ! -f "$file_path" ]]; then + echo "File not created: $file_path" + echo "$message" + return 1 + fi +} + +# Assert that a directory was created +assert_directory_created() { + local dir_path="$1" + local message="${2:-Directory should be created}" + + if [[ ! -d "$dir_path" ]]; then + echo "Directory not created: $dir_path" + echo "$message" + return 1 + fi +} + +# Assert that output contains success message +assert_success_message() { + local output="$1" + local success_pattern="${2:-success|completed|finished|done}" + local message="${3:-Output should contain success message}" + + if [[ ! "$output" =~ $success_pattern ]]; then + echo "Success message not found" + echo "Expected pattern: $success_pattern" + echo "Actual output: $output" + echo "$message" + return 1 + fi +} + +# Assert that build was successful +assert_build_success() { + local output="$1" + local message="${2:-Build should complete successfully}" + + local build_success_patterns="Build completed|compilation successful|build.*success|make.*success" + assert_success_message "$output" "$build_success_patterns" "$message" +} + +# Assert that server is responsive +assert_server_responsive() { + local output="$1" + local server_type="$2" + local message="${3:-Server should be responsive}" + + if [[ ! "$output" =~ $server_type.*initialized ]] && [[ ! "$output" =~ $server_type.*ready ]]; then + echo "Server responsiveness not detected for $server_type" + echo "Expected pattern: '$server_type.*initialized' OR '$server_type.*ready'" + echo "Actual output: $output" + echo "$message" + return 1 + fi +} + +# Assert that timeout occurred (for long-running processes) +assert_timeout() { + local exit_code="$1" + local message="${2:-Process should timeout as expected}" + + if [[ "$exit_code" -ne 124 ]]; then + echo "Expected timeout (exit code 124)" + echo "Actual exit code: $exit_code" + echo "$message" + return 1 + fi +} + +# Assert that log file contains expected content +assert_log_contains() { + local log_file="$1" + local expected_content="$2" + local message="${3:-Log file should contain expected content}" + + if [[ ! -f "$log_file" ]]; then + echo "Log file not found: $log_file" + echo "$message" + return 1 + fi + + if ! grep -q "$expected_content" "$log_file"; then + echo "Expected content not found in log: $expected_content" + echo "Log file: $log_file" + echo "Log contents:" + cat "$log_file" | head -20 + echo "$message" + return 1 + fi +} diff --git a/apps/test-framework/bats_libs/acore-support.bash b/apps/test-framework/bats_libs/acore-support.bash new file mode 100644 index 0000000000..afbeb4fd9a --- /dev/null +++ b/apps/test-framework/bats_libs/acore-support.bash @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +# AzerothCore BATS Support Library +# Additional helper functions for BATS testing + +# Load common test utilities +source "$(dirname "${BASH_SOURCE[0]}")/../helpers/test_common.sh" + +# Standard setup for all AzerothCore tests +acore_test_setup() { + setup_test_env + create_acore_binaries + create_acore_configs +} + +# Standard teardown for all AzerothCore tests +acore_test_teardown() { + cleanup_test_env +} + +# Quick setup for startup script tests +startup_scripts_setup() { + acore_test_setup + create_test_script_config "test" "test-server" + + # Create additional test binary for startup scripts + create_test_binary "test-server" 0 2 "Test server starting with config:" + + # Create the test-server.conf file that tests expect + cat > "$TEST_DIR/test-server.conf" << EOF +# Test server configuration file +# Generated by AzerothCore test framework +Database.Info = "127.0.0.1;3306;acore;acore;acore_world" +LoginDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_auth" +CharacterDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_characters" +EOF +} + +# Quick setup for compiler tests +compiler_setup() { + acore_test_setup + + # Create mock build tools + create_test_binary "gcc" 0 1 + create_test_binary "g++" 0 1 + create_test_binary "ninja" 0 2 + + # Create mock CMake files + mkdir -p "$TEST_DIR/build" + touch "$TEST_DIR/build/CMakeCache.txt" + echo "CMAKE_BUILD_TYPE:STRING=RelWithDebInfo" > "$TEST_DIR/build/CMakeCache.txt" +} + +# Quick setup for docker tests +docker_setup() { + acore_test_setup + + # Create mock docker commands + create_test_binary "docker" 0 1 "Docker container started" + create_test_binary "docker-compose" 0 2 "Docker Compose services started" + + # Create test docker files + cat > "$TEST_DIR/Dockerfile" << 'EOF' +FROM ubuntu:20.04 +RUN apt-get update +EOF + + cat > "$TEST_DIR/docker-compose.yml" << 'EOF' +version: '3.8' +services: + test-service: + image: ubuntu:20.04 +EOF +} + +# Quick setup for extractor tests +extractor_setup() { + acore_test_setup + + # Create mock client data directories + mkdir -p "$TEST_DIR/client"/{Maps,vmaps,mmaps,dbc} + + # Create some test data files + echo "Test map data" > "$TEST_DIR/client/Maps/test.map" + echo "Test DBC data" > "$TEST_DIR/client/dbc/test.dbc" +} + +# Helper to run command with timeout and capture output +run_with_timeout() { + local timeout_duration="$1" + shift + run timeout "$timeout_duration" "$@" +} + +# Helper to check if a process is running +process_running() { + local process_name="$1" + pgrep -f "$process_name" >/dev/null 2>&1 +} + +# Helper to wait for a condition +wait_for_condition() { + local condition="$1" + local timeout="${2:-10}" + local interval="${3:-1}" + + local count=0 + while ! eval "$condition"; do + sleep "$interval" + count=$((count + interval)) + if [[ $count -ge $timeout ]]; then + return 1 + fi + done + return 0 +} diff --git a/apps/test-framework/helpers/test_common.sh b/apps/test-framework/helpers/test_common.sh new file mode 100644 index 0000000000..67193cacbb --- /dev/null +++ b/apps/test-framework/helpers/test_common.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash + +# AzerothCore Test Common Utilities +# Shared functions and setup for all BATS tests + +export AC_TEST_FRAMEWORK_VERSION="1.0.0" + +# Get paths +AC_TEST_FRAMEWORK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +AC_PROJECT_ROOT="$(cd "$AC_TEST_FRAMEWORK_DIR/../.." && pwd)" + +# Common test environment setup +setup_test_env() { + export TEST_DIR="$(mktemp -d)" + export AC_TEST_ROOT="$AC_PROJECT_ROOT" + export AC_TEST_APPS="$AC_TEST_ROOT/apps" + + # Create standard test directory structure + mkdir -p "$TEST_DIR"/{bin,etc,logs,data,crashes,build} + + # Set up test-specific environment variables + export ORIGINAL_PATH="$PATH" + export PATH="$TEST_DIR/bin:$PATH" + + # Common environment variables for AzerothCore + export BUILDPATH="$TEST_DIR/build" + export SRCPATH="$AC_TEST_ROOT" + export BINPATH="$TEST_DIR/bin" + export LOGS_PATH="$TEST_DIR/logs" +} + +cleanup_test_env() { + if [[ -n "$TEST_DIR" && -d "$TEST_DIR" ]]; then + rm -rf "$TEST_DIR" + fi + if [[ -n "$ORIGINAL_PATH" ]]; then + export PATH="$ORIGINAL_PATH" + fi +} + +# Create standard test binary +create_test_binary() { + local binary_name="$1" + local exit_code="${2:-0}" + local runtime="${3:-2}" + local extra_output="${4:-""}" + + cat > "$TEST_DIR/bin/$binary_name" << EOF +#!/usr/bin/env bash +echo "$binary_name starting with config: \$2" +echo "$binary_name running for $runtime seconds..." +if [[ -n "$extra_output" ]]; then + echo "$extra_output" +fi +sleep $runtime +echo "$binary_name exiting with code $exit_code" +exit $exit_code +EOF + chmod +x "$TEST_DIR/bin/$binary_name" +} + +# Create test configuration file +create_test_config() { + local config_name="$1" + local content="$2" + + cat > "$TEST_DIR/etc/$config_name" << EOF +# Test configuration file: $config_name +# Generated by AzerothCore test framework +$content +EOF +} + +# Create AzerothCore specific test binaries +create_acore_binaries() { + create_test_binary "authserver" 0 1 "AuthServer initialized" + create_test_binary "worldserver" 0 2 "WorldServer initialized" + create_test_binary "cmake" 0 1 "CMake configured" + create_test_binary "make" 0 2 "Build completed" + create_test_binary "mapextractor" 0 3 "Map extraction completed" + create_test_binary "vmap4extractor" 0 2 "VMap extraction completed" + create_test_binary "vmap4assembler" 0 1 "VMap assembly completed" + create_test_binary "mmaps_generator" 0 5 "MMap generation completed" +} + +# Create AzerothCore specific test configs +create_acore_configs() { + create_test_config "authserver.conf" 'Database.Info = "127.0.0.1;3306;acore;acore;acore_auth" +LoginDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_auth"' + + create_test_config "worldserver.conf" 'Database.Info = "127.0.0.1;3306;acore;acore;acore_world" +LoginDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_auth" +CharacterDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_characters"' + + create_test_config "config.sh" "export BUILDPATH=\"$TEST_DIR/build\" +export SRCPATH=\"$AC_TEST_ROOT\" +export BINPATH=\"$TEST_DIR/bin\" +export LOGS_PATH=\"$TEST_DIR/logs\"" +} + +# Create a test script configuration (for startup scripts) +create_test_script_config() { + local script_name="$1" + local binary_name="${2:-authserver}" + + cat > "$TEST_DIR/conf-$script_name.sh" << EOF +export BINPATH="$TEST_DIR/bin" +export SERVERBIN="$binary_name" +export CONFIG="$TEST_DIR/etc/$binary_name.conf" +export LOGS_PATH="$TEST_DIR/logs" +export LOG_PREFIX_NAME="$script_name" +export SCREEN_NAME="AC-$script_name" +export GDB_ENABLED=0 +export WITH_CONSOLE=1 +EOF +} + +# Debug helper function +debug_on_failure() { + if [[ "$status" -ne 0 ]]; then + echo "Command failed with status: $status" >&3 + echo "Output was:" >&3 + echo "$output" >&3 + if [[ -n "$TEST_DIR" ]]; then + echo "Test directory contents:" >&3 + ls -la "$TEST_DIR" >&3 2>/dev/null || true + fi + fi +} + +# Print test environment info +print_test_env() { + echo "Test Environment:" >&3 + echo " TEST_DIR: $TEST_DIR" >&3 + echo " AC_TEST_ROOT: $AC_TEST_ROOT" >&3 + echo " AC_TEST_APPS: $AC_TEST_APPS" >&3 + echo " PATH: $PATH" >&3 +} + +# Check if running in test mode +is_test_mode() { + [[ -n "$BATS_TEST_FILENAME" ]] || [[ -n "$TEST_DIR" ]] +} diff --git a/apps/test-framework/run-tests.sh b/apps/test-framework/run-tests.sh new file mode 100755 index 0000000000..0cf4c08a4a --- /dev/null +++ b/apps/test-framework/run-tests.sh @@ -0,0 +1,290 @@ +#!/usr/bin/env bash + +# AzerothCore Universal Test Runner +# This script provides a unified way to run BATS tests across all modules + +# Get the script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +show_help() { + echo -e "${BLUE}AzerothCore Universal Test Runner${NC}" + echo "" + echo "Usage: $0 [OPTIONS] [TEST_MODULES...]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -v, --verbose Enable verbose output" + echo " -t, --tap Use TAP output format (for CI/CD)" + echo " -p, --pretty Use pretty output format (default)" + echo " -f, --filter Run only tests matching pattern" + echo " -c, --count Show test count only" + echo " -d, --debug Enable debug mode (shows output on failure)" + echo " -l, --list List available test modules" + echo " --dir <path> Run tests in specific directory" + echo " --all Run all tests in all modules" + echo "" + echo "Test Modules:" + echo " startup-scripts - Startup script tests" + echo " compiler - Compiler script tests" + echo " docker - Docker-related tests" + echo " installer - Installer script tests" + echo "" + echo "Examples:" + echo " $0 # Run tests in current directory" + echo " $0 --all # Run all tests in all modules" + echo " $0 startup-scripts # Run startup-scripts tests only" + echo " $0 --dir apps/docker # Run tests in specific directory" + echo " $0 --verbose startup-scripts # Run with verbose output" + echo " $0 --filter starter # Run only tests matching 'starter'" + echo " $0 --tap # Output in TAP format for CI" +} + +# Parse command line arguments +VERBOSE=false +TAP=false +PRETTY=true +FILTER="" +COUNT_ONLY=false +DEBUG=false +LIST_MODULES=false +RUN_ALL=false +TEST_DIRS=() +TEST_MODULES=() + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + exit 0 + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -t|--tap) + TAP=true + PRETTY=false + shift + ;; + -p|--pretty) + PRETTY=true + TAP=false + shift + ;; + -f|--filter) + FILTER="$2" + shift 2 + ;; + -c|--count) + COUNT_ONLY=true + shift + ;; + -d|--debug) + DEBUG=true + shift + ;; + -l|--list) + LIST_MODULES=true + shift + ;; + --dir) + TEST_DIRS+=("$2") + shift 2 + ;; + --all) + RUN_ALL=true + shift + ;; + *.bats) + # Individual test files + TEST_FILES+=("$1") + shift + ;; + *) + # Assume it's a module name + TEST_MODULES+=("$1") + shift + ;; + esac +done + +# Check if BATS is installed +if ! command -v bats >/dev/null 2>&1; then + echo -e "${RED}Error: BATS is not installed${NC}" + echo "Please install BATS first:" + echo " sudo apt install bats # On Ubuntu/Debian" + echo " brew install bats-core # On macOS" + echo "Or run: make install-bats" + exit 1 +fi + +# Function to find test directories +find_test_directories() { + local search_paths=() + + if [[ "$RUN_ALL" == true ]]; then + # Find all test directories + mapfile -t search_paths < <(find "$PROJECT_ROOT/apps" -type d -name "test" 2>/dev/null) + elif [[ ${#TEST_DIRS[@]} -gt 0 ]]; then + # Use specified directories + for dir in "${TEST_DIRS[@]}"; do + if [[ -d "$PROJECT_ROOT/$dir/test" ]]; then + search_paths+=("$PROJECT_ROOT/$dir/test") + elif [[ -d "$dir/test" ]]; then + search_paths+=("$dir/test") + elif [[ -d "$dir" ]]; then + search_paths+=("$dir") + else + echo -e "${YELLOW}Warning: Test directory not found: $dir${NC}" + fi + done + elif [[ ${#TEST_MODULES[@]} -gt 0 ]]; then + # Use specified modules + for module in "${TEST_MODULES[@]}"; do + if [[ -d "$PROJECT_ROOT/apps/$module/test" ]]; then + search_paths+=("$PROJECT_ROOT/apps/$module/test") + else + echo -e "${YELLOW}Warning: Module test directory not found: $module${NC}" + fi + done + else + # Default: use current directory or startup-scripts if run from test-framework + if [[ "$(basename "$PWD")" == "test-framework" ]]; then + search_paths=("$PROJECT_ROOT/apps/startup-scripts/test") + elif [[ -d "./test" ]]; then + search_paths=("./test") + else + echo -e "${YELLOW}No test directory found. Use --all or specify a module.${NC}" + exit 0 + fi + fi + + echo "${search_paths[@]}" +} + +# Function to list available modules +list_modules() { + echo -e "${BLUE}Available test modules:${NC}" + find "$PROJECT_ROOT/apps" -type d -name "test" 2>/dev/null | while read -r test_dir; do + module_name=$(basename "$(dirname "$test_dir")") + test_count=$(find "$test_dir" -name "*.bats" | wc -l) + echo -e " ${GREEN}$module_name${NC} ($test_count test files)" + done +} + +# Show available modules if requested +if [[ "$LIST_MODULES" == true ]]; then + list_modules + exit 0 +fi + +# Find test directories +TEST_SEARCH_PATHS=($(find_test_directories)) + +if [[ ${#TEST_SEARCH_PATHS[@]} -eq 0 ]]; then + echo -e "${YELLOW}No test directories found.${NC}" + echo "Use --list to see available modules." + exit 0 +fi + +# Collect all test files +TEST_FILES=() +for test_dir in "${TEST_SEARCH_PATHS[@]}"; do + if [[ -d "$test_dir" ]]; then + if [[ -n "$FILTER" ]]; then + # Find test files matching filter + mapfile -t filtered_files < <(find "$test_dir" -name "*.bats" -exec grep -l "$FILTER" {} \; 2>/dev/null) + TEST_FILES+=("${filtered_files[@]}") + else + # Use all test files in directory + mapfile -t dir_files < <(find "$test_dir" -name "*.bats" 2>/dev/null) + TEST_FILES+=("${dir_files[@]}") + fi + fi +done + +if [[ ${#TEST_FILES[@]} -eq 0 ]]; then + if [[ -n "$FILTER" ]]; then + echo -e "${YELLOW}No test files found matching filter: $FILTER${NC}" + else + echo -e "${YELLOW}No test files found in specified directories.${NC}" + fi + exit 0 +fi + +# Show test count only +if [[ "$COUNT_ONLY" == true ]]; then + total_tests=0 + for file in "${TEST_FILES[@]}"; do + count=$(grep -c "^@test" "$file" 2>/dev/null || echo 0) + total_tests=$((total_tests + count)) + done + echo "Total tests: $total_tests" + echo "Test files: ${#TEST_FILES[@]}" + echo "Test directories: ${#TEST_SEARCH_PATHS[@]}" + exit 0 +fi + +# Build BATS command +BATS_CMD="bats" + +# Set output format +if [[ "$TAP" == true ]]; then + BATS_CMD+=" --formatter tap" +elif [[ "$PRETTY" == true ]]; then + BATS_CMD+=" --formatter pretty" +fi + +# Enable verbose output +if [[ "$VERBOSE" == true ]]; then + BATS_CMD+=" --verbose-run" +fi + +# Add filter if specified +if [[ -n "$FILTER" ]]; then + BATS_CMD+=" --filter '$FILTER'" +fi + +# Add test files +BATS_CMD+=" ${TEST_FILES[*]}" + +echo -e "${BLUE}Running AzerothCore Tests${NC}" +echo -e "${YELLOW}Test directories: ${TEST_SEARCH_PATHS[*]}${NC}" +echo -e "${YELLOW}Test files: ${#TEST_FILES[@]}${NC}" +if [[ -n "$FILTER" ]]; then + echo -e "${YELLOW}Filter: $FILTER${NC}" +fi +echo "" + +# Run tests +if [[ "$DEBUG" == true ]]; then + echo -e "${YELLOW}Command: $BATS_CMD${NC}" + echo "" +fi + +# Execute BATS +if eval "$BATS_CMD"; then + echo "" + echo -e "${GREEN}✅ All tests passed!${NC}" + exit 0 +else + exit_code=$? + echo "" + echo -e "${RED}❌ Some tests failed!${NC}" + + if [[ "$DEBUG" == true ]]; then + echo -e "${YELLOW}Tip: Check the output above for detailed error information${NC}" + echo -e "${YELLOW}You can also run individual tests for more detailed debugging:${NC}" + echo -e "${YELLOW} $0 --verbose --filter <test_name>${NC}" + fi + + exit $exit_code +fi diff --git a/conf/dist/config.sh b/conf/dist/config.sh index 3c85c0c18e..df7ddd3fc2 100644 --- a/conf/dist/config.sh +++ b/conf/dist/config.sh @@ -14,6 +14,10 @@ BINPATH="$AC_PATH_ROOT/env/dist" # Change it if you really know what you're doing. # OSTYPE="" +# Configuration for the installer to skip the MySQL installation. +# This is useful when your MySQL is in a container or another machine. +SKIP_MYSQL_INSTALL=${SKIP_MYSQL_INSTALL:-false} + # When using linux, our installer automatically get information about your distro # using lsb_release. If your distro is not supported but it's based on ubuntu or debian, # please change it to one of these values. @@ -84,6 +88,7 @@ CCOREPCH=${CCOREPCH:-ON} CAPPS_BUILD=${CAPPS_BUILD:-all} # build tools list variable +# example: none, db-only, maps-only, all CTOOLS_BUILD=${CTOOLS_BUILD:-none} # build apps list diff --git a/data/sql/create/create_mysql.sql b/data/sql/create/create_mysql.sql index c9af8c3cd8..5e362df791 100644 --- a/data/sql/create/create_mysql.sql +++ b/data/sql/create/create_mysql.sql @@ -3,11 +3,12 @@ CREATE USER 'acore'@'localhost' IDENTIFIED BY 'acore' WITH MAX_QUERIES_PER_HOUR GRANT ALL PRIVILEGES ON * . * TO 'acore'@'localhost' WITH GRANT OPTION; -CREATE DATABASE `acore_world` DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; +-- Create databases for AzerothCore, only if they do not exist +CREATE DATABASE IF NOT EXISTS `acore_world` DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; -CREATE DATABASE `acore_characters` DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS `acore_characters` DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; -CREATE DATABASE `acore_auth` DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS `acore_auth` DEFAULT CHARACTER SET UTF8MB4 COLLATE utf8mb4_unicode_ci; GRANT ALL PRIVILEGES ON `acore_world` . * TO 'acore'@'localhost' WITH GRANT OPTION; |