diff options
Diffstat (limited to 'apps')
22 files changed, 1218 insertions, 165 deletions
diff --git a/apps/EnumUtils/enumutils_describe.py b/apps/EnumUtils/enumutils_describe.py index ef9bf72a4f..b1adee2aba 100644 --- a/apps/EnumUtils/enumutils_describe.py +++ b/apps/EnumUtils/enumutils_describe.py @@ -4,14 +4,14 @@ from os import walk, getcwd notice = ('''/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation; either version 3 of the License, or (at your - * option) any later version. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along diff --git a/apps/bash_shared/common.sh b/apps/bash_shared/common.sh index acd23eacdf..46422119b1 100644 --- a/apps/bash_shared/common.sh +++ b/apps/bash_shared/common.sh @@ -1,17 +1,19 @@ function registerHooks() { acore_event_registerHooks "$@"; } function runHooks() { acore_event_runHooks "$@"; } -#shellcheck source=../../conf/dist/config.sh -source "$AC_PATH_CONF/dist/config.sh" # include dist to avoid missing conf variables +function acore_common_loadConfig() { + #shellcheck source=../../conf/dist/config.sh + source "$AC_PATH_CONF/dist/config.sh" # include dist to avoid missing conf variables -# first check if it's defined in env, otherwise use the default -USER_CONF_PATH=${USER_CONF_PATH:-"$AC_PATH_CONF/config.sh"} + # first check if it's defined in env, otherwise use the default + USER_CONF_PATH=${USER_CONF_PATH:-"$AC_PATH_CONF/config.sh"} -if [ -f "$USER_CONF_PATH" ]; then - source "$USER_CONF_PATH" # should overwrite previous -else - echo "NOTICE: file <$USER_CONF_PATH> not found, we use default configuration only." -fi + if [ -f "$USER_CONF_PATH" ]; then + source "$USER_CONF_PATH" # should overwrite previous + else + echo "NOTICE: file <$USER_CONF_PATH> not found, we use default configuration only." + fi +} # # Load modules diff --git a/apps/bash_shared/defines.sh b/apps/bash_shared/defines.sh index 4b014bd9c1..af9e9dfc97 100644 --- a/apps/bash_shared/defines.sh +++ b/apps/bash_shared/defines.sh @@ -25,4 +25,6 @@ export AC_PATH_MODULES="$AC_PATH_ROOT/modules" export AC_PATH_DEPS="$AC_PATH_ROOT/deps" +export AC_BASH_LIB_PATH="$AC_PATH_DEPS/acore/bash-lib/src" + export AC_PATH_VAR="$AC_PATH_ROOT/var" diff --git a/apps/bash_shared/includes.sh b/apps/bash_shared/includes.sh index d2bf07db12..679fc8e6d3 100644 --- a/apps/bash_shared/includes.sh +++ b/apps/bash_shared/includes.sh @@ -16,6 +16,8 @@ source "$AC_PATH_DEPS/acore/bash-lib/src/event/hooks.sh" # shellcheck source=./common.sh source "$AC_PATH_SHARED/common.sh" +acore_common_loadConfig + if [[ "$OSTYPE" = "msys" ]]; then AC_BINPATH_FULL="$BINPATH" else diff --git a/apps/ci/ci-install-modules.sh b/apps/ci/ci-install-modules.sh index 5987eaf7ad..7ff6470750 100755 --- a/apps/ci/ci-install-modules.sh +++ b/apps/ci/ci-install-modules.sh @@ -35,7 +35,7 @@ git clone --depth=1 --branch=master https://github.com/azerothcore/mod-detailed- git clone --depth=1 --branch=main https://github.com/azerothcore/mod-dmf-switch modules/mod-dmf-switch git clone --depth=1 --branch=master https://github.com/azerothcore/mod-duel-reset modules/mod-duel-reset git clone --depth=1 --branch=master https://github.com/azerothcore/mod-dynamic-xp modules/mod-dynamic-xp -git clone --depth=1 --branch=master https://github.com/azerothcore/mod-eluna modules/mod-eluna +git clone --depth=1 --branch=master https://github.com/azerothcore/mod-ale modules/mod-ale git clone --depth=1 --branch=master https://github.com/azerothcore/mod-emblem-transfer modules/mod-emblem-transfer git clone --depth=1 --branch=master https://github.com/azerothcore/mod-fireworks-on-level modules/mod-fireworks-on-level git clone --depth=1 --branch=main https://github.com/azerothcore/mod-global-chat modules/mod-global-chat diff --git a/apps/compiler/includes/functions.sh b/apps/compiler/includes/functions.sh index c608cff24d..4428f9132f 100644 --- a/apps/compiler/includes/functions.sh +++ b/apps/compiler/includes/functions.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +# shellcheck source=../../../deps/acore/bash-lib/src/common/boolean.sh +source "$AC_BASH_LIB_PATH/common/boolean.sh" + # Set SUDO variable - one liner SUDO="" @@ -135,7 +138,8 @@ function comp_compile() { echo "Done" ;; linux*|darwin*) - local confDir=${CONFDIR:-"$AC_BINPATH_FULL/../etc"} + local confDir + confDir=${CONFDIR:-"$AC_BINPATH_FULL/../etc"} # create the folders before installing to # set the current user and permissions @@ -143,6 +147,9 @@ function comp_compile() { mkdir -p "$AC_BINPATH_FULL" echo "Creating $confDir..." mkdir -p "$confDir" + mkdir -p "$confDir/modules" + + confDir=$(realpath "$confDir") echo "Cmake install..." $SUDO cmake --install . --config $CTYPE @@ -156,14 +163,29 @@ function comp_compile() { 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 -- {} + + $SUDO setcap cap_sys_nice=eip "$AC_BINPATH_FULL/worldserver" + $SUDO setcap cap_sys_nice=eip "$AC_BINPATH_FULL/authserver" fi - [[ -f "$confDir/worldserver.conf.dist" ]] && \ - cp -v --no-clobber "$confDir/worldserver.conf.dist" "$confDir/worldserver.conf" - [[ -f "$confDir/authserver.conf.dist" ]] && \ - cp -v --no-clobber "$confDir/authserver.conf.dist" "$confDir/authserver.conf" - [[ -f "$confDir/dbimport.conf.dist" ]] && \ - cp -v --no-clobber "$confDir/dbimport.conf.dist" "$confDir/dbimport.conf" + + if ( isTrue "$AC_ENABLE_CONF_COPY_ON_INSTALL" ) then + echo "Copying default configuration files to $confDir ..." + [[ -f "$confDir/worldserver.conf.dist" && ! -f "$confDir/worldserver.conf" ]] && \ + cp -v "$confDir/worldserver.conf.dist" "$confDir/worldserver.conf" + [[ -f "$confDir/authserver.conf.dist" && ! -f "$confDir/authserver.conf" ]] && \ + cp -v "$confDir/authserver.conf.dist" "$confDir/authserver.conf" + [[ -f "$confDir/dbimport.conf.dist" && ! -f "$confDir/dbimport.conf" ]] && \ + cp -v "$confDir/dbimport.conf.dist" "$confDir/dbimport.conf" + + for f in "$confDir/modules/"*.dist + do + [[ -e $f ]] || break # handle the case of no *.dist files + if [[ ! -f "${f%.dist}" ]]; then + echo "Copying module config $(basename "${f%.dist}")" + cp -v "$f" "${f%.dist}"; + fi + done + fi echo "Done" ;; diff --git a/apps/installer/includes/config/config-main.sh b/apps/installer/includes/config/config-main.sh new file mode 100644 index 0000000000..f5f0c01f64 --- /dev/null +++ b/apps/installer/includes/config/config-main.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +CURRENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd ) + +# shellcheck source=./config.sh +source "$CURRENT_PATH/config.sh" + +acore_dash_config "$@" + diff --git a/apps/installer/includes/config/config.sh b/apps/installer/includes/config/config.sh new file mode 100644 index 0000000000..40192c4008 --- /dev/null +++ b/apps/installer/includes/config/config.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +CURRENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd ) + +# shellcheck source=../../../bash_shared/includes.sh +source "$CURRENT_PATH/../../../bash_shared/includes.sh" +# shellcheck source=../includes.sh +source "$CURRENT_PATH/../includes.sh" +# shellcheck source=../../../bash_shared/menu_system.sh +source "$AC_PATH_APPS/bash_shared/menu_system.sh" + +function acore_dash_configShowValue() { + if [ $# -ne 1 ]; then + echo "Usage: show <VAR_NAME>" + return 1 + fi + + local varName="$1" + local varValue="${!varName}" + if [ -z "$varValue" ]; then + echo "$varName is not set." + else + echo "$varName=$varValue" + fi +} + +function acore_dash_configLoad() { + acore_common_loadConfig + echo "Configuration loaded into the current shell session." +} + +# Configuration management menu definition +# Format: "key|short|description" +config_menu_items=( + "show|s|Show configuration variable value" + "load|l|Load configurations variables within the current shell session" + "help|h|Show detailed help" + "quit|q|Close this menu" +) + +# Menu command handler for configuration operations +function handle_config_command() { + local key="$1" + shift + + case "$key" in + "show") + acore_dash_configShowValue "$@" + ;; + "load") + acore_dash_configLoad + ;; + esac +} + +function acore_dash_config() { + menu_run_with_items "CONFIG MANAGER" handle_config_command -- "${config_menu_items[@]}" -- "$@" + return $? +} + diff --git a/apps/installer/includes/functions.sh b/apps/installer/includes/functions.sh index 9dbe2652c1..3c9b2edba2 100644 --- a/apps/installer/includes/functions.sh +++ b/apps/installer/includes/functions.sh @@ -155,7 +155,7 @@ function inst_simple_restarter { function inst_download_client_data { # change the following version when needed - local VERSION=v16 + local VERSION=v18.0 echo "#######################" echo "Client data downloader" @@ -183,3 +183,5 @@ function inst_download_client_data { && echo "Remove downloaded file" && rm "$zipPath" \ && echo "INSTALLED_VERSION=$VERSION" > "$dataVersionFile" } + + diff --git a/apps/installer/includes/includes.sh b/apps/installer/includes/includes.sh index c0d6bb8bd6..e4c1b9f2b5 100644 --- a/apps/installer/includes/includes.sh +++ b/apps/installer/includes/includes.sh @@ -2,6 +2,7 @@ CURRENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd ) +# shellcheck source=../../bash_shared/includes.sh source "$CURRENT_PATH/../../bash_shared/includes.sh" AC_PATH_INSTALLER="$AC_PATH_APPS/installer" @@ -9,14 +10,14 @@ AC_PATH_INSTALLER="$AC_PATH_APPS/installer" J_PATH="$AC_PATH_DEPS/acore/joiner" J_PATH_MODULES="$AC_PATH_MODULES" +# shellcheck source=../../../deps/acore/joiner/joiner.sh source "$J_PATH/joiner.sh" -if [ -f "$AC_PATH_INSTALLER/config.sh" ]; then - source "$AC_PATH_INSTALLER/config.sh" # should overwrite previous -fi - +# shellcheck source=../../compiler/includes/includes.sh source "$AC_PATH_APPS/compiler/includes/includes.sh" +# shellcheck source=../../../deps/semver_bash/semver.sh source "$AC_PATH_DEPS/semver_bash/semver.sh" +# shellcheck source=../includes/functions.sh source "$AC_PATH_INSTALLER/includes/functions.sh" diff --git a/apps/installer/includes/modules-manager/README.md b/apps/installer/includes/modules-manager/README.md index 93496a91a6..d6a160b6c7 100644 --- a/apps/installer/includes/modules-manager/README.md +++ b/apps/installer/includes/modules-manager/README.md @@ -63,7 +63,7 @@ repo[:dirname][@branch[:commit]] ./acore.sh module install https://github.com/azerothcore/mod-transmog.git@main # Install multiple modules -./acore.sh module install mod-transmog mod-eluna:custom-eluna +./acore.sh module install mod-transmog mod-ale:custom-eluna # Install all modules from list ./acore.sh module install --all @@ -92,7 +92,7 @@ repo[:dirname][@branch[:commit]] ./acore.sh module remove https://github.com/azerothcore/mod-transmog.git # Remove multiple modules -./acore.sh module remove mod-transmog mod-eluna +./acore.sh module remove mod-transmog mod-ale ``` ### Searching Modules @@ -232,7 +232,7 @@ repo_reference branch commit # Examples: azerothcore/mod-transmog master abc123def456 https://github.com/custom/mod-custom.git develop def456abc789 -mod-eluna:custom-eluna-dir main 789abc123def +mod-ale:custom-eluna-dir main 789abc123def ``` The list maintains: diff --git a/apps/installer/includes/modules-manager/modules.sh b/apps/installer/includes/modules-manager/modules.sh index 787d07677c..89c7ea50ac 100644 --- a/apps/installer/includes/modules-manager/modules.sh +++ b/apps/installer/includes/modules-manager/modules.sh @@ -59,7 +59,6 @@ else C_GREEN='' C_YELLOW='' C_BLUE='' - C_MAGENTA='' C_CYAN='' fi @@ -127,10 +126,13 @@ function inst_module_help() { echo " ./acore.sh module # Interactive menu" echo " ./acore.sh module search [terms...]" echo " ./acore.sh module install [--all | modules...]" - echo " ./acore.sh module update [--all | modules...]" + echo " ./acore.sh module update [--discard-changes] [--all | modules...]" echo " ./acore.sh module remove [modules...]" echo " ./acore.sh module list # List installed modules" echo "" + echo "Options:" + echo " --discard-changes Reset module repositories to a clean state before updating" + echo "" echo "Module Specification Syntax:" echo " name # Simple name (e.g., mod-transmog)" echo " owner/name # GitHub repository" @@ -171,42 +173,8 @@ function inst_module_list() { # Usage: ./acore.sh module <search|install|update|remove> [args...] # ./acore.sh module # Interactive menu function inst_module() { - # If no arguments provided, start interactive menu - if [[ $# -eq 0 ]]; then - menu_run_with_items "MODULE MANAGER" handle_module_command -- "${module_menu_items[@]}" -- - return $? - fi - - # Normalize arguments into an array - local tokens=() - read -r -a tokens <<< "$*" - local cmd="${tokens[0]}" - local args=("${tokens[@]:1}") - - case "$cmd" in - ""|"help"|"-h"|"--help") - inst_module_help - ;; - "search"|"s") - inst_module_search "${args[@]}" - ;; - "install"|"i") - inst_module_install "${args[@]}" - ;; - "update"|"u") - inst_module_update "${args[@]}" - ;; - "remove"|"r") - inst_module_remove "${args[@]}" - ;; - "list"|"l") - inst_module_list "${args[@]}" - ;; - *) - print_error "Unknown module command: $cmd. Use 'help' to see available commands." - return 1 - ;; - esac + menu_run_with_items "MODULE MANAGER" handle_module_command -- "${module_menu_items[@]}" -- "$@" + return $? } # ============================================================================= @@ -602,6 +570,37 @@ function inst_mod_is_installed() { return 1 } +# Discard local changes from a module repository to guarantee a clean update. +function inst_module_reset_repo() { + local repo_ref="$1" + local dirname="$2" + local repo_path="$J_PATH_MODULES/$dirname" + + if [ ! -d "$repo_path" ]; then + print_error "[$repo_ref] Cannot discard changes; path not found ($repo_path)." + return 1 + fi + + if [ ! -d "$repo_path/.git" ]; then + print_error "[$repo_ref] Cannot discard changes; $repo_path is not a git repository." + return 1 + fi + + print_warn "[$repo_ref] Discarding local changes (--discard-changes)." + + if ! git -C "$repo_path" reset --hard >/dev/null 2>&1; then + print_error "[$repo_ref] Failed to reset repository at $repo_path." + return 1 + fi + + if ! git -C "$repo_path" clean -fd >/dev/null 2>&1; then + print_error "[$repo_ref] Failed to remove untracked files from $repo_path." + return 1 + fi + + return 0 +} + # ============================================================================= # Conflict Detection and Validation # ============================================================================= @@ -649,7 +648,7 @@ function inst_getVersionBranch() { res="none" # since we've the pair version,branch alternated in not associative and one-dimensional # array, we've to simulate the association with length/2 trick - for idx in `seq 0 $((${#vers[*]}/2-1))`; do + for idx in $(seq 0 $((${#vers[*]}/2-1))); do semverParseInto "${vers[idx*2]}" MODULE_MAJOR MODULE_MINOR MODULE_PATCH MODULE_SPECIAL if [[ $MODULE_MAJOR -eq $ACV_MAJOR && $MODULE_MINOR -le $ACV_MINOR ]]; then res="${vers[idx*2+1]}" @@ -901,31 +900,48 @@ function inst_module_install { # Update one or more modules function inst_module_update { - # Handle help request - if [[ "$1" == "--help" || "$1" == "-h" ]]; then - inst_module_help - return 0 - fi - - # Support multiple modules and the --all flag; prompt if none specified. - local args=("$@") + local modules=() local use_all=false - if [ ${#args[@]} -gt 0 ] && { [ "${args[0]}" = "--all" ] || [ "${args[0]}" = "-a" ]; }; then - use_all=true + local discard_changes=false + local had_errors=0 + + while [[ $# -gt 0 ]]; do + case "$1" in + --help|-h) + inst_module_help + return 0 + ;; + --all|-a) + use_all=true + ;; + --discard-changes|--reset|-r) + discard_changes=true + ;; + --) + shift || true + while [[ $# -gt 0 ]]; do + modules+=("$1") + shift || true + done + break + ;; + *) + modules+=("$1") + ;; + esac shift || true - fi - - local _tmp=$PWD + done if $use_all; then - local line repo_ref branch commit newCommit owner modname url dirname + local repo_ref branch commit owner modname url dirname newCommit + local parsed_output while read -r repo_ref branch commit; do [ -z "$repo_ref" ] && continue - # Skip excluded modules during update --all if inst_mod_is_excluded "$repo_ref"; then print_warn "[$repo_ref] Excluded by MODULES_EXCLUDE_LIST (skipping)." continue fi + parsed_output=$(inst_parse_module_spec "$repo_ref") IFS=' ' read -r _ owner modname _ _ url dirname <<< "$parsed_output" @@ -935,16 +951,23 @@ function inst_module_update { continue fi + if $discard_changes; then + if ! inst_module_reset_repo "$repo_ref" "$dirname"; then + had_errors=1 + continue + fi + fi + if Joiner:upd_repo "$url" "$dirname" "$branch" ""; then newCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") inst_mod_list_upsert "$repo_ref" "$branch" "$newCommit" print_success "[$repo_ref] Updated to latest commit on '$branch'." else print_error "[$repo_ref] Cannot update" + had_errors=1 fi done < <(inst_mod_list_read) else - local modules=("$@") if [ ${#modules[@]} -eq 0 ]; then echo "Type the name(s) of the module(s) to update" read -p "Insert name(s): " _line @@ -952,6 +975,7 @@ function inst_module_update { fi local spec repo_ref override_branch override_commit owner modname url dirname v b branch def newCommit + local parsed_output for spec in "${modules[@]}"; do [ -z "$spec" ] && continue parsed_output=$(inst_parse_module_spec "$spec") @@ -959,11 +983,10 @@ function inst_module_update { dirname="${dirname:-$modname}" if [ -d "$J_PATH_MODULES/$dirname/" ]; then - # determine preferred branch if not provided + b="" if [ -n "$override_branch" ] && [ "$override_branch" != "-" ]; then b="$override_branch" else - # try reading acore-module.json for this repo if [[ "$url" =~ github.com ]]; then read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/${owner}/${modname}/master/acore-module.json") else @@ -981,21 +1004,35 @@ function inst_module_update { fi fi + if $discard_changes; then + if ! inst_module_reset_repo "$repo_ref" "$dirname"; then + had_errors=1 + continue + fi + fi + if Joiner:upd_repo "$url" "$dirname" "$b" ""; then newCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") inst_mod_list_upsert "$repo_ref" "$b" "$newCommit" print_success "[$repo_ref] Done, please re-run compiling and db assembly" else print_error "[$repo_ref] Cannot update" + had_errors=1 fi else print_error "[$repo_ref] Cannot update! Path doesn't exist ($J_PATH_MODULES/$dirname/)" + had_errors=1 fi done fi echo "" echo "" + + if [ "$had_errors" -ne 0 ]; then + return 1 + fi + return 0 } # Remove one or more modules diff --git a/apps/installer/includes/os_configs/debian.sh b/apps/installer/includes/os_configs/debian.sh index 5bfc93f8f8..1aecbe3d26 100644 --- a/apps/installer/includes/os_configs/debian.sh +++ b/apps/installer/includes/os_configs/debian.sh @@ -32,7 +32,7 @@ $SUDO apt-get install -y gdbserver gdb unzip curl \ 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" -DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.32-1_all.deb" +wget https://dev.mysql.com/get/mysql-apt-config_0.8.35-1_all.deb -P "$VAR_PATH" +DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.35-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 cd3944fa6d..c2c84fff35 100644 --- a/apps/installer/includes/os_configs/ubuntu.sh +++ b/apps/installer/includes/os_configs/ubuntu.sh @@ -40,8 +40,10 @@ apt-get -y install ccache clang cmake curl google-perftools libmysqlclient-dev m # 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" - DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.32-1_all.deb" + wget https://dev.mysql.com/get/mysql-apt-config_0.8.35-1_all.deb -P "$VAR_PATH" + # resolve expired key issue + sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A8D3785C + DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.35-1_all.deb" $SUDO apt-get update DEBIAN_FRONTEND="noninteractive" $SUDO apt-get install -y mysql-server fi diff --git a/apps/installer/includes/os_configs/windows.sh b/apps/installer/includes/os_configs/windows.sh index b99c2bfebf..cdba50c27f 100644 --- a/apps/installer/includes/os_configs/windows.sh +++ b/apps/installer/includes/os_configs/windows.sh @@ -24,7 +24,6 @@ fi choco install -y --skip-checksums "${INSTALL_ARGS[@]}" cmake.install -y --installargs 'ADD_CMAKE_TO_PATH=System' choco install -y --skip-checksums "${INSTALL_ARGS[@]}" visualstudio2022-workload-nativedesktop -choco install -y --skip-checksums "${INSTALL_ARGS[@]}" openssl --force --version=3.5.3 +choco install -y --skip-checksums "${INSTALL_ARGS[@]}" openssl --force --version=3.5.4 choco install -y --skip-checksums "${INSTALL_ARGS[@]}" boost-msvc-14.3 --force --version=1.87.0 -choco install -y --skip-checksums "${INSTALL_ARGS[@]}" mysql --force --version=8.4.4 - +choco install -y --skip-checksums "${INSTALL_ARGS[@]}" mysql --force --version=8.4.6 diff --git a/apps/installer/main.sh b/apps/installer/main.sh index fea9dc3acf..a64787269e 100644 --- a/apps/installer/main.sh +++ b/apps/installer/main.sh @@ -45,6 +45,7 @@ menu_items=( "docker|dr|Run docker tools" "version|v|Show AzerothCore version" "service-manager|sm|Run service manager to run authserver and worldserver in background" + "config|cf|Configuration manager" "quit|q|Exit from this menu" ) @@ -100,6 +101,9 @@ function handle_menu_command() { bash "$AC_PATH_APPS/startup-scripts/src/service-manager.sh" "$@" exit ;; + "config") + bash "$AC_PATH_APPS/installer/includes/config/config-main.sh" "$@" + ;; "quit") echo "Goodbye!" exit diff --git a/apps/installer/test/test_module_commands.bats b/apps/installer/test/test_module_commands.bats index 1223a80a6d..d829c1a32b 100755 --- a/apps/installer/test/test_module_commands.bats +++ b/apps/installer/test/test_module_commands.bats @@ -751,5 +751,5 @@ EOF run inst_module "unknown-command" [ "$status" -eq 1 ] - [[ "$output" =~ "Unknown module command" ]] + [[ "$output" =~ "Invalid option" ]] }
\ No newline at end of file diff --git a/apps/startup-scripts/README.md b/apps/startup-scripts/README.md index 0c7cb0940d..b99c38bd68 100644 --- a/apps/startup-scripts/README.md +++ b/apps/startup-scripts/README.md @@ -453,6 +453,40 @@ This is particularly useful for: - **Multiple Projects**: Separate service configurations per project - **Team Collaboration**: Share service setups across development teams +#### Service Configuration Portability + +The service manager automatically stores binary and configuration paths as relative paths when they are located under the `AC_SERVICE_CONFIG_DIR`, making service configurations portable across environments: + +```bash +# Set up a portable project structure +export AC_SERVICE_CONFIG_DIR="/opt/myproject/services" +mkdir -p "$AC_SERVICE_CONFIG_DIR"/{bin,etc} + +# Copy your binaries and configs +cp /path/to/compiled/authserver "$AC_SERVICE_CONFIG_DIR/bin/" +cp /path/to/authserver.conf "$AC_SERVICE_CONFIG_DIR/etc/" + +# Create service - paths under AC_SERVICE_CONFIG_DIR will be stored as relative +./service-manager.sh create auth authserver \ + --bin-path "$AC_SERVICE_CONFIG_DIR/bin" \ + --server-config "$AC_SERVICE_CONFIG_DIR/etc/authserver.conf" + +# Registry will contain relative paths like "bin/authserver" and "etc/authserver.conf" +# instead of absolute paths, making the entire directory portable +``` + +**Benefits:** +- **Environment Independence**: Move the entire services directory between machines +- **Container Friendly**: Perfect for Docker volumes and bind mounts +- **Backup/Restore**: Archive and restore complete service configurations +- **Development/Production Parity**: Same relative structure across environments + +**How it works:** +- Paths under `AC_SERVICE_CONFIG_DIR` are automatically stored as relative paths +- Paths outside `AC_SERVICE_CONFIG_DIR` are stored as absolute paths for safety +- When services are restored or started, relative paths are resolved from `AC_SERVICE_CONFIG_DIR` +- If `AC_SERVICE_CONFIG_DIR` is not set, all paths are stored as absolute paths (traditional behavior) + #### Migration from Legacy Format If you have existing services in the old format, use the migration script: diff --git a/apps/startup-scripts/src/run-engine b/apps/startup-scripts/src/run-engine index 761e51b3d5..72a3f304ca 100755 --- a/apps/startup-scripts/src/run-engine +++ b/apps/startup-scripts/src/run-engine @@ -219,6 +219,13 @@ function parse_arguments() { export PARSED_CONFIG_FILE="$config_file" export PARSED_SERVERCONFIG="$serverconfig" export PARSED_SESSION_MANAGER="$session_manager" + + echo "Parsed arguments:" + echo " Mode: $PARSED_MODE" + echo " Server Binary: $PARSED_SERVERBIN" + echo " Config File: $PARSED_CONFIG_FILE" + echo " Server Config: $PARSED_SERVERCONFIG" + echo " Session Manager: $PARSED_SESSION_MANAGER" } # Start service (single run or with simple-restarter) diff --git a/apps/startup-scripts/src/service-manager.sh b/apps/startup-scripts/src/service-manager.sh index ccc4e8e359..4f597fbebe 100755 --- a/apps/startup-scripts/src/service-manager.sh +++ b/apps/startup-scripts/src/service-manager.sh @@ -16,6 +16,19 @@ ROOT_DIR="$(cd "$CURRENT_PATH/../../.." && pwd)" # Configuration directory (can be overridden with AC_SERVICE_CONFIG_DIR) CONFIG_DIR="${AC_SERVICE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/azerothcore/services}" REGISTRY_FILE="$CONFIG_DIR/service_registry.json" +export AC_SERVICE_CONFIG_DIR="${AC_SERVICE_CONFIG_DIR:-$CONFIG_DIR}" + +# Default values for variables that might be loaded from config files +# This prevents "unbound variable" errors when sourcing configuration files +RUN_ENGINE_CONFIG_FILE="${RUN_ENGINE_CONFIG_FILE:-}" +SESSION_MANAGER="${SESSION_MANAGER:-}" +SESSION_NAME="${SESSION_NAME:-}" +BINPATH="${BINPATH:-}" +SERVERBIN="${SERVERBIN:-}" +CONFIG="${CONFIG:-}" +RESTART_POLICY="${RESTART_POLICY:-}" +GDB_ENABLED="${GDB_ENABLED:-}" +SERVER_CONFIG="${SERVER_CONFIG:-}" # Colors for output readonly YELLOW='\033[1;33m' @@ -32,6 +45,114 @@ if [ ! -f "$REGISTRY_FILE" ]; then echo "[]" > "$REGISTRY_FILE" fi +# Path conversion utilities for portability +# When AC_SERVICE_CONFIG_DIR (hence CONFIG_DIR) is set, always store paths +# relative to it. Resolve relative paths back against CONFIG_DIR. +function make_path_relative() { + local input="$1" + + # Pass through empty or non-absolute inputs + if [ -z "$input" ] || [[ ! "$input" = /* ]]; then + echo "$input" + return + fi + + # If AC_SERVICE_CONFIG_DIR is explicitly set, check if path is under it + if [ -n "${AC_SERVICE_CONFIG_DIR:-}" ]; then + local config_dir_abs + config_dir_abs="$(realpath -m "$AC_SERVICE_CONFIG_DIR" 2>/dev/null || echo "$AC_SERVICE_CONFIG_DIR")" + local rel_path="" + + if command -v realpath >/dev/null 2>&1; then + rel_path="$(realpath --relative-to="$config_dir_abs" "$input" 2>/dev/null || true)" + if [ -z "$rel_path" ]; then + rel_path="$(realpath -m --relative-to="$config_dir_abs" "$input" 2>/dev/null || true)" + fi + fi + + if [ -z "$rel_path" ] && command -v python3 >/dev/null 2>&1; then + rel_path="$(python3 -c 'import os,sys; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$input" "$config_dir_abs" 2>/dev/null || true)" + fi + + if [ -n "$rel_path" ]; then + echo "$rel_path" + return + fi + fi + + # If not under AC_SERVICE_CONFIG_DIR or AC_SERVICE_CONFIG_DIR not set, return absolute path unchanged + echo "$input" +} + +function make_path_absolute() { + local input="$1" + + # Already absolute + if [[ "$input" = /* ]]; then + echo "$input" + return + fi + + # Resolve relative paths against AC_SERVICE_CONFIG_DIR when explicitly set + if [ -n "${AC_SERVICE_CONFIG_DIR:-}" ] && [ -n "$input" ]; then + local config_dir_abs + config_dir_abs="$(realpath "$AC_SERVICE_CONFIG_DIR" 2>/dev/null || echo "$AC_SERVICE_CONFIG_DIR")" + # Join and normalize; do not require the target to exist + realpath -m "$config_dir_abs/$input" 2>/dev/null || echo "$config_dir_abs/$input" + return + fi + + # Fallback: try to normalize relative to current directory + realpath -m "$input" 2>/dev/null || echo "$input" +} + +# Tokenize a shell command string without executing it. Supports basic quoting +# and escaping rules so paths with spaces remain intact. +# Serialize a command definition (binary + args) to JSON while converting paths +# to be relative to CONFIG_DIR when possible. +function serialize_exec_definition() { + local command_path="$1" + shift + local -a args=("$@") + local rel_command="$command_path" + + if [[ -n "$command_path" ]]; then + rel_command="$(make_path_relative "$command_path")" + fi + + local -a rel_args=() + local arg + for arg in "${args[@]}"; do + if [[ "$arg" == /* ]]; then + rel_args+=("$(make_path_relative "$arg")") + else + rel_args+=("$arg") + fi + done + + local args_json + args_json=$(printf '%s\0' "${rel_args[@]}" | jq -R -s 'split("\u0000")[:-1]') + + jq -n --arg command "$rel_command" --argjson args "$args_json" '{command: $command, args: $args}' +} + +# Combine command + args into a shell-safe string suitable for pm2/systemd +# ExecStart lines. Each token is bash-quoted to preserve spaces. +function render_exec_command() { + local command_path="$1" + shift + local -a args=("$@") + local parts=() + local token + + parts+=("$(printf '%q' "$command_path")") + for token in "${args[@]}"; do + parts+=("$(printf '%q' "$token")") + done + + (IFS=' '; printf '%s' "${parts[*]}") +} + # Check dependencies check_dependencies() { command -v jq >/dev/null 2>&1 || { @@ -53,6 +174,18 @@ function add_service_to_registry() { local gdb_enabled="$9" local pm2_opts="${10}" local server_config="${11}" + local exec_definition="${12:-}" # JSON payload describing command + args + + # Convert paths to relative if possible for portability + local relative_bin_path="$(make_path_relative "$bin_path")" + + # Convert server_config to relative if possible for portability + local relative_server_config="" + if [ -n "$server_config" ] && [ "$server_config" != "null" ]; then + relative_server_config="$(make_path_relative "$server_config")" + else + relative_server_config="$server_config" + fi # Remove any existing entry with the same service name to avoid duplicates local tmp_file @@ -61,10 +194,14 @@ function add_service_to_registry() { # Add the new entry to the registry tmp_file=$(mktemp) + local exec_json_payload="null" + if [ -n "$exec_definition" ]; then + exec_json_payload="$exec_definition" + fi jq --arg name "$service_name" \ --arg provider "$provider" \ --arg type "$service_type" \ - --arg bin_path "$bin_path" \ + --arg bin_path "$relative_bin_path" \ --arg args "$args" \ --arg created "$(date -Iseconds)" \ --arg systemd_type "$systemd_type" \ @@ -72,8 +209,9 @@ function add_service_to_registry() { --arg session_manager "$session_manager" \ --arg gdb_enabled "$gdb_enabled" \ --arg pm2_opts "$pm2_opts" \ - --arg server_config "$server_config" \ - '. += [{"name": $name, "provider": $provider, "type": $type, "bin_path": $bin_path, "args": $args, "created": $created, "status": "active", "systemd_type": $systemd_type, "restart_policy": $restart_policy, "session_manager": $session_manager, "gdb_enabled": $gdb_enabled, "pm2_opts": $pm2_opts, "server_config": $server_config}]' \ + --arg server_config "$relative_server_config" \ + --argjson exec "$exec_json_payload" \ + '. += [{"name": $name, "provider": $provider, "type": $type, "bin_path": $bin_path, "exec": $exec, "args": $args, "created": $created, "status": "active", "systemd_type": $systemd_type, "restart_policy": $restart_policy, "session_manager": $session_manager, "gdb_enabled": $gdb_enabled, "pm2_opts": $pm2_opts, "server_config": $server_config}]' \ "$REGISTRY_FILE" > "$tmp_file" && mv "$tmp_file" "$REGISTRY_FILE" echo -e "${GREEN}Service '$service_name' added to registry${NC}" @@ -117,7 +255,7 @@ function restore_missing_services() { local name=$(echo "$service" | jq -r '.name') local provider=$(echo "$service" | jq -r '.provider') local service_type=$(echo "$service" | jq -r '.type') - local bin_path=$(echo "$service" | jq -r '.bin_path // "unknown"') + local bin_path_raw=$(echo "$service" | jq -r '.bin_path // "unknown"') local args=$(echo "$service" | jq -r '.args // ""') local status=$(echo "$service" | jq -r '.status // "active"') local systemd_type=$(echo "$service" | jq -r '.systemd_type // "--user"') @@ -125,11 +263,23 @@ function restore_missing_services() { local session_manager=$(echo "$service" | jq -r '.session_manager // "none"') local gdb_enabled=$(echo "$service" | jq -r '.gdb_enabled // "0"') local pm2_opts=$(echo "$service" | jq -r '.pm2_opts // ""') - local server_config=$(echo "$service" | jq -r '.server_config // ""') + local server_config_raw=$(echo "$service" | jq -r '.server_config // ""') + + # Convert paths back to absolute for operation + local bin_path="$(make_path_absolute "$bin_path_raw")" + local server_config="" + if [ -n "$server_config_raw" ] && [ "$server_config_raw" != "null" ] && [ "$server_config_raw" != "" ]; then + server_config="$(make_path_absolute "$server_config_raw")" + else + server_config="$server_config_raw" + fi local service_exists=false if [ "$provider" = "pm2" ]; then + echo "Check if PM2 is installed..." + check_pm2 || { echo -e "${RED}PM2 is not installed. Cannot check service status.${NC}"; exit 1; } + if pm2 describe "$name" >/dev/null 2>&1; then service_exists=true fi @@ -166,14 +316,61 @@ function restore_missing_services() { local name=$(echo "$service" | jq -r '.name') local provider=$(echo "$service" | jq -r '.provider') local service_type=$(echo "$service" | jq -r '.type') - local bin_path=$(echo "$service" | jq -r '.bin_path') - local args=$(echo "$service" | jq -r '.args') local systemd_type=$(echo "$service" | jq -r '.systemd_type // "--user"') local restart_policy=$(echo "$service" | jq -r '.restart_policy // "always"') local session_manager=$(echo "$service" | jq -r '.session_manager // "none"') local gdb_enabled=$(echo "$service" | jq -r '.gdb_enabled // "0"') local pm2_opts=$(echo "$service" | jq -r '.pm2_opts // ""') - local server_config=$(echo "$service" | jq -r '.server_config // ""') + local status=$(echo "$service" | jq -r '.status // "active"') + local service_resolved="$(get_service_info_resolved "$name")" + local bin_path="$(echo "$service_resolved" | jq -r '.bin_path // "unknown"')" + local server_config="$(echo "$service_resolved" | jq -r '.server_config // ""')" + if [ "$server_config" = "null" ]; then + server_config="" + fi + local exec_definition_raw="$(echo "$service" | jq -c '.exec // null')" + local exec_command_abs="$(echo "$service_resolved" | jq -r '.exec.command // ""')" + local -a exec_args_abs=() + if [ -n "$exec_definition_raw" ] && [ "$exec_definition_raw" != "null" ]; then + while IFS= read -r arg; do + exec_args_abs+=("$arg") + done < <(echo "$service_resolved" | jq -r '.exec.args[]?') + fi + local exec_command_string="" + if [ -n "$exec_command_abs" ] && [ "$exec_command_abs" != "null" ]; then + exec_command_string="$(render_exec_command "$exec_command_abs" "${exec_args_abs[@]}")" + fi + local server_binary_path_for_configs="$bin_path" + local run_engine_config_path="" + if [ ${#exec_args_abs[@]} -gt 0 ]; then + if [ -n "${exec_args_abs[1]:-}" ] && [ "${exec_args_abs[1]}" != "null" ]; then + server_binary_path_for_configs="${exec_args_abs[1]}" + fi + local arg_index + for arg_index in "${!exec_args_abs[@]}"; do + if [ "${exec_args_abs[$arg_index]}" = "--config" ]; then + local next_index=$((arg_index + 1)) + if [ $next_index -lt ${#exec_args_abs[@]} ]; then + run_engine_config_path="${exec_args_abs[$next_index]}" + fi + break + fi + done + fi + if [ -n "$server_binary_path_for_configs" ] && [ "$server_binary_path_for_configs" != "unknown" ] && [ "$server_binary_path_for_configs" != "null" ]; then + server_binary_path_for_configs="$(make_path_absolute "$server_binary_path_for_configs")" + fi + if [ -z "$run_engine_config_path" ] || [ "$run_engine_config_path" = "null" ]; then + run_engine_config_path="$CONFIG_DIR/$name-run-engine.conf" + else + run_engine_config_path="$(make_path_absolute "$run_engine_config_path")" + fi + if [ -d "$run_engine_config_path" ]; then + run_engine_config_path="$CONFIG_DIR/$name-run-engine.conf" + fi + if [[ "$run_engine_config_path" != /* ]]; then + run_engine_config_path="$(make_path_absolute "$run_engine_config_path")" + fi echo "" echo -e "${YELLOW}Service '$name' ($provider) is missing${NC}" @@ -182,13 +379,20 @@ function restore_missing_services() { if [ "$bin_path" = "unknown" ] || [ "$bin_path" = "null" ] || [ "$status" = "migrated" ]; then echo " Binary: <needs manual configuration>" - echo " Args: <needs manual configuration>" + echo " Exec: <needs manual configuration>" echo "" echo -e "${YELLOW}This service needs to be recreated manually:${NC}" echo " $0 create $service_type $name --provider $provider --bin-path /path/to/your/bin" else echo " Binary: $bin_path" - echo " Args: $args" + if [ -n "$exec_command_string" ]; then + echo " Exec: $exec_command_string" + else + echo " Exec: <unavailable>" + fi + if [ -n "$server_config" ]; then + echo " Server config: $server_config" + fi fi echo "" @@ -204,19 +408,30 @@ function restore_missing_services() { fi else echo -e "${BLUE}Recreating service '$name'...${NC}" + if ! ensure_service_configs_restored "$name" "$service_type" "$provider" "$server_binary_path_for_configs" "$server_config" "$restart_policy" "$session_manager" "$gdb_enabled" "$systemd_type" "$pm2_opts" "$run_engine_config_path" "false"; then + echo -e "${YELLOW}Warning: Unable to restore configuration files for '$name'${NC}" + fi if [ "$provider" = "pm2" ]; then - if [ "$args" != "null" ] && [ -n "$args" ]; then - pm2_create_service "$name" "$bin_path $args" "$restart_policy" $pm2_opts + if [ -z "$exec_command_string" ] || [ "$exec_definition_raw" = "null" ]; then + echo -e "${RED}Cannot recreate PM2 service automatically: missing exec definition${NC}" else - pm2_create_service "$name" "$bin_path" "$restart_policy" $pm2_opts + if [ -n "$pm2_opts" ]; then + pm2_create_service "$name" "$exec_command_string" "$restart_policy" "$bin_path" "$server_config" "$exec_definition_raw" $pm2_opts + else + pm2_create_service "$name" "$exec_command_string" "$restart_policy" "$bin_path" "$server_config" "$exec_definition_raw" + fi fi elif [ "$provider" = "systemd" ]; then echo -e "${BLUE}Attempting to recreate systemd service '$name' automatically...${NC}" - if systemd_create_service "$name" "$bin_path $args" "$restart_policy" "$systemd_type" "$session_manager" "$gdb_enabled" "$server_config"; then - echo -e "${GREEN}Systemd service '$name' recreated successfully${NC}" + if [ -z "$exec_command_string" ] || [ "$exec_definition_raw" = "null" ]; then + echo -e "${RED}Cannot recreate systemd service automatically: missing exec definition${NC}" else - echo -e "${RED}Failed to recreate systemd service '$name'. Please recreate manually.${NC}" - echo " $0 create $name $service_type --provider systemd --bin-path $bin_path" + if systemd_create_service "$name" "$exec_command_string" "$restart_policy" "$bin_path" "$server_config" "$exec_definition_raw" "$systemd_type"; then + echo -e "${GREEN}Systemd service '$name' recreated successfully${NC}" + else + echo -e "${RED}Failed to recreate systemd service '$name'. Please recreate manually.${NC}" + echo " $0 create $name $service_type --provider systemd --bin-path $bin_path" + fi fi fi fi @@ -275,7 +490,7 @@ function print_help() { echo " $base_name update <service-name> [options]" echo " $base_name delete <service-name>" echo " $base_name list [provider]" - echo " $base_name restore" + echo " $base_name restore [--sync-only]" echo " $base_name start|stop|restart|status <service-name>" echo " $base_name logs <service-name> [--follow]" echo " $base_name attach <service-name>" @@ -342,6 +557,9 @@ function print_help() { echo " # Restore missing services from registry" echo " $base_name restore" echo "" + echo " # Normalize registry paths to be relative to AC_SERVICE_CONFIG_DIR" + echo " $base_name registry-normalize" + echo "" echo "Notes:" echo " - Configuration editing modifies run-engine settings (GDB, session manager, etc.)" echo " - Use --server-config for the actual server configuration file" @@ -353,10 +571,13 @@ function print_help() { 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" - echo " - restore command checks registry and helps recreate missing services" + echo " - restore command first normalizes registry paths, syncs config files, and then recreates missing services" echo "" echo "Environment Variables:" echo " AC_SERVICE_CONFIG_DIR - Override default config directory for services registry" + echo " When set, binary and configuration paths under this directory" + echo " are stored as relative paths for improved portability." + echo " Use '$base_name registry-normalize' to rewrite existing entries." } @@ -442,19 +663,314 @@ function get_service_info() { jq --arg name "$service_name" '.[] | select(.name == $name)' "$REGISTRY_FILE" } +# Get service info with resolved paths (relative paths converted to absolute) +function get_service_info_resolved() { + local service_name="$1" + local service_info="$(get_service_info "$service_name")" + + if [ -z "$service_info" ] || [ "$service_info" = "null" ]; then + echo "" + return + fi + + # Extract paths and convert them + local bin_path_raw="$(echo "$service_info" | jq -r '.bin_path // ""')" + local server_config_raw="$(echo "$service_info" | jq -r '.server_config // ""')" + local exec_command_raw="$(echo "$service_info" | jq -r '.exec.command // ""')" + local exec_args_raw_json="$(echo "$service_info" | jq -c '.exec.args // []')" + + local bin_path_resolved="" + local server_config_resolved="" + local exec_command_resolved="" + local -a exec_args_resolved=() + + if [ -n "$bin_path_raw" ] && [ "$bin_path_raw" != "null" ] && [ "$bin_path_raw" != "" ]; then + bin_path_resolved="$(make_path_absolute "$bin_path_raw")" + else + bin_path_resolved="$bin_path_raw" + fi + + if [ -n "$exec_command_raw" ] && [ "$exec_command_raw" != "null" ]; then + exec_command_resolved="$(make_path_absolute "$exec_command_raw")" + else + exec_command_resolved="$exec_command_raw" + fi + + if [ -n "$exec_args_raw_json" ] && [ "$exec_args_raw_json" != "null" ]; then + local prev_arg="" + while IFS= read -r arg; do + if [[ -z "$arg" ]]; then + exec_args_resolved+=("$arg") + elif [ "$prev_arg" = "--config" ]; then + exec_args_resolved+=("$(make_path_absolute "$arg")") + elif [[ "$arg" == /* ]]; then + exec_args_resolved+=("$(make_path_absolute "$arg")") + elif [[ "$arg" == */* && "$arg" != -* ]]; then + exec_args_resolved+=("$(make_path_absolute "$arg")") + else + exec_args_resolved+=("$arg") + fi + prev_arg="$arg" + done < <(echo "$exec_args_raw_json" | jq -r '.[]?') + fi + + if [ -n "$server_config_raw" ] && [ "$server_config_raw" != "null" ] && [ "$server_config_raw" != "" ]; then + server_config_resolved="$(make_path_absolute "$server_config_raw")" + else + server_config_resolved="$server_config_raw" + fi + + local exec_args_resolved_json + exec_args_resolved_json=$(printf '%s\0' "${exec_args_resolved[@]}" | jq -R -s 'split("\u0000")[:-1]') + local exec_resolved_json + exec_resolved_json=$(jq -n --arg command "${exec_command_resolved:-}" --argjson args "$exec_args_resolved_json" '{command: $command, args: $args}') + + # Return the service info with resolved paths + echo "$service_info" | jq --arg bin_path "$bin_path_resolved" \ + --arg server_config "$server_config_resolved" \ + --argjson exec "$exec_resolved_json" \ + '.bin_path = $bin_path | .exec = $exec | .server_config = $server_config' +} + +function ensure_service_configs_restored() { + local service_name="$1" + local service_type="$2" + local provider="$3" + local server_binary_path="$4" + local server_config_path="$5" + local restart_policy="$6" + local session_manager="$7" + local gdb_enabled="$8" + local systemd_type="$9" + local pm2_opts="${10:-}" + local run_engine_config_path="${11:-}" + local force_rewrite="${12:-false}" + + if [ -z "$service_name" ]; then + return 1 + fi + + if [ -z "$server_binary_path" ] || [ "$server_binary_path" = "null" ] || [ "$server_binary_path" = "unknown" ]; then + return 0 + fi + + if [ "$server_config_path" = "null" ]; then + server_config_path="" + fi + + if [ -z "$restart_policy" ] || [ "$restart_policy" = "null" ]; then + restart_policy="always" + fi + + if [ -z "$session_manager" ] || [ "$session_manager" = "null" ]; then + session_manager="none" + fi + + if [ -z "$gdb_enabled" ] || [ "$gdb_enabled" = "null" ]; then + gdb_enabled="0" + fi + + if [ -z "$systemd_type" ] || [ "$systemd_type" = "null" ]; then + systemd_type="--user" + fi + + pm2_opts="${pm2_opts:-}" + pm2_opts="${pm2_opts//$'\n'/ }" + pm2_opts="${pm2_opts#"${pm2_opts%%[![:space:]]*}"}" + pm2_opts="${pm2_opts%"${pm2_opts##*[![:space:]]}"}" + + if [ -z "$run_engine_config_path" ] || [ "$run_engine_config_path" = "null" ]; then + run_engine_config_path="$CONFIG_DIR/$service_name-run-engine.conf" + fi + + local service_conf_path="$CONFIG_DIR/$service_name.conf" + local server_binary_dir + local server_binary_name + + server_binary_dir="$(dirname "$server_binary_path")" + server_binary_name="$(basename "$server_binary_path")" + + mkdir -p "$(dirname "$run_engine_config_path")" + + if [ "$force_rewrite" = "true" ] || [ ! -f "$run_engine_config_path" ]; then + echo -e "${BLUE}Restoring run-engine config: $run_engine_config_path${NC}" + cat > "$run_engine_config_path" << EOF +# run-engine configuration for service: $service_name +# Restored: $(date) + +# Enable/disable GDB execution +export GDB_ENABLED=$gdb_enabled + +# Session manager (none|auto|tmux|screen) +export SESSION_MANAGER="$session_manager" + +# Restart policy (on-failure|always) +export RESTART_POLICY="$restart_policy" + +# Service mode - indicates this is running under a service manager (systemd/pm2) +# When true, AC_DISABLE_INTERACTIVE will be set if no interactive session manager is used +export SERVICE_MODE="true" + +# Session name for tmux/screen (optional) +export SESSION_NAME="${service_name}" + +# Binary directory path +export BINPATH="$server_binary_dir" + +# Server binary name +export SERVERBIN="$server_binary_name" + +# Server configuration file path +export CONFIG="$server_config_path" + +# Show console output for easier debugging +export WITH_CONSOLE=1 +EOF + fi + + if [ "$force_rewrite" = "true" ] || [ ! -f "$service_conf_path" ]; then + echo -e "${BLUE}Restoring service metadata: $service_conf_path${NC}" + cat > "$service_conf_path" << EOF +# AzerothCore service configuration for $service_name +# Restored: $(date) +# Provider: $provider +# Service Type: $service_type + +# run-engine configuration file +RUN_ENGINE_CONFIG_FILE="$run_engine_config_path" + +# Restart policy +RESTART_POLICY="$restart_policy" + +# Provider-specific options +SYSTEMD_TYPE="$systemd_type" +PM2_OPTS="$pm2_opts" +EOF + fi + + return 0 +} + +function sync_service_configs_from_registry() { + echo -e "${BLUE}Syncing service configuration files from registry...${NC}" + + if [ ! -f "$REGISTRY_FILE" ] || [ ! -s "$REGISTRY_FILE" ]; then + echo -e "${YELLOW}No services registry found or empty${NC}" + return 0 + fi + + local services_count + services_count=$(jq length "$REGISTRY_FILE") + if [ "$services_count" -eq 0 ]; then + echo -e "${YELLOW}No services registered${NC}" + return 0 + fi + + local synced=0 + + for i in $(seq 0 $((services_count - 1))); do + local entry + entry=$(jq -c ".[$i]" "$REGISTRY_FILE") + [ -z "$entry" ] && continue + + local name + name=$(echo "$entry" | jq -r '.name // empty') + [ -z "$name" ] && continue + + local service_resolved + service_resolved="$(get_service_info_resolved "$name")" + [ -z "$service_resolved" ] && continue + + local service_type provider restart_policy session_manager gdb_enabled systemd_type pm2_opts + service_type=$(echo "$entry" | jq -r '.type // ""') + provider=$(echo "$entry" | jq -r '.provider // ""') + restart_policy=$(echo "$entry" | jq -r '.restart_policy // "always"') + session_manager=$(echo "$entry" | jq -r '.session_manager // "none"') + gdb_enabled=$(echo "$entry" | jq -r '.gdb_enabled // "0"') + systemd_type=$(echo "$entry" | jq -r '.systemd_type // "--user"') + pm2_opts=$(echo "$entry" | jq -r '.pm2_opts // ""') + + local server_binary_path + server_binary_path=$(echo "$service_resolved" | jq -r '.bin_path // ""') + if [ -z "$server_binary_path" ] || [ "$server_binary_path" = "null" ] || [ "$server_binary_path" = "unknown" ]; then + server_binary_path="" + fi + + local server_config_path + server_config_path=$(echo "$service_resolved" | jq -r '.server_config // ""') + if [ -n "$server_config_path" ] && [ "$server_config_path" != "null" ]; then + server_config_path="$(make_path_absolute "$server_config_path")" + else + server_config_path="" + fi + + local exec_definition + exec_definition=$(echo "$entry" | jq -c '.exec // null') + local -a exec_args=() + if [ -n "$exec_definition" ] && [ "$exec_definition" != "null" ]; then + while IFS= read -r arg; do + exec_args+=("$arg") + done < <(echo "$exec_definition" | jq -r '.args[]?') + fi + + if { [ -z "$server_binary_path" ] || [ "$server_binary_path" = "null" ]; } && [ ${#exec_args[@]} -ge 2 ]; then + server_binary_path="$(make_path_absolute "${exec_args[1]}")" + fi + + local run_engine_config_rel="" + for idx in "${!exec_args[@]}"; do + if [ "${exec_args[$idx]}" = "--config" ]; then + local next_idx=$((idx + 1)) + if [ $next_idx -lt ${#exec_args[@]} ]; then + run_engine_config_rel="${exec_args[$next_idx]}" + fi + break + fi + done + + if [ -z "$run_engine_config_rel" ] || [ "$run_engine_config_rel" = "null" ]; then + run_engine_config_rel="$name-run-engine.conf" + fi + + local run_engine_config_path + if [[ "$run_engine_config_rel" = /* ]]; then + run_engine_config_path="$run_engine_config_rel" + else + run_engine_config_path="$CONFIG_DIR/$run_engine_config_rel" + fi + run_engine_config_path="$(realpath -m "$run_engine_config_path" 2>/dev/null || echo "$run_engine_config_path")" + + if [ -n "$server_binary_path" ]; then + server_binary_path="$(make_path_absolute "$server_binary_path")" + fi + + ensure_service_configs_restored "$name" "$service_type" "$provider" "$server_binary_path" "$server_config_path" "$restart_policy" "$session_manager" "$gdb_enabled" "$systemd_type" "$pm2_opts" "$run_engine_config_path" "true" + synced=$((synced + 1)) + done + + if [ "$synced" -gt 0 ]; then + echo -e "${GREEN}Synchronized $synced service configuration file(s)${NC}" + else + echo -e "${YELLOW}No service configuration files required synchronization${NC}" + fi +} + # PM2 service management functions function pm2_create_service() { local service_name="$1" - local command="$2" + local command_string="$2" local restart_policy="$3" - shift 3 + local real_bin_path="$4" + local server_config_path="$5" + local exec_definition="$6" + shift 6 check_pm2 || return 1 # Parse additional PM2 options local max_memory="" local max_restarts="" - local additional_args="" + local -a extra_pm2_args=() while [[ $# -gt 0 ]]; do case "$1" in @@ -467,39 +983,79 @@ function pm2_create_service() { shift 2 ;; *) - additional_args+=" $1" + extra_pm2_args+=("$1") shift ;; esac done - + + if [ -z "$exec_definition" ] || [ "$exec_definition" = "null" ]; then + echo -e "${RED}Error: Missing exec definition for PM2 service '$service_name'${NC}" + return 1 + fi + + local exec_command_rel + exec_command_rel=$(echo "$exec_definition" | jq -r '.command // empty') + if [ -z "$exec_command_rel" ] || [ "$exec_command_rel" = "null" ]; then + echo -e "${RED}Error: Exec command not defined for service '$service_name'${NC}" + return 1 + fi + + local exec_command_abs + exec_command_abs="$(make_path_absolute "$exec_command_rel")" + local -a exec_args_abs=() + local prev_arg="" + while IFS= read -r arg; do + if [[ -z "$arg" ]]; then + exec_args_abs+=("$arg") + elif [ "$prev_arg" = "--config" ]; then + exec_args_abs+=("$(make_path_absolute "$arg")") + elif [[ "$arg" == /* ]]; then + exec_args_abs+=("$(make_path_absolute "$arg")") + elif [[ "$arg" == */* && "$arg" != -* ]]; then + exec_args_abs+=("$(make_path_absolute "$arg")") + else + exec_args_abs+=("$arg") + fi + prev_arg="$arg" + done < <(echo "$exec_definition" | jq -r '.args[]?') # Set stop exit codes based on restart policy - local stop_exit_codes="" + local -a pm2_args=(start "$exec_command_abs" --interpreter none --name "$service_name") if [ "$restart_policy" = "always" ]; then # PM2 will restart on any exit code (including 0) - stop_exit_codes="" + : else # PM2 will not restart on clean shutdown (exit code 0) - stop_exit_codes=" --stop-exit-codes 0" + pm2_args+=("--stop-exit-codes" "0") fi - # Build PM2 start command with AzerothCore environment variable - local pm2_cmd="AC_LAUNCHED_BY_PM2=1 pm2 start '$command$additional_args' --name '$service_name'$stop_exit_codes" # Add memory limit if specified if [ -n "$max_memory" ]; then - pm2_cmd+=" --max-memory-restart $max_memory" + pm2_args+=("--max-memory-restart" "$max_memory") fi # Add max restarts if specified if [ -n "$max_restarts" ]; then - pm2_cmd+=" --max-restarts $max_restarts" + pm2_args+=("--max-restarts" "$max_restarts") + fi + + if [ ${#extra_pm2_args[@]} -gt 0 ]; then + pm2_args+=("${extra_pm2_args[@]}") + fi + + if [ ${#exec_args_abs[@]} -gt 0 ]; then + pm2_args+=("--") + pm2_args+=("${exec_args_abs[@]}") fi # Execute command echo -e "${YELLOW}Creating PM2 service: $service_name${NC}" + if [ -n "$command_string" ]; then + echo " Exec: $command_string" + fi - if eval "$pm2_cmd"; then + if AC_LAUNCHED_BY_PM2=1 pm2 "${pm2_args[@]}"; then echo -e "${GREEN}PM2 service '$service_name' created successfully${NC}" pm2 save @@ -507,9 +1063,13 @@ function pm2_create_service() { echo -e "${BLUE}Configuring PM2 startup for persistence...${NC}" pm2 startup --auto >/dev/null 2>&1 || true - # Add to registry (extract command and args from the full command) - local clean_command="$command$additional_args" - add_service_to_registry "$service_name" "pm2" "executable" "$command" "$additional_args" "" "$restart_policy" "none" "0" "$max_memory $max_restarts" "" + # Add to registry (use real binary path instead of command for portability) + local extra_args_serialized="" + if [ ${#extra_pm2_args[@]} -gt 0 ]; then + extra_args_serialized="$(printf '%s ' "${extra_pm2_args[@]}")" + extra_args_serialized="${extra_args_serialized% }" + fi + add_service_to_registry "$service_name" "pm2" "executable" "$real_bin_path" "$extra_args_serialized" "" "$restart_policy" "none" "0" "$max_memory $max_restarts" "$server_config_path" "$exec_definition" return 0 else @@ -552,12 +1112,12 @@ function pm2_remove_service() { pm2 save echo -e "${GREEN}PM2 service '$service_name' stopped and removed${NC}" - # Remove from registry - remove_service_from_registry "$service_name" + # Note: Registry removal is handled by the caller (delete_service) + return 0 else - echo -e "${YELLOW}PM2 service '$service_name' not found or already removed${NC}" - # Still try to remove from registry in case it's orphaned - remove_service_from_registry "$service_name" + echo -e "${YELLOW}PM2 service '$service_name' not found${NC}" + # Service not found in PM2 - let caller decide about registry + return 0 fi return 0 @@ -602,11 +1162,14 @@ function systemd_create_service() { local service_name="$1" local command="$2" local restart_policy="$3" + local real_bin_path="$4" + local server_config_path="$5" + local exec_definition="$6" local systemd_type="--user" local bin_path="" local gdb_enabled="0" local server_config="" - shift 3 + shift 6 check_systemd || return 1 @@ -743,7 +1306,7 @@ EOF echo -e "${GREEN}Systemd service '$service_name' created successfully with session manager '$session_manager'${NC}" # Add to registry - add_service_to_registry "$service_name" "systemd" "service" "$command" "" "$systemd_type" "$restart_policy" "$session_manager" "$gdb_enabled" "" "$server_config" + add_service_to_registry "$service_name" "systemd" "service" "$real_bin_path" "" "$systemd_type" "$restart_policy" "$session_manager" "$gdb_enabled" "" "$server_config_path" "$exec_definition" return 0 } @@ -805,9 +1368,7 @@ function systemd_remove_service() { echo -e "${YELLOW}Note: Service may still be running but configuration was removed${NC}" fi - # Remove from registry - remove_service_from_registry "$service_name" - + # Note: Registry removal is handled by the caller (delete_service) return 0 else echo -e "${RED}Failed to remove systemd service file '$service_file'${NC}" @@ -1051,24 +1612,29 @@ 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" + # Build run-engine command definition + local run_engine_path="$SCRIPT_DIR/run-engine" + local -a run_engine_args=("start" "$server_binary_path" "--config" "$run_engine_config") + local run_engine_cmd + run_engine_cmd="$(render_exec_command "$run_engine_path" "${run_engine_args[@]}")" + local exec_definition + exec_definition="$(serialize_exec_definition "$run_engine_path" "${run_engine_args[@]}")" # 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" "$restart_policy" $pm2_opts; then + if pm2_create_service "$service_name" "$run_engine_cmd" "$restart_policy" "$server_binary_path" "$server_config" "$exec_definition" $pm2_opts; then service_creation_success=true fi else - if pm2_create_service "$service_name" "$run_engine_cmd" "$restart_policy"; then + if pm2_create_service "$service_name" "$run_engine_cmd" "$restart_policy" "$server_binary_path" "$server_config" "$exec_definition"; then service_creation_success=true fi fi elif [ "$provider" = "systemd" ]; then - if systemd_create_service "$service_name" "$run_engine_cmd" "$restart_policy" "$systemd_type"; then + if systemd_create_service "$service_name" "$run_engine_cmd" "$restart_policy" "$server_binary_path" "$server_config" "$exec_definition" "$systemd_type"; then service_creation_success=true fi fi @@ -1124,10 +1690,10 @@ function update_service() { source "$config_file" # Load current run-engine configuration - if [ -f "$RUN_ENGINE_CONFIG_FILE" ]; then + if [ -n "${RUN_ENGINE_CONFIG_FILE:-}" ] && [ -f "$RUN_ENGINE_CONFIG_FILE" ]; then source "$RUN_ENGINE_CONFIG_FILE" else - echo -e "${YELLOW}Warning: Run-engine configuration file not found: $RUN_ENGINE_CONFIG_FILE${NC}" + echo -e "${YELLOW}Warning: Run-engine configuration file not found: ${RUN_ENGINE_CONFIG_FILE:-<unset>}${NC}" fi # Parse options to update @@ -1284,8 +1850,11 @@ function delete_service() { fi if [ "$removal_success" = "true" ]; then + # Remove from registry only after successful service removal + remove_service_from_registry "$service_name" + # Remove run-engine configuration file - if [ -n "$RUN_ENGINE_CONFIG_FILE" ] && [ -f "$RUN_ENGINE_CONFIG_FILE" ]; then + 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 @@ -1296,6 +1865,7 @@ function delete_service() { echo -e "${GREEN}Service '$service_name' deleted successfully${NC}" else echo -e "${RED}Failed to remove service '$service_name' from $provider${NC}" + echo -e "${YELLOW}Registry entry preserved. Service may need manual cleanup.${NC}" return 1 fi } @@ -1416,7 +1986,11 @@ function edit_config() { # 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}" + echo -e "${BLUE}File: ${RUN_ENGINE_CONFIG_FILE:-<unset>}${NC}" + if [ -z "${RUN_ENGINE_CONFIG_FILE:-}" ] || [ ! -f "$RUN_ENGINE_CONFIG_FILE" ]; then + echo -e "${RED}Error: Run-engine configuration file not found: ${RUN_ENGINE_CONFIG_FILE:-<unset>}${NC}" + return 1 + fi ${EDITOR:-nano} "$RUN_ENGINE_CONFIG_FILE" echo -e "${GREEN}Configuration updated. Run '$0 restart $service_name' to apply changes.${NC}" @@ -1445,16 +2019,12 @@ function attach_to_service() { 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}" + if [ -z "${RUN_ENGINE_CONFIG_FILE:-}" ] || [ ! -f "$RUN_ENGINE_CONFIG_FILE" ]; then + echo -e "${RED}Error: Run-engine configuration file not found: ${RUN_ENGINE_CONFIG_FILE:-<unset>}${NC}" return 1 fi - 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 - + # Now safe to source source "$RUN_ENGINE_CONFIG_FILE" # Auto-detect session manager and attach accordingly @@ -1721,6 +2291,9 @@ function wait_service_uptime() { sleep 1 waited=$((waited + 1)) done + # show service logs for debugging + echo -e "${YELLOW}Service logs for '$service_name':${NC}" + service_logs "$service_name" true echo -e "${RED}Timeout: $service_name did not reach ${min_seconds}s uptime within ${timeout}s${NC}" >&2 return 1 } @@ -1770,8 +2343,8 @@ function attach_interactive_shell() { source "$config_file" # Check if RUN_ENGINE_CONFIG_FILE exists before sourcing - if [ ! -f "$RUN_ENGINE_CONFIG_FILE" ]; then - echo -e "${RED}Error: Run-engine configuration file not found: $RUN_ENGINE_CONFIG_FILE${NC}" + if [ -z "${RUN_ENGINE_CONFIG_FILE:-}" ] || [ ! -f "$RUN_ENGINE_CONFIG_FILE" ]; then + echo -e "${RED}Error: Run-engine configuration file not found: ${RUN_ENGINE_CONFIG_FILE:-<unset>}${NC}" return 1 fi @@ -1796,6 +2369,96 @@ function attach_interactive_shell() { return 1 } +# Normalize existing registry entries to use relative paths for fields that +# represent filesystem paths (bin_path, server_config) and refresh exec command +# definitions to the new schema. +function normalize_registry_paths() { + if [ ! -f "$REGISTRY_FILE" ]; then + echo -e "${YELLOW}No registry file found at: $REGISTRY_FILE${NC}" + return 0 + fi + + # Validate JSON + if ! jq empty "$REGISTRY_FILE" >/dev/null 2>&1; then + echo -e "${RED}Error: Invalid JSON in $REGISTRY_FILE${NC}" + return 1 + fi + + local tmp_file + tmp_file=$(mktemp) + echo "[]" > "$tmp_file" + + local count=0 + while IFS= read -r entry; do + [ -z "$entry" ] && continue + + # Extract raw values (empty if null or missing) + local bin_path_raw server_config_raw + bin_path_raw=$(echo "$entry" | jq -r '.bin_path // empty') + server_config_raw=$(echo "$entry" | jq -r '.server_config // empty') + + # Compute normalized values + local bin_path_new server_config_new + if [ -n "$bin_path_raw" ]; then + bin_path_new="$(make_path_relative "$bin_path_raw")" + else + bin_path_new="" + fi + if [ -n "$server_config_raw" ]; then + server_config_new="$(make_path_relative "$server_config_raw")" + else + server_config_new="" + fi + + local exec_command_raw + exec_command_raw=$(echo "$entry" | jq -r '.exec.command // empty') + local exec_args_array=() + local exec_json_new="null" + if [ -n "$exec_command_raw" ]; then + while IFS= read -r arg; do + exec_args_array+=("$arg") + done < <(echo "$entry" | jq -r '.exec.args[]?') + exec_json_new="$(serialize_exec_definition "$exec_command_raw" "${exec_args_array[@]}")" + fi + + # Update entry (only override when non-empty; preserve nulls) + local updated + if [ "$exec_json_new" != "null" ]; then + updated=$(echo "$entry" | jq \ + --arg bin "$bin_path_new" \ + --arg srv "$server_config_new" \ + --argjson exec "$exec_json_new" \ + ' + (.bin_path) |= (if $bin != "" then $bin else . end) | + (.server_config) |= (if $srv != "" then $srv else . end) | + (.exec) = $exec | + del(.real_bin_path) + ') + else + updated=$(echo "$entry" | jq \ + --arg bin "$bin_path_new" \ + --arg srv "$server_config_new" \ + ' + (.bin_path) |= (if $bin != "" then $bin else . end) | + (.server_config) |= (if $srv != "" then $srv else . end) | + del(.real_bin_path) + ') + fi + + # Append to new array + if ! jq --argjson e "$updated" '. += [$e]' "$tmp_file" > "$tmp_file.new"; then + rm -f "$tmp_file" "$tmp_file.new" + echo -e "${RED}Error: Failed updating registry${NC}" + return 1 + fi + mv "$tmp_file.new" "$tmp_file" + count=$((count+1)) + done < <(jq -c '.[]?' "$REGISTRY_FILE") + + mv "$tmp_file" "$REGISTRY_FILE" + echo -e "${GREEN}Normalized $count registry entrie(s) to relative paths${NC}" +} + function attach_tmux_session() { local service_name="$1" local provider="$2" @@ -1852,10 +2515,12 @@ function attach_screen_session() { # Main execution -check_dependencies +# Only run the main logic if this script is executed directly (not sourced) +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + check_dependencies -# Main command processing -case "${1:-help}" in + # Main command processing + case "${1:-help}" in create) if [ $# -lt 3 ]; then echo -e "${RED}Error: Not enough arguments for create command${NC}" @@ -1884,7 +2549,26 @@ case "${1:-help}" in list_services "${2:-}" ;; restore) - restore_missing_services + sync_only=false + if [ $# -gt 1 ]; then + for opt in "${@:2}"; do + case "$opt" in + --sync-only) + sync_only=true + ;; + *) + echo -e "${RED}Error: Unknown option for restore command: $opt${NC}" + print_help + exit 1 + ;; + esac + done + fi + normalize_registry_paths + sync_service_configs_from_registry + if [ "$sync_only" = "false" ]; then + restore_missing_services + fi ;; start|stop|restart|status) if [ $# -lt 2 ]; then @@ -1900,12 +2584,15 @@ case "${1:-help}" in print_help exit 1 fi - if [ "$3" = "--follow" ]; then + if [ "${3:-}" = "--follow" ]; then service_logs "$2" "true" else service_logs "$2" "false" fi ;; + registry-normalize) + normalize_registry_paths + ;; edit-config) if [ $# -lt 2 ]; then echo -e "${RED}Error: Service name required for edit-config command${NC}" @@ -1977,3 +2664,5 @@ case "${1:-help}" in exit 1 ;; esac + +fi # End of main execution block diff --git a/apps/startup-scripts/src/simple-restarter b/apps/startup-scripts/src/simple-restarter index a158b38bef..1865eaa87d 100755 --- a/apps/startup-scripts/src/simple-restarter +++ b/apps/startup-scripts/src/simple-restarter @@ -50,6 +50,8 @@ fi # Main restart loop while true; do STARTING_TIME=$(date +%s) + + echo "AC_CONFIG_POLICY: $AC_CONFIG_POLICY" # Use starter script to launch the binary with all parameters "$STARTER_SCRIPT" "$BINPATH" "$BINFILE" "$GDB_FILE" "$CONFIG" "$SYSLOG" "$SYSERR" "$GDB_ENABLED" "$CRASHES_PATH" diff --git a/apps/startup-scripts/test/test_startup_scripts.bats b/apps/startup-scripts/test/test_startup_scripts.bats index 119a8c80cc..c7a90c7f61 100644..100755 --- a/apps/startup-scripts/test/test_startup_scripts.bats +++ b/apps/startup-scripts/test/test_startup_scripts.bats @@ -160,7 +160,19 @@ teardown() { # Create registry with pm2 provider service cat > "$AC_SERVICE_CONFIG_DIR/service_registry.json" << 'EOF' [ - {"name":"test-world","provider":"pm2","type":"service","bin_path":"/bin/worldserver","args":"","systemd_type":"--user","restart_policy":"always"} + { + "name":"test-world", + "provider":"pm2", + "type":"service", + "bin_path":"/bin/worldserver", + "args":"", + "systemd_type":"--user", + "restart_policy":"always", + "exec":{ + "command":"/bin/true", + "args":[] + } + } ] EOF # Create minimal service config and run-engine config files required by 'send' @@ -215,7 +227,19 @@ EOF # Create registry and config as in previous test cat > "$AC_SERVICE_CONFIG_DIR/service_registry.json" << 'EOF' [ - {"name":"test-world","provider":"pm2","type":"service","bin_path":"/bin/worldserver","args":"","systemd_type":"--user","restart_policy":"always"} + { + "name":"test-world", + "provider":"pm2", + "type":"service", + "bin_path":"/bin/worldserver", + "args":"", + "systemd_type":"--user", + "restart_policy":"always", + "exec":{ + "command":"/bin/true", + "args":[] + } + } ] EOF echo "RUN_ENGINE_CONFIG_FILE=\"$AC_SERVICE_CONFIG_DIR/test-world-run-engine.conf\"" > "$AC_SERVICE_CONFIG_DIR/test-world.conf" @@ -258,6 +282,31 @@ EOF [ "$status" -eq 0 ] } +@test "service-manager: restore helper recreates missing configs" { + command -v jq >/dev/null 2>&1 || skip "jq not installed" + export AC_SERVICE_CONFIG_DIR="$TEST_DIR/services" + mkdir -p "$AC_SERVICE_CONFIG_DIR" + source "$SCRIPT_DIR/service-manager.sh" + + local service_name="restore-test" + local run_engine_config="$AC_SERVICE_CONFIG_DIR/$service_name-run-engine.conf" + local service_conf="$AC_SERVICE_CONFIG_DIR/$service_name.conf" + rm -f "$run_engine_config" "$service_conf" + + mkdir -p "$TEST_DIR/bin" "$TEST_DIR/etc" + touch "$TEST_DIR/bin/worldserver" + touch "$TEST_DIR/etc/worldserver.conf" + + ensure_service_configs_restored "$service_name" "world" "systemd" "$TEST_DIR/bin/worldserver" "$TEST_DIR/etc/worldserver.conf" "always" "none" "0" "--user" "" "$run_engine_config" + + [ -f "$run_engine_config" ] + [ -f "$service_conf" ] + grep -Fq 'export SESSION_MANAGER="none"' "$run_engine_config" + grep -Fq 'export BINPATH="'$TEST_DIR'/bin"' "$run_engine_config" + grep -Fq "RUN_ENGINE_CONFIG_FILE=\"$run_engine_config\"" "$service_conf" + grep -Fq 'RESTART_POLICY="always"' "$service_conf" +} + @test "service-manager: wait-uptime times out for unknown service" { command -v jq >/dev/null 2>&1 || skip "jq not installed" export AC_SERVICE_CONFIG_DIR="$TEST_DIR/services" @@ -279,6 +328,136 @@ EOF [[ "$output" =~ "Configuration file not found" ]] } +# ===== PATH PORTABILITY TESTS ===== + +@test "service-manager: path conversion functions work correctly" { + # Source the service-manager script to access helper functions + source "$SCRIPT_DIR/service-manager.sh" + + # Test make_path_relative without AC_SERVICE_CONFIG_DIR + unset AC_SERVICE_CONFIG_DIR + result=$(make_path_relative "/absolute/path/test") + [[ "$result" == "/absolute/path/test" ]] + + # Test make_path_relative with AC_SERVICE_CONFIG_DIR + export AC_SERVICE_CONFIG_DIR="/tmp/test-config" + mkdir -p "$AC_SERVICE_CONFIG_DIR/subdir" + + result=$(make_path_relative "$AC_SERVICE_CONFIG_DIR/subdir/binary") + [[ "$result" == "subdir/binary" ]] + + result=$(make_path_relative "/opt/bin/authserver") + [[ "$result" == "../../opt/bin/authserver" ]] + + # Test make_path_absolute + result=$(make_path_absolute "subdir/binary") + [[ "$result" == "$AC_SERVICE_CONFIG_DIR/subdir/binary" ]] + + result=$(make_path_absolute "../../opt/bin/authserver") + [[ "$result" == "/opt/bin/authserver" ]] + + # Test absolute path stays absolute + result=$(make_path_absolute "/absolute/path") + [[ "$result" == "/absolute/path" ]] + + # Cleanup + rm -rf "$AC_SERVICE_CONFIG_DIR" + unset AC_SERVICE_CONFIG_DIR +} + +@test "service-manager: registry stores relative paths when possible" { + # Set up test environment + export AC_SERVICE_CONFIG_DIR="$TEST_DIR/service-config" + mkdir -p "$AC_SERVICE_CONFIG_DIR" + + # Create a temporary service registry in our test directory + local test_registry="$AC_SERVICE_CONFIG_DIR/test_registry.json" + echo "[]" > "$test_registry" + + # Source the service-manager and override REGISTRY_FILE + source "$SCRIPT_DIR/service-manager.sh" + REGISTRY_FILE="$test_registry" + + # Create test binary directory under config dir + mkdir -p "$AC_SERVICE_CONFIG_DIR/bin" + + # Test that paths under AC_SERVICE_CONFIG_DIR are stored as relative + add_service_to_registry "test-service" "pm2" "auth" "$AC_SERVICE_CONFIG_DIR/bin/authserver" "--config test.conf" "" "always" "none" "0" "" "$AC_SERVICE_CONFIG_DIR/etc/test.conf" + + # Check that paths were stored as relative + local stored_bin_path=$(jq -r '.[0].bin_path' "$test_registry") + local stored_config_path=$(jq -r '.[0].server_config' "$test_registry") + + [[ "$stored_bin_path" == "bin/authserver" ]] + [[ "$stored_config_path" == "etc/test.conf" ]] + + # Test that absolute paths outside config dir are stored as absolute + add_service_to_registry "test-service2" "pm2" "auth" "/opt/azerothcore/bin/authserver" "--config test.conf" "" "always" "none" "0" "" "/opt/azerothcore/etc/test.conf" + + local stored_bin_path2=$(jq -r '.[1].bin_path' "$test_registry") + local stored_config_path2=$(jq -r '.[1].server_config' "$test_registry") + + local expected_bin_rel=$(make_path_relative "/opt/azerothcore/bin/authserver") + local expected_cfg_rel=$(make_path_relative "/opt/azerothcore/etc/test.conf") + + [[ "$stored_bin_path2" == "$expected_bin_rel" ]] + [[ "$stored_config_path2" == "$expected_cfg_rel" ]] + + # Cleanup + rm -rf "$AC_SERVICE_CONFIG_DIR" + unset AC_SERVICE_CONFIG_DIR +} + +@test "service-manager: restore --sync-only recreates config files" { + command -v jq >/dev/null 2>&1 || skip "jq not installed" + export AC_SERVICE_CONFIG_DIR="$TEST_DIR/services" + mkdir -p "$AC_SERVICE_CONFIG_DIR" + + cat > "$AC_SERVICE_CONFIG_DIR/service_registry.json" <<'EOF' +[ + { + "name": "sync-test", + "provider": "pm2", + "type": "auth", + "bin_path": "bin/authserver", + "exec": { + "command": "../src/run-engine", + "args": [ + "start", + "bin/authserver", + "--config", + "sync-test-run-engine.conf" + ] + }, + "args": "", + "created": "2025-10-12T20:00:54+02:00", + "status": "active", + "systemd_type": "--user", + "restart_policy": "always", + "session_manager": "none", + "gdb_enabled": "0", + "pm2_opts": " ", + "server_config": "etc/authserver.conf" + } +] +EOF + + rm -f "$AC_SERVICE_CONFIG_DIR/sync-test.conf" "$AC_SERVICE_CONFIG_DIR/sync-test-run-engine.conf" + + mkdir -p "$AC_SERVICE_CONFIG_DIR/bin" "$AC_SERVICE_CONFIG_DIR/etc" + touch "$AC_SERVICE_CONFIG_DIR/bin/authserver" + touch "$AC_SERVICE_CONFIG_DIR/etc/authserver.conf" + + run "$SCRIPT_DIR/service-manager.sh" restore --sync-only + debug_on_failure + [ "$status" -eq 0 ] + + [ -f "$AC_SERVICE_CONFIG_DIR/sync-test.conf" ] + [ -f "$AC_SERVICE_CONFIG_DIR/sync-test-run-engine.conf" ] + grep -Fq "RUN_ENGINE_CONFIG_FILE=\"$AC_SERVICE_CONFIG_DIR/sync-test-run-engine.conf\"" "$AC_SERVICE_CONFIG_DIR/sync-test.conf" + grep -Fq "export BINPATH=\"$AC_SERVICE_CONFIG_DIR/bin\"" "$AC_SERVICE_CONFIG_DIR/sync-test-run-engine.conf" +} + @test "examples: restarter-auth should show configuration error" { run "$SCRIPT_DIR/examples/restarter-auth.sh" [[ "$output" =~ "Configuration file not found" ]] |
