diff options
author | Yehonal <yehonal.azeroth@gmail.com> | 2025-08-25 20:25:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-08-25 20:25:17 +0200 |
commit | de98f424116e77b9ec802db59d4c515cc6be144f (patch) | |
tree | 9146e1a57da5f224c13fec60ba529174619a0bb0 | |
parent | 846efc3b6407b83da1b09eee78d1d94a654b219a (diff) |
feat(Service Manager): add service registry custom dir and restore functionality (#22589)
This pull request introduces significant enhancements to the service management system by adding a service registry with features like automatic tracking, reboot persistence, and restoration of missing services.
The goal of this PR is to allow the user to store the service configuration files into an arbitrary directory, in this way they can be easily tracked, versioned, and replicated across different environments
It also includes a migration script to transition from the legacy service configuration format to the new registry-based system. Below is a summary of the most important changes:
### Service Registry and Management Enhancements:
1. **Service Registry Integration**:
- Added a comprehensive service registry system to track all created services, enabling features like cross-reboot persistence and restoration of missing services (`apps/startup-scripts/src/service-manager.sh`). [[1]](diffhunk://#diff-31edfed7f73d0647a5fc96ce74c249e025e884cd1fe06621cb78eb4a381464f9R41-R229) [[2]](diffhunk://#diff-31edfed7f73d0647a5fc96ce74c249e025e884cd1fe06621cb78eb4a381464f9R273)
- Introduced commands for managing the registry, such as `restore` for recreating missing services and `list` for viewing registered services. [[1]](diffhunk://#diff-31edfed7f73d0647a5fc96ce74c249e025e884cd1fe06621cb78eb4a381464f9R273) [[2]](diffhunk://#diff-31edfed7f73d0647a5fc96ce74c249e025e884cd1fe06621cb78eb4a381464f9R332-R334) [[3]](diffhunk://#diff-31edfed7f73d0647a5fc96ce74c249e025e884cd1fe06621cb78eb4a381464f9R346-L172)
2. **PM2 Persistence**:
- Enhanced PM2 integration to automatically configure startup persistence across reboots using `pm2 startup` and `pm2 save` after service creation.
### Migration and Compatibility:
3. **Migration Script**:
- Added a `migrate-registry.sh` script to convert legacy service configurations into the new registry format. It ensures compatibility while preserving existing service information (`apps/startup-scripts/src/migrate-registry.sh`).
### Documentation Updates:
4. **Updated README**:
- Expanded documentation in `README.md` to explain the new service registry features, including usage examples, custom configuration directories, and migration instructions. [[1]](diffhunk://#diff-0917b2888cc9b16539173f318b77773d08f7bf360579b68b9710a96ca2bcbb64L387-R468) [[2]](diffhunk://#diff-0917b2888cc9b16539173f318b77773d08f7bf360579b68b9710a96ca2bcbb64R613-R626)
### Configuration Improvements:
5. **Custom Configuration Directories**:
- Added support for overriding the default configuration directory for service registry and files using the `AC_SERVICE_CONFIG_DIR` environment variable. [[1]](diffhunk://#diff-31edfed7f73d0647a5fc96ce74c249e025e884cd1fe06621cb78eb4a381464f9L14-R15) [[2]](diffhunk://#diff-31edfed7f73d0647a5fc96ce74c249e025e884cd1fe06621cb78eb4a381464f9R346-L172)
These changes significantly improve the usability, reliability, and maintainability of the service management system, especially for setups requiring persistence and multi-project configurations.
-rw-r--r-- | apps/startup-scripts/README.md | 95 | ||||
-rwxr-xr-x | apps/startup-scripts/src/migrate-registry.sh | 144 | ||||
-rwxr-xr-x | apps/startup-scripts/src/service-manager.sh | 407 |
3 files changed, 561 insertions, 85 deletions
diff --git a/apps/startup-scripts/README.md b/apps/startup-scripts/README.md index 7b057581d7..176ec3ed6c 100644 --- a/apps/startup-scripts/README.md +++ b/apps/startup-scripts/README.md @@ -312,6 +312,9 @@ Services support two restart policies: # Edit configuration ./service-manager.sh edit world + +# Restore missing services from registry +./service-manager.sh restore ``` ## 🌍 Multiple Realms Setup @@ -384,29 +387,85 @@ cp examples/restarter-world.sh restarter-realm2.sh ## 🛠️ Service Management -### PM2 Services +### Service Registry and Persistence -When using PM2 as the service provider: +The service manager includes a comprehensive registry system that tracks all created services and enables automatic restoration: + +#### Service Registry Features + +- **Automatic Tracking**: All services are automatically registered when created +- **Cross-Reboot Persistence**: PM2 services are configured with startup persistence +- **Service Restoration**: Missing services can be detected and restored from registry +- **Migration Support**: Legacy service configurations can be migrated to the new format + +#### Using the Registry + +```bash +# Check for missing services and restore them +./service-manager.sh restore + +# List all registered services (includes status) +./service-manager.sh list + +# Services are automatically added to registry on creation +./service-manager.sh create auth authserver --bin-path /path/to/bin +``` + +#### Custom Configuration Directories + +You can customize where service configurations and PM2/systemd files are stored: + +```bash +# Set custom directories +export AC_SERVICE_CONFIG_DIR="/path/to/your/project/services" + +# Now all service operations will use these custom directories +./service-manager.sh create auth authserver --bin-path /path/to/bin +``` + +This is particularly useful for: +- **Version Control**: Keep service configurations in your project repository +- **Multiple Projects**: Separate service configurations per project +- **Team Collaboration**: Share service setups across development teams + +#### Migration from Legacy Format + +If you have existing services in the old format, use the migration script: ```bash -# PM2-specific commands -pm2 list # List all PM2 processes -pm2 logs auth # View logs -pm2 monit # Real-time monitoring -pm2 restart auth # Restart service -pm2 delete auth # Remove service +# Migrate existing registry to new format +./migrate-registry.sh -# Save PM2 configuration -pm2 save -pm2 startup # Auto-start on boot +# The script will: +# - Detect old format automatically +# - Create a backup of the old registry +# - Convert to new format with proper tracking +# - Preserve all existing service information ``` +### PM2 Services + +When using PM2 as the service provider: + +* [PM2 CLI Documentation](https://pm2.io/docs/runtime/reference/pm2-cli/) + +**Automatic PM2 Persistence**: The service manager automatically configures PM2 for persistence across reboots by: +- Running `pm2 startup` to set up the startup script +- Running `pm2 save` after each service creation/modification +- This ensures your services automatically start when the system reboots + NOTE: pm2 cannot run tmux/screen sessions, but you can always use the `attach` command to connect to the service console because pm2 supports interactive mode. ### Environment Variables The startup scripts recognize several environment variables for configuration and runtime behavior: +#### Configuration Directory Variables + +- **`AC_SERVICE_CONFIG_DIR`**: Override the default configuration directory for services registry and configurations + - Default: `${XDG_CONFIG_HOME:-$HOME/.config}/azerothcore/services` + - Used for storing service registry and run-engine configurations + #### Service Detection Variables - **`AC_LAUNCHED_BY_PM2`**: Set to `1` when launched by PM2 (automatically set by service-manager) @@ -551,4 +610,18 @@ npm install -g pm2 sudo npm install -g pm2 ``` +#### 7. Registry Out of Sync +```bash +# If the service registry shows services that don't actually exist +``` +**Solution**: Use registry sync or restore +```bash +# Check and restore missing services (also cleans up orphaned entries) +./service-manager.sh restore + +# If you have a very old registry format, migrate it +./migrate-registry.sh +``` + + diff --git a/apps/startup-scripts/src/migrate-registry.sh b/apps/startup-scripts/src/migrate-registry.sh new file mode 100755 index 0000000000..c5898794f5 --- /dev/null +++ b/apps/startup-scripts/src/migrate-registry.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash + +# One-time migration script for service registry +# Converts old format to new format + +set -euo pipefail # Strict error handling + +CONFIG_DIR="${AC_SERVICE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/azerothcore/services}" +REGISTRY_FILE="$CONFIG_DIR/service_registry.json" +BACKUP_FILE="$CONFIG_DIR/service_registry.json.backup" + +# Colors +readonly YELLOW='\033[1;33m' +readonly GREEN='\033[0;32m' +readonly RED='\033[0;31m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' + +echo -e "${BLUE}AzerothCore Service Registry Migration Tool${NC}" +echo "==============================================" + +# Check dependencies +if ! command -v jq >/dev/null 2>&1; then + echo -e "${RED}Error: jq is required but not installed. Please install jq package.${NC}" + exit 1 +fi + +# Create config directory if it doesn't exist +mkdir -p "$CONFIG_DIR" + +# Check if registry exists +if [ ! -f "$REGISTRY_FILE" ]; then + echo -e "${YELLOW}No registry file found. Nothing to migrate.${NC}" + exit 0 +fi + +# Validate JSON format +if ! jq empty "$REGISTRY_FILE" >/dev/null 2>&1; then + echo -e "${RED}Error: Registry file contains invalid JSON.${NC}" + echo "Please check the file: $REGISTRY_FILE" + exit 1 +fi + +# Check if it's already new format +if jq -e 'type == "array" and (length == 0 or .[0] | has("bin_path"))' "$REGISTRY_FILE" >/dev/null 2>&1; then + echo -e "${GREEN}Registry is already in new format. No migration needed.${NC}" + exit 0 +fi + +# Check if it's old format +if ! jq -e 'type == "array" and (length == 0 or .[0] | has("config"))' "$REGISTRY_FILE" >/dev/null 2>&1; then + echo -e "${YELLOW}Registry format not recognized. Manual review needed.${NC}" + echo "Current registry content:" + cat "$REGISTRY_FILE" + exit 1 +fi + +echo -e "${YELLOW}Old format detected. Starting migration...${NC}" + +# Create backup +if ! cp "$REGISTRY_FILE" "$BACKUP_FILE"; then + echo -e "${RED}Error: Failed to create backup file.${NC}" + exit 1 +fi +echo -e "${BLUE}Backup created: $BACKUP_FILE${NC}" + +# Convert to new format +echo "[]" > "$REGISTRY_FILE.new" + +services_migrated=0 +while IFS= read -r service; do + if [ -n "$service" ] && [ "$service" != "null" ]; then + name=$(echo "$service" | jq -r '.name // ""') + provider=$(echo "$service" | jq -r '.provider // ""') + type=$(echo "$service" | jq -r '.type // ""') + config=$(echo "$service" | jq -r '.config // ""') + + # Validate required fields + if [ -z "$name" ] || [ -z "$provider" ] || [ -z "$type" ]; then + echo -e "${YELLOW}Skipping invalid service entry: $service${NC}" + continue + fi + + echo -e "${YELLOW}Migrating service: $name${NC}" + + # Create new format entry with all required fields + new_entry=$(jq -n \ + --arg name "$name" \ + --arg provider "$provider" \ + --arg type "$type" \ + --arg bin_path "unknown" \ + --arg args "" \ + --arg created "$(date -Iseconds)" \ + --arg status "migrated" \ + --arg systemd_type "--user" \ + --arg restart_policy "always" \ + --arg session_manager "none" \ + --arg gdb_enabled "0" \ + --arg pm2_opts "" \ + --arg server_config "" \ + --arg legacy_config "$config" \ + '{ + name: $name, + provider: $provider, + type: $type, + bin_path: $bin_path, + args: $args, + created: $created, + status: $status, + systemd_type: $systemd_type, + restart_policy: $restart_policy, + session_manager: $session_manager, + gdb_enabled: $gdb_enabled, + pm2_opts: $pm2_opts, + server_config: $server_config, + legacy_config: $legacy_config + }') + + # Add to new registry with error checking + if ! jq --argjson entry "$new_entry" '. += [$entry]' "$REGISTRY_FILE.new" > "$REGISTRY_FILE.new.tmp"; then + echo -e "${RED}Error: Failed to add service $name to new registry${NC}" + rm -f "$REGISTRY_FILE.new" "$REGISTRY_FILE.new.tmp" + exit 1 + fi + mv "$REGISTRY_FILE.new.tmp" "$REGISTRY_FILE.new" + + services_migrated=$((services_migrated + 1)) + fi +done < <(jq -c '.[]?' "$BACKUP_FILE" 2>/dev/null || echo "") + +# Replace old registry with new one +if ! mv "$REGISTRY_FILE.new" "$REGISTRY_FILE"; then + echo -e "${RED}Error: Failed to replace old registry with new one${NC}" + exit 1 +fi + +echo -e "${GREEN}Migration completed successfully!${NC}" +echo -e "${BLUE}Services migrated: $services_migrated${NC}" +echo -e "${BLUE}Use 'service-manager.sh restore' to review and update services.${NC}" +echo -e "${YELLOW}Note: Migrated services have bin_path='unknown' and need manual recreation.${NC}" +echo "" +echo -e "${BLUE}To recreate services, use commands like:${NC}" +echo " ./service-manager.sh create auth authserver --provider pm2 --bin-path /path/to/your/bin" +echo " ./service-manager.sh create world worldserver --provider systemd --bin-path /path/to/your/bin" diff --git a/apps/startup-scripts/src/service-manager.sh b/apps/startup-scripts/src/service-manager.sh index 980dfbb3a0..34dc4c4d5a 100755 --- a/apps/startup-scripts/src/service-manager.sh +++ b/apps/startup-scripts/src/service-manager.sh @@ -4,6 +4,8 @@ # A unified interface for managing AzerothCore services with PM2 or systemd # This script provides commands to create, update, delete, and manage server instances +set -euo pipefail # Strict error handling + # Script location CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -11,16 +13,16 @@ SCRIPT_DIR="$CURRENT_PATH" ROOT_DIR="$(cd "$CURRENT_PATH/../../.." && pwd)" -# Configuration directory -CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/azerothcore/services" +# 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" # Colors for output -YELLOW='\033[1;33m' -GREEN='\033[0;32m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color +readonly YELLOW='\033[1;33m' +readonly GREEN='\033[0;32m' +readonly RED='\033[0;31m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color # Create config directory if it doesn't exist mkdir -p "$CONFIG_DIR" @@ -38,6 +40,198 @@ check_dependencies() { } } +# Registry management functions +function add_service_to_registry() { + local service_name="$1" + local provider="$2" + local service_type="$3" + local bin_path="$4" + local args="$5" + local systemd_type="$6" + local restart_policy="$7" + local session_manager="$8" + local gdb_enabled="$9" + local pm2_opts="${10}" + local server_config="${11}" + + # Remove any existing entry with the same service name to avoid duplicates + local tmp_file + tmp_file=$(mktemp) + jq --arg name "$service_name" 'map(select(.name != $name))' "$REGISTRY_FILE" > "$tmp_file" && mv "$tmp_file" "$REGISTRY_FILE" + + # Add the new entry to the registry + tmp_file=$(mktemp) + jq --arg name "$service_name" \ + --arg provider "$provider" \ + --arg type "$service_type" \ + --arg bin_path "$bin_path" \ + --arg args "$args" \ + --arg created "$(date -Iseconds)" \ + --arg systemd_type "$systemd_type" \ + --arg restart_policy "$restart_policy" \ + --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}]' \ + "$REGISTRY_FILE" > "$tmp_file" && mv "$tmp_file" "$REGISTRY_FILE" + + echo -e "${GREEN}Service '$service_name' added to registry${NC}" +} + +function remove_service_from_registry() { + local service_name="$1" + + if [ -f "$REGISTRY_FILE" ]; then + local tmp_file + tmp_file=$(mktemp) + jq --arg name "$service_name" \ + 'map(select(.name != $name))' \ + "$REGISTRY_FILE" > "$tmp_file" && mv "$tmp_file" "$REGISTRY_FILE" + echo -e "${GREEN}Service '$service_name' removed from registry${NC}" + fi +} + +function restore_missing_services() { + echo -e "${BLUE}Checking for missing services...${NC}" + + if [ ! -f "$REGISTRY_FILE" ] || [ ! -s "$REGISTRY_FILE" ]; then + echo -e "${YELLOW}No services registry found or empty${NC}" + return 0 + fi + + local missing_services=() + 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 + + echo -e "${BLUE}Found $services_count registered services. Checking status...${NC}" + + # Check each service + for i in $(seq 0 $((services_count-1))); do + local service=$(jq -r ".[$i]" "$REGISTRY_FILE") + 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 args=$(echo "$service" | jq -r '.args // ""') + local status=$(echo "$service" | jq -r '.status // "active"') + 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 service_exists=false + + if [ "$provider" = "pm2" ]; then + if pm2 describe "$name" >/dev/null 2>&1; then + service_exists=true + fi + elif [ "$provider" = "systemd" ]; then + local user_unit="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/$name.service" + local system_unit="/etc/systemd/system/$name.service" + if [ -f "$user_unit" ] || [ -f "$system_unit" ]; then + # Unit file present, you can also check if it is active + service_exists=true + else + # Unit file missing: service needs to be recreated! + service_exists=false + fi + fi + + if [ "$service_exists" = false ]; then + missing_services+=("$i") + echo -e "${YELLOW}Missing service: $name ($provider)${NC}" + else + echo -e "${GREEN}✓ Service $name ($provider) exists${NC}" + fi + done + + # Handle missing services + if [ ${#missing_services[@]} -eq 0 ]; then + echo -e "${GREEN}All registered services are present${NC}" + return 0 + fi + + echo -e "${YELLOW}Found ${#missing_services[@]} missing services${NC}" + + for index in "${missing_services[@]}"; do + local service=$(jq -r ".[$index]" "$REGISTRY_FILE") + 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 // ""') + + echo "" + echo -e "${YELLOW}Service '$name' ($provider) is missing${NC}" + echo " Type: $service_type" + echo " Status: $status" + + if [ "$bin_path" = "unknown" ] || [ "$bin_path" = "null" ] || [ "$status" = "migrated" ]; then + echo " Binary: <needs manual configuration>" + echo " Args: <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" + fi + echo "" + + read -p "Do you want to (r)ecreate, (d)elete from registry, or (s)kip? [r/d/s]: " choice + + case "$choice" in + r|R|recreate) + if [ "$bin_path" = "unknown" ] || [ "$status" = "migrated" ]; then + echo -e "${YELLOW}Please recreate manually with full create command${NC}" + read -p "Remove this entry from registry? [y/n]: " remove_entry + if [[ "$remove_entry" =~ ^[Yy]$ ]]; then + remove_service_from_registry "$name" + fi + else + echo -e "${BLUE}Recreating service '$name'...${NC}" + if [ "$provider" = "pm2" ]; then + if [ "$args" != "null" ] && [ -n "$args" ]; then + pm2_create_service "$name" "$bin_path $args" "$restart_policy" $pm2_opts + else + pm2_create_service "$name" "$bin_path" "$restart_policy" $pm2_opts + 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}" + 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 + ;; + d|D|delete) + echo -e "${BLUE}Removing '$name' from registry...${NC}" + remove_service_from_registry "$name" + ;; + s|S|skip|*) + echo -e "${BLUE}Skipping '$name'${NC}" + ;; + esac + done +} + # Check if PM2 is installed check_pm2() { if ! command -v pm2 >/dev/null 2>&1; then @@ -81,6 +275,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 start|stop|restart|status <service-name>" echo " $base_name logs <service-name> [--follow]" echo " $base_name attach <service-name>" @@ -139,6 +334,9 @@ function print_help() { echo " $base_name attach worldserver-realm1" echo " $base_name list pm2" echo "" + echo " # Restore missing services from registry" + echo " $base_name restore" + echo "" echo "Notes:" echo " - Configuration editing modifies run-engine settings (GDB, session manager, etc.)" echo " - Use --server-config for the actual server configuration file" @@ -150,26 +348,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 "" + echo "Environment Variables:" + echo " AC_SERVICE_CONFIG_DIR - Override default config directory for services registry" } -function register_service() { - local service_name="$1" - local provider="$2" - local service_type="$3" - local config_file="$CONFIG_DIR/$service_name.conf" - - # Add to registry - local tmp_file=$(mktemp) - jq --arg name "$service_name" \ - --arg provider "$provider" \ - --arg type "$service_type" \ - --arg config "$config_file" \ - '. += [{"name": $name, "provider": $provider, "type": $type, "config": $config}]' \ - "$REGISTRY_FILE" > "$tmp_file" - mv "$tmp_file" "$REGISTRY_FILE" - - echo -e "${GREEN}Service $service_name registered successfully${NC}" -} + function validate_service_exists() { local service_name="$1" @@ -210,47 +395,42 @@ function validate_service_exists() { function sync_registry() { echo -e "${YELLOW}Syncing service registry with actual services...${NC}" - local services=$(jq -c '.[]' "$REGISTRY_FILE") - local tmp_file=$(mktemp) + if [ ! -f "$REGISTRY_FILE" ] || [ ! -s "$REGISTRY_FILE" ]; then + echo -e "${YELLOW}No services registry found or empty${NC}" + return 0 + fi - # Initialize with empty array + local services_count=$(jq length "$REGISTRY_FILE") + if [ "$services_count" -eq 0 ]; then + echo -e "${YELLOW}No services registered${NC}" + return 0 + fi + + local tmp_file=$(mktemp) echo "[]" > "$tmp_file" # Check each service in registry - while read -r service_info; do - if [ -n "$service_info" ]; then - local name=$(echo "$service_info" | jq -r '.name') - local provider=$(echo "$service_info" | jq -r '.provider') - - if validate_service_exists "$name" "$provider"; then - # Service exists, add it to the new registry - jq --argjson service "$service_info" '. += [$service]' "$tmp_file" > "$tmp_file.new" - mv "$tmp_file.new" "$tmp_file" - else - echo -e "${YELLOW}Service '$name' no longer exists. Removing from registry.${NC}" - # Don't add to new registry - fi + for i in $(seq 0 $((services_count-1))); do + local service=$(jq -r ".[$i]" "$REGISTRY_FILE") + local name=$(echo "$service" | jq -r '.name') + local provider=$(echo "$service" | jq -r '.provider') + + if validate_service_exists "$name" "$provider"; then + # Service exists, add it to the new registry + jq --argjson service "$service" '. += [$service]' "$tmp_file" > "$tmp_file.new" + mv "$tmp_file.new" "$tmp_file" + else + echo -e "${YELLOW}Service '$name' no longer exists. Removing from registry.${NC}" + # Don't add to new registry fi - done <<< "$services" + done # Replace registry with synced version mv "$tmp_file" "$REGISTRY_FILE" echo -e "${GREEN}Registry synchronized.${NC}" } -function unregister_service() { - local service_name="$1" - - # Remove from registry - local tmp_file=$(mktemp) - jq --arg name "$service_name" '. | map(select(.name != $name))' "$REGISTRY_FILE" > "$tmp_file" - mv "$tmp_file" "$REGISTRY_FILE" - - # Remove configuration file - rm -f "$CONFIG_DIR/$service_name.conf" - - echo -e "${GREEN}Service $service_name unregistered${NC}" -} + function get_service_info() { local service_name="$1" @@ -317,6 +497,15 @@ function pm2_create_service() { if eval "$pm2_cmd"; then echo -e "${GREEN}PM2 service '$service_name' created successfully${NC}" pm2 save + + # Setup PM2 startup for persistence across reboots + 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" "" + return 0 else echo -e "${RED}Failed to create PM2 service '$service_name'${NC}" @@ -334,8 +523,8 @@ function pm2_remove_service() { # Stop the service if it's running if pm2 describe "$service_name" >/dev/null 2>&1; then - pm2 stop "$service_name" 2>/dev/null || true - pm2 delete "$service_name" 2>/dev/null + pm2 stop "$service_name" 2>&1 || true + pm2 delete "$service_name" 2>&1 || true # Wait for PM2 to process the stop/delete command with timeout local timeout=10 @@ -357,8 +546,13 @@ 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" 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" fi return 0 @@ -391,6 +585,7 @@ function pm2_service_logs() { # Systemd service management functions function get_systemd_dir() { local type="$1" + if [ "$type" = "--system" ]; then echo "/etc/systemd/system" else @@ -403,17 +598,32 @@ function systemd_create_service() { local command="$2" local restart_policy="$3" local systemd_type="--user" + local bin_path="" + local gdb_enabled="0" + local server_config="" shift 3 check_systemd || return 1 - # Parse systemd type + # Parse systemd type and extract additional parameters while [[ $# -gt 0 ]]; do case "$1" in --system|--user) systemd_type="$1" shift ;; + --bin-path) + bin_path="$2" + shift 2 + ;; + --gdb-enabled) + gdb_enabled="$2" + shift 2 + ;; + --server-config) + server_config="$2" + shift 2 + ;; *) command+=" $1" shift @@ -421,6 +631,18 @@ function systemd_create_service() { esac done + # If bin_path is not provided, try to extract from command + if [ -z "$bin_path" ]; then + # Try to extract bin path from run-engine command + if [[ "$command" =~ run-engine[[:space:]]+start[[:space:]]+([^[:space:]]+) ]]; then + local binary_path="${BASH_REMATCH[1]}" + bin_path="$(dirname "$binary_path")" + else + # Fallback to current directory + bin_path="$(pwd)" + fi + fi + local systemd_dir=$(get_systemd_dir "$systemd_type") local service_file="$systemd_dir/$service_name.service" @@ -457,6 +679,11 @@ function systemd_create_service() { # Create service file echo -e "${YELLOW}Creating systemd service: $service_name${NC}" + # Ensure bin_path is absolute + if [[ ! "$bin_path" = /* ]]; then + bin_path="$(realpath "$bin_path")" + fi + if [ "$systemd_type" = "--system" ]; then # System service template (with User directive) cat > "$service_file" << EOF @@ -471,7 +698,7 @@ Restart=$restart_policy RestartSec=3 User=$(whoami) Group=$(id -gn) -WorkingDirectory=$(realpath "$bin_path") +WorkingDirectory=$bin_path StandardOutput=journal+console StandardError=journal+console @@ -490,7 +717,7 @@ Type=${service_type} ExecStart=$command Restart=$restart_policy RestartSec=3 -WorkingDirectory=$(realpath "$bin_path") +WorkingDirectory=$bin_path StandardOutput=journal+console StandardError=journal+console @@ -498,10 +725,6 @@ StandardError=journal+console WantedBy=default.target EOF fi - - if [ "$systemd_type" = "--system" ]; then - sed -i 's/WantedBy=default.target/WantedBy=multi-user.target/' "$service_file" - fi # Reload systemd and enable service if [ "$systemd_type" = "--system" ]; then @@ -513,6 +736,10 @@ EOF fi echo -e "${GREEN}Systemd service '$service_name' created successfully${NC}" + + # Add to registry + add_service_to_registry "$service_name" "systemd" "service" "$command" "" "$systemd_type" "$restart_policy" "$session_manager" "$gdb_enabled" "" "$server_config" + return 0 } @@ -572,6 +799,10 @@ function systemd_remove_service() { if [ "$removal_failed" = "true" ]; then echo -e "${YELLOW}Note: Service may still be running but configuration was removed${NC}" fi + + # Remove from registry + remove_service_from_registry "$service_name" + return 0 else echo -e "${RED}Failed to remove systemd service file '$service_file'${NC}" @@ -659,7 +890,7 @@ function create_service() { # Default values for run-engine configuration local provider="auto" - local bin_path="$BINPATH/bin" # get from config or environment + local bin_path="${BINPATH:-$ROOT_DIR/bin}" # get from config or environment local server_config="" local session_manager="none" local gdb_enabled="0" @@ -839,8 +1070,6 @@ EOF # Check if service creation was successful if [ "$service_creation_success" = "true" ]; then - # Register the service - register_service "$service_name" "$provider" "$service_type" echo -e "${GREEN}Service '$service_name' created successfully${NC}" echo -e "${BLUE}Run-engine config: $run_engine_config${NC}" @@ -880,14 +1109,20 @@ function update_service() { # Extract service information local provider=$(echo "$service_info" | jq -r '.provider') local service_type=$(echo "$service_info" | jq -r '.type') - local config_file=$(echo "$service_info" | jq -r '.config') + local config_file="$CONFIG_DIR/$service_name.conf" # Load current configuration + if [ ! -f "$config_file" ]; then + echo -e "${RED}Error: Service configuration file not found: $config_file${NC}" + return 1 + fi source "$config_file" # Load current run-engine configuration if [ -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}" fi # Parse options to update @@ -1020,11 +1255,13 @@ function delete_service() { # Extract provider and config local provider=$(echo "$service_info" | jq -r '.provider') - local config_file=$(echo "$service_info" | jq -r '.config') + local config_file="$CONFIG_DIR/$service_name.conf" # Load configuration to get run-engine config file if [ -f "$config_file" ]; then source "$config_file" + else + echo -e "${YELLOW}Warning: Service configuration file not found: $config_file${NC}" fi echo -e "${YELLOW}Deleting service '$service_name' (provider: $provider)...${NC}" @@ -1048,8 +1285,9 @@ function delete_service() { echo -e "${GREEN}Removed run-engine config: $RUN_ENGINE_CONFIG_FILE${NC}" fi - # Unregister service - unregister_service "$service_name" + # Remove configuration file + rm -f "$config_file" + echo -e "${GREEN}Service '$service_name' deleted successfully${NC}" else echo -e "${RED}Failed to remove service '$service_name' from $provider${NC}" @@ -1166,7 +1404,7 @@ function edit_config() { fi # Get configuration file path - local config_file=$(echo "$service_info" | jq -r '.config') + local config_file="$CONFIG_DIR/$service_name.conf" # Load configuration to get run-engine config file source "$config_file" @@ -1191,7 +1429,7 @@ function attach_to_service() { # Extract provider local provider=$(echo "$service_info" | jq -r '.provider') - local config_file=$(echo "$service_info" | jq -r '.config') + local config_file="$CONFIG_DIR/$service_name.conf" # Load configuration to get run-engine config file if [ ! -f "$config_file" ]; then @@ -1206,6 +1444,11 @@ function attach_to_service() { 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 source "$RUN_ENGINE_CONFIG_FILE" @@ -1264,9 +1507,22 @@ function attach_interactive_shell() { # For systemd without session manager, show helpful message local service_info=$(get_service_info "$service_name") - local config_file=$(echo "$service_info" | jq -r '.config') + local config_file="$CONFIG_DIR/$service_name.conf" + + # Check if config file exists before sourcing + if [ ! -f "$config_file" ]; then + echo -e "${RED}Error: Service configuration file not found: $config_file${NC}" + return 1 + fi 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}" + return 1 + fi + source "$RUN_ENGINE_CONFIG_FILE" echo -e "${RED}Error: Cannot attach to systemd service '$service_name'${NC}" @@ -1375,6 +1631,9 @@ case "${1:-help}" in list) list_services "$2" ;; + restore) + restore_missing_services + ;; start|stop|restart|status) if [ $# -lt 2 ]; then echo -e "${RED}Error: Service name required for $1 command${NC}" |