summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYehonal <yehonal.azeroth@gmail.com>2025-07-01 15:35:54 +0200
committerGitHub <noreply@github.com>2025-07-01 15:35:54 +0200
commite1b2689c3a2b1395323d2fc58588b1e1b3c07c53 (patch)
treef36a888ec1fef286ac59607c8a513668286e4065
parentd3130f0d39064d03bed969c7bc135ebb6066442f (diff)
feat(bash): startup-scripts reworked + bash scripts workflow integration (#22401)
-rw-r--r--.devcontainer/devcontainer.json37
-rw-r--r--.github/workflows/dashboard-ci.yml82
-rw-r--r--.vscode/extensions.json5
-rw-r--r--.vscode/settings.json9
-rw-r--r--CMakeLists.txt3
-rw-r--r--apps/compiler/includes/functions.sh26
-rw-r--r--apps/compiler/includes/includes.sh2
-rw-r--r--apps/installer/includes/functions.sh46
-rw-r--r--apps/installer/includes/os_configs/debian.sh17
-rw-r--r--apps/installer/includes/os_configs/ubuntu.sh40
-rw-r--r--apps/installer/main.sh10
-rw-r--r--apps/startup-scripts/README.md497
-rw-r--r--apps/startup-scripts/conf.sh.dist50
-rw-r--r--apps/startup-scripts/examples/restarter-auth.sh14
-rw-r--r--apps/startup-scripts/examples/restarter-world.sh14
-rw-r--r--apps/startup-scripts/examples/starter-auth.sh13
-rw-r--r--apps/startup-scripts/examples/starter-world.sh14
-rw-r--r--apps/startup-scripts/run-engine115
-rwxr-xr-xapps/startup-scripts/simple-restarter70
-rw-r--r--apps/startup-scripts/src/.gitignore1
-rw-r--r--apps/startup-scripts/src/conf.sh.dist57
-rwxr-xr-xapps/startup-scripts/src/examples/restarter-auth.sh48
-rwxr-xr-xapps/startup-scripts/src/examples/restarter-world.sh47
-rwxr-xr-xapps/startup-scripts/src/examples/starter-auth.sh46
-rwxr-xr-xapps/startup-scripts/src/examples/starter-world.sh47
-rw-r--r--apps/startup-scripts/src/gdb.conf (renamed from apps/startup-scripts/gdb.conf)0
-rwxr-xr-xapps/startup-scripts/src/run-engine467
-rwxr-xr-xapps/startup-scripts/src/service-manager.sh1261
-rwxr-xr-xapps/startup-scripts/src/simple-restarter89
-rwxr-xr-xapps/startup-scripts/src/starter117
-rw-r--r--apps/startup-scripts/starter32
-rw-r--r--apps/startup-scripts/test/bats.conf14
-rw-r--r--apps/startup-scripts/test/test_startup_scripts.bats147
-rw-r--r--apps/test-framework/README.md335
-rw-r--r--apps/test-framework/bats_libs/acore-assert.bash178
-rw-r--r--apps/test-framework/bats_libs/acore-support.bash116
-rw-r--r--apps/test-framework/helpers/test_common.sh143
-rwxr-xr-xapps/test-framework/run-tests.sh290
-rw-r--r--conf/dist/config.sh5
-rw-r--r--data/sql/create/create_mysql.sql7
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;