From 9f732c8cea96a26f1eef699e1b2d00c69e73afc9 Mon Sep 17 00:00:00 2001 From: Yehonal Date: Sun, 12 Oct 2025 22:28:22 +0200 Subject: [PATCH] feat(Bash/ServiceManager): enhance path portability (#22982) --- .vscode/settings.json | 4 +- apps/startup-scripts/README.md | 34 + apps/startup-scripts/src/run-engine | 7 + apps/startup-scripts/src/service-manager.sh | 809 ++++++++++++++++-- .../test/test_startup_scripts.bats | 183 +++- 5 files changed, 966 insertions(+), 71 deletions(-) mode change 100644 => 100755 apps/startup-scripts/test/test_startup_scripts.bats diff --git a/.vscode/settings.json b/.vscode/settings.json index dcc292e7a3..077d1618fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -122,5 +122,7 @@ "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" + "C_Cpp.default.compilerPath": "/usr/bin/clang", + "cmake.sourceDirectory": ["${workspaceFolder}"], + "cmake.buildDirectory": "${workspaceFolder}/var/build", } 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..a71212ec22 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,7 +263,16 @@ 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 @@ -166,14 +313,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 +376,20 @@ function restore_missing_services() { if [ "$bin_path" = "unknown" ] || [ "$bin_path" = "null" ] || [ "$status" = "migrated" ]; then echo " Binary: " - echo " Args: " + echo " Exec: " 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: " + fi + if [ -n "$server_config" ]; then + echo " Server config: $server_config" + fi fi echo "" @@ -204,19 +405,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 +487,7 @@ function print_help() { echo " $base_name update [options]" echo " $base_name delete " echo " $base_name list [provider]" - echo " $base_name restore" + echo " $base_name restore [--sync-only]" echo " $base_name start|stop|restart|status " echo " $base_name logs [--follow]" echo " $base_name attach " @@ -342,6 +554,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 +568,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 +660,308 @@ 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 + while IFS= read -r arg; do + if [[ -z "$arg" ]]; then + exec_args_resolved+=("$arg") + elif [[ "$arg" == /* ]]; then + exec_args_resolved+=("$(make_path_absolute "$arg")") + else + exec_args_resolved+=("$arg") + fi + 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 +974,75 @@ 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=() + while IFS= read -r arg; do + if [[ -z "$arg" ]]; then + exec_args_abs+=("$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 + 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 +1050,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 +1099,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 +1149,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 +1293,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 +1355,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 +1599,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 +1677,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:-}${NC}" fi # Parse options to update @@ -1284,8 +1837,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 +1852,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 +1973,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:-}${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:-}${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 +2006,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:-}${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 @@ -1770,8 +2327,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:-}${NC}" return 1 fi @@ -1796,6 +2353,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 +2499,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 +2533,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 +2568,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 +2648,5 @@ case "${1:-help}" in exit 1 ;; esac + +fi # End of main execution block diff --git a/apps/startup-scripts/test/test_startup_scripts.bats b/apps/startup-scripts/test/test_startup_scripts.bats old mode 100644 new mode 100755 index 119a8c80cc..c7a90c7f61 --- 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" ]]