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.
This commit is contained in:
Yehonal
2025-08-25 20:25:17 +02:00
committed by GitHub
parent 846efc3b64
commit de98f42411
3 changed files with 561 additions and 85 deletions

View File

@@ -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,22 +387,72 @@ cp examples/restarter-world.sh restarter-realm2.sh
## 🛠️ Service Management
### Service Registry and Persistence
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
# Migrate existing registry to new format
./migrate-registry.sh
# 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:
```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
* [PM2 CLI Documentation](https://pm2.io/docs/runtime/reference/pm2-cli/)
# Save PM2 configuration
pm2 save
pm2 startup # Auto-start on boot
```
**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.
@@ -407,6 +460,12 @@ NOTE: pm2 cannot run tmux/screen sessions, but you can always use the `attach` c
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
```

View File

@@ -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"

View File

@@ -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}"