#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" declare -a APP_NAMES=() declare -a APP_DIRS=() declare -a APP_ECOSYSTEMS=() trim() { local s="$1" s="${s#"${s%%[![:space:]]*}"}" s="${s%"${s##*[![:space:]]}"}" printf "%s" "$s" } extract_app_name() { local ecosystem_file="$1" local name name="$(grep -E 'name:[[:space:]]*"[^"]+"' "$ecosystem_file" | head -n1 | sed -E 's/.*name:[[:space:]]*"([^"]+)".*/\1/' || true)" name="$(trim "$name")" if [ -z "$name" ]; then name="$(basename "$(dirname "$ecosystem_file")")" fi printf "%s" "$name" } discover_ecosystems() { local file app_name project_dir while IFS= read -r file; do project_dir="$(dirname "$file")" app_name="$(extract_app_name "$file")" APP_NAMES+=("$app_name") APP_DIRS+=("$project_dir") APP_ECOSYSTEMS+=("$file") done < <( find "$ROOT_DIR" -mindepth 2 -maxdepth 2 -type f \ \( -name "ecosystem.config.cjs" -o -name "ecosystem.cron.config.js" \) \ | sort ) } print_services() { local i rel if [ "${#APP_NAMES[@]}" -eq 0 ]; then echo "No ecosystem config files found under $ROOT_DIR" return fi echo "Discovered services:" for i in "${!APP_NAMES[@]}"; do rel="${APP_ECOSYSTEMS[$i]#"$ROOT_DIR/"}" printf " %2d) %-18s %s\n" "$((i + 1))" "${APP_NAMES[$i]}" "$rel" done } run_action() { local action="$1" local app_name="$2" local project_dir="$3" local ecosystem_file="$4" cd "$project_dir" case "$action" in recreate) echo "Recreating '$app_name' from '$ecosystem_file'..." pm2 delete "$app_name" >/dev/null 2>&1 || true pm2 start "$ecosystem_file" pm2 save ;; reload) echo "Reloading '$app_name'..." pm2 reload "$app_name" --update-env pm2 save ;; restart) echo "Restarting '$app_name'..." pm2 restart "$app_name" --update-env pm2 save ;; delete) echo "Deleting '$app_name'..." pm2 delete "$app_name" pm2 save ;; *) echo "Unknown action: $action" exit 1 ;; esac } interactive_mode() { local action_pick action idx print_services echo echo "Choose action:" echo " 1) Recreate from ecosystem (best for .env/config changes)" echo " 2) Reload" echo " 3) Restart" echo " 4) Delete" read -r -p "Action [1]: " action_pick action_pick="${action_pick:-1}" case "$action_pick" in 1) action="recreate" ;; 2) action="reload" ;; 3) action="restart" ;; 4) action="delete" ;; *) echo "Invalid action"; exit 1 ;; esac echo read -r -p "Service number: " idx if ! [[ "$idx" =~ ^[0-9]+$ ]]; then echo "Invalid service number" exit 1 fi idx=$((idx - 1)) if [ "$idx" -lt 0 ] || [ "$idx" -ge "${#APP_NAMES[@]}" ]; then echo "Service number out of range" exit 1 fi echo run_action "$action" "${APP_NAMES[$idx]}" "${APP_DIRS[$idx]}" "${APP_ECOSYSTEMS[$idx]}" echo pm2 ls } usage() { cat < Actions: recreate | reload | restart | delete Service can be: - numeric index from "list" - app name (e.g. zap, zap-cleanup, caddy-manager, api) EOF } resolve_service_index() { local target="$1" local i if [[ "$target" =~ ^[0-9]+$ ]]; then i=$((target - 1)) if [ "$i" -ge 0 ] && [ "$i" -lt "${#APP_NAMES[@]}" ]; then echo "$i" return 0 fi return 1 fi for i in "${!APP_NAMES[@]}"; do if [ "${APP_NAMES[$i]}" = "$target" ]; then echo "$i" return 0 fi done return 1 } main() { if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then usage exit 0 fi discover_ecosystems if [ "${1:-}" = "list" ]; then print_services exit 0 fi if [ $# -eq 0 ]; then interactive_mode exit 0 fi if [ $# -ne 2 ]; then usage exit 1 fi local action="$1" local target="$2" local idx if ! idx="$(resolve_service_index "$target")"; then echo "Unknown service: $target" echo print_services exit 1 fi run_action "$action" "${APP_NAMES[$idx]}" "${APP_DIRS[$idx]}" "${APP_ECOSYSTEMS[$idx]}" } main "$@"