0e5670b5e8
- Ctrl-managed configs are preferred. - Repo-local duplicates for the same name + cwd are hidden - Each service is one line: index, app name, source, cwd, config - list-all still shows every discovered ecosystem file when you need to debug stale/duplicate configs.
278 lines
5.9 KiB
Bash
Executable File
278 lines
5.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
CTRL_DATA_DIR="${CTRL_DATA_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/ctrl}"
|
|
CTRL_ECOSYSTEM_DIR="${CTRL_ECOSYSTEM_DIR:-$CTRL_DATA_DIR/ecosystems}"
|
|
SHOW_ALL="${PM2_MANAGER_SHOW_ALL:-0}"
|
|
|
|
declare -a APP_NAMES=()
|
|
declare -a APP_DIRS=()
|
|
declare -a APP_ECOSYSTEMS=()
|
|
declare -a APP_SOURCES=()
|
|
declare -A SEEN_SERVICES=()
|
|
|
|
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"
|
|
}
|
|
|
|
extract_app_cwd() {
|
|
local ecosystem_file="$1"
|
|
local cwd
|
|
cwd="$(grep -E 'cwd:[[:space:]]*"[^"]+"' "$ecosystem_file" | head -n1 | sed -E 's/.*cwd:[[:space:]]*"([^"]+)".*/\1/' || true)"
|
|
cwd="$(trim "$cwd")"
|
|
if [ -z "$cwd" ]; then
|
|
cwd="$(dirname "$ecosystem_file")"
|
|
fi
|
|
printf "%s" "$cwd"
|
|
}
|
|
|
|
add_ecosystem() {
|
|
local file="$1"
|
|
local source="$2"
|
|
local app_name project_dir key
|
|
|
|
app_name="$(extract_app_name "$file")"
|
|
project_dir="$(extract_app_cwd "$file")"
|
|
key="${app_name}|${project_dir}"
|
|
|
|
if [ "$SHOW_ALL" != "1" ] && [ -n "${SEEN_SERVICES[$key]:-}" ]; then
|
|
return
|
|
fi
|
|
|
|
SEEN_SERVICES[$key]=1
|
|
APP_NAMES+=("$app_name")
|
|
APP_DIRS+=("$project_dir")
|
|
APP_ECOSYSTEMS+=("$file")
|
|
APP_SOURCES+=("$source")
|
|
}
|
|
|
|
discover_ecosystems() {
|
|
local file
|
|
|
|
if [ -d "$CTRL_ECOSYSTEM_DIR" ]; then
|
|
while IFS= read -r file; do
|
|
add_ecosystem "$file" "ctrl"
|
|
done < <(
|
|
find "$CTRL_ECOSYSTEM_DIR" -maxdepth 1 -type f -name "*.config.cjs" | sort
|
|
)
|
|
fi
|
|
|
|
while IFS= read -r file; do
|
|
add_ecosystem "$file" "repo"
|
|
done < <(
|
|
find "$ROOT_DIR" -maxdepth 4 \
|
|
\( -path "$ROOT_DIR/gitea" -o -path "*/node_modules" -o -path "*/.next" -o -path "*/.git" \) -prune -o \
|
|
-type f \
|
|
\( -name "ecosystem.config.cjs" -o -name "ecosystem.cron.config.js" \) \
|
|
-print \
|
|
| sort
|
|
)
|
|
}
|
|
|
|
print_services() {
|
|
local i rel cwd_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
|
|
if [[ "${APP_ECOSYSTEMS[$i]}" == "$ROOT_DIR/"* ]]; then
|
|
rel="${APP_ECOSYSTEMS[$i]#"$ROOT_DIR/"}"
|
|
else
|
|
rel="$(basename "${APP_ECOSYSTEMS[$i]}")"
|
|
fi
|
|
|
|
if [[ "${APP_DIRS[$i]}" == "$ROOT_DIR/"* ]]; then
|
|
cwd_rel="${APP_DIRS[$i]#"$ROOT_DIR/"}"
|
|
else
|
|
cwd_rel="${APP_DIRS[$i]}"
|
|
fi
|
|
|
|
printf " %2d) %-16s %-4s %-24s %s\n" \
|
|
"$((i + 1))" "${APP_NAMES[$i]}" "${APP_SOURCES[$i]}" "$cwd_rel" "$rel"
|
|
done
|
|
|
|
if [ "$SHOW_ALL" != "1" ]; then
|
|
echo
|
|
echo "Showing preferred configs only. Use '$0 list-all' to include repo-local duplicates."
|
|
fi
|
|
}
|
|
|
|
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 <<EOF
|
|
Usage:
|
|
$0 # interactive mode
|
|
$0 list
|
|
$0 list-all
|
|
$0 <action> <service>
|
|
|
|
Actions:
|
|
recreate | reload | restart | delete
|
|
|
|
Service can be:
|
|
- numeric index from "list"
|
|
- app name (e.g. zap, zap-cleanup, caddy-manager, api)
|
|
|
|
Ctrl-managed configs are discovered from:
|
|
${CTRL_ECOSYSTEM_DIR}
|
|
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
|
|
|
|
if [ "${1:-}" = "list-all" ]; then
|
|
SHOW_ALL=1
|
|
fi
|
|
|
|
discover_ecosystems
|
|
|
|
if [ "${1:-}" = "list" ] || [ "${1:-}" = "list-all" ]; 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 "$@"
|