From ce97bfa3eaba843b92666859978c3b1135b47bd3 Mon Sep 17 00:00:00 2001 From: LyAhn Date: Mon, 8 Jun 2026 21:36:11 +0000 Subject: [PATCH] Add pm2 helper scripts and README --- pm2/README.md | 30 +++++++ pm2/pm2-manager.sh | 209 ++++++++++++++++++++++++++++++++++++++++++++ pm2/pm2-recreate.sh | 33 +++++++ 3 files changed, 272 insertions(+) create mode 100644 pm2/README.md create mode 100755 pm2/pm2-manager.sh create mode 100755 pm2/pm2-recreate.sh diff --git a/pm2/README.md b/pm2/README.md new file mode 100644 index 0000000..9fa0d2f --- /dev/null +++ b/pm2/README.md @@ -0,0 +1,30 @@ +# PM2 Helpers + +Utility scripts for managing PM2 process groups across multiple projects. + +## Files + +- **`pm2-manager.sh`** — Interactive CLI for managing all PM2 services discovered under the parent directory. Supports `list`, `recreate`, `reload`, `restart`, and `delete` actions. Operates on `ecosystem.config.cjs` and `ecosystem.cron.config.js` files found at depth 2. + +- **`pm2-recreate.sh`** — Simple one-shot script to delete and re-create a single PM2 app from its ecosystem file. Useful for picking up `.env` or config changes. + +## Usage + +```bash +# Interactive mode +./pm2-manager.sh + +# List discovered services +./pm2-manager.sh list + +# Run action by index or name +./pm2-manager.sh restart 1 +./pm2-manager.sh reload zap + +# Recreate a single app +./pm2-recreate.sh /path/to/project app-name ecosystem.config.cjs +``` + +## Conventions + +Both scripts look for project directories with `ecosystem.config.cjs` (or `ecosystem.cron.config.js`) one level below the workspace root. App names are extracted from the ecosystem file's `name` field, falling back to the directory name. diff --git a/pm2/pm2-manager.sh b/pm2/pm2-manager.sh new file mode 100755 index 0000000..cc7d758 --- /dev/null +++ b/pm2/pm2-manager.sh @@ -0,0 +1,209 @@ +#!/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 "$@" diff --git a/pm2/pm2-recreate.sh b/pm2/pm2-recreate.sh new file mode 100755 index 0000000..7c4daba --- /dev/null +++ b/pm2/pm2-recreate.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then + echo "Usage: $0 [ecosystem_file]" + echo "Example: $0 /home/ubuntu/JezzWTF/zap zap ecosystem.config.cjs" + exit 1 +fi + +PROJECT_DIR="$1" +APP_NAME="$2" +ECOSYSTEM_FILE="${3:-ecosystem.config.cjs}" + +if [ ! -d "$PROJECT_DIR" ]; then + echo "ERROR: project directory not found: $PROJECT_DIR" + exit 1 +fi + +ECOSYSTEM_PATH="$PROJECT_DIR/$ECOSYSTEM_FILE" +if [ ! -f "$ECOSYSTEM_PATH" ]; then + echo "ERROR: ecosystem file not found: $ECOSYSTEM_PATH" + exit 1 +fi + +cd "$PROJECT_DIR" + +echo "Recreating PM2 app '$APP_NAME' from '$ECOSYSTEM_FILE'..." +pm2 delete "$APP_NAME" >/dev/null 2>&1 || true +pm2 start "$ECOSYSTEM_PATH" +pm2 save + +echo "Done. Current PM2 status:" +pm2 ls