#!/usr/bin/env bash # APM — Tom's Advanced Process Manager • https://processmanager.dev set -euo pipefail VER="2.3.0" INSTALLER_VER="7" BASE="https://processmanager.dev/downloads/current" # ── Self-elevate ────────────────────────────────────────────────────────────── if [[ $EUID -ne 0 ]]; then if [[ -f "${BASH_SOURCE[0]:-}" ]]; then exec sudo bash "${BASH_SOURCE[0]}" "$@" else exec sudo bash -c 'curl -fsSL https://processmanager.dev/install.sh | bash' fi fi # ── Colours ────────────────────────────────────────────────────────────────── if [[ -t 1 ]]; then R='\033[0;31m' G='\033[0;32m' Y='\033[1;33m' C='\033[0;36m' B='\033[1m' D='\033[2m' N='\033[0m' else R='' G='' Y='' C='' B='' D='' N='' fi OK="${G} ✓${N}" ERR="${R} ✗${N}" INFO="${C} →${N}" WARN="${Y} ⚠${N}" SEP="${D} ─────────────────────────────────────────${N}" # ── Header ─────────────────────────────────────────────────────────────────── echo printf "${B}${C} APM v%s${N} ${D}Tom's Advanced Process Manager${N}\n" "$VER" printf " ${D}installer r%s${N}\n" "$INSTALLER_VER" echo -e "$SEP" echo # ── Paths ──────────────────────────────────────────────────────────────────── INSTALL_PATH="/usr/sbin/apm" PID_FILE="/root/.apm/apm.pid" LOG_FILE="/var/log/apm.log" ETC_CONF="/etc/apm/apm.conf" IPS_DIR="/etc/apm/ips" # ── Architecture ───────────────────────────────────────────────────────────── case "$(uname -m)" in x86_64) SUFFIX="x64" ;; aarch64|arm64) SUFFIX="arm64" ;; armv7l) SUFFIX="armhf" ;; *) echo -e "${ERR} Unsupported architecture: $(uname -m)" exit 1 ;; esac # ── Download binary ─────────────────────────────────────────────────────────── FILE="apm-v${VER}-${SUFFIX}-upx" URL="${BASE}/${FILE}" TMP="$(mktemp /tmp/apm-XXXXXX)" trap 'rm -f "$TMP"' EXIT echo -e "${INFO} Downloading ${D}$URL${N}" if command -v curl &>/dev/null; then curl -fsSL --progress-bar "$URL" -o "$TMP" \ || { echo -e "${ERR} Download failed"; exit 1; } elif command -v wget &>/dev/null; then wget -q --show-progress "$URL" -O "$TMP" \ || { echo -e "${ERR} Download failed"; exit 1; } else echo -e "${ERR} curl or wget not found" exit 1 fi chmod +x "$TMP" echo -e "${OK} Downloaded" # ── Stop running daemon ─────────────────────────────────────────────────────── # Stop systemd first so it won't restart the process while we're killing it if command -v systemctl &>/dev/null && systemctl is-active --quiet apm 2>/dev/null; then echo -e "${INFO} Stopping systemd service..." systemctl stop apm 2>/dev/null || true fi # Graceful socket-based shutdown via the existing apm binary (if any). # Works regardless of PID file location and who owns the daemon. This is # the reliable path; the PID-file kill below is a fallback for cases # where the abstract socket isn't reachable (broken binary, daemon hung). APM_BIN="" for candidate in "$INSTALL_PATH" /usr/local/bin/apm "$(command -v apm 2>/dev/null || true)"; do if [[ -n "$candidate" && -x "$candidate" ]]; then APM_BIN="$candidate" break fi done if [[ -n "$APM_BIN" ]]; then timeout 6 "$APM_BIN" exit daemon &>/dev/null || true sleep 0.3 fi # Kill via PID file (fallback) if [[ -f "$PID_FILE" ]]; then PID="$(cat "$PID_FILE" 2>/dev/null || true)" if [[ -n "$PID" ]] && kill -0 "$PID" 2>/dev/null; then echo -e "${INFO} Killing daemon ${D}(pid $PID)${N}" kill -9 "$PID" 2>/dev/null || true sleep 0.3 fi fi # Fallback: kill any remaining apm --daemon process (catches non-root # daemons whose PID file isn't at /root/.apm/apm.pid) pkill -9 -f "apm --daemon" 2>/dev/null || true sleep 0.3 echo -e "${OK} Daemon stopped" # ── Install binary ──────────────────────────────────────────────────────────── echo -e "${INFO} Installing ${D}→ $INSTALL_PATH${N}" rm -f "$INSTALL_PATH" install -m 0755 "$TMP" "$INSTALL_PATH" echo -e "${OK} Binary installed" # ── PATH shim ──────────────────────────────────────────────────────────────── # On Debian/Ubuntu /usr/sbin is not in the default non-root PATH, so `apm` is # unreachable for regular users. Detect that and symlink into /usr/local/bin. _sbin_in_user_path() { local user_path="" if [[ -r /etc/login.defs ]]; then user_path="$(awk '$1 == "ENV_PATH" { sub(/^PATH=/, "", $2); print $2; exit }' /etc/login.defs)" fi [[ -z "$user_path" ]] && user_path="/usr/local/bin:/usr/bin:/bin" [[ ":$user_path:" == *":/usr/sbin:"* ]] } SHIM_PATH="/usr/local/bin/apm" if _sbin_in_user_path; then # /usr/sbin is in the user PATH — clean up any stale shim from a prior install if [[ -L "$SHIM_PATH" && "$(readlink "$SHIM_PATH")" == "$INSTALL_PATH" ]]; then rm -f "$SHIM_PATH" fi echo -e "${INFO} ${D}/usr/sbin in user PATH — no shim needed${N}" else if [[ -d /usr/local/bin ]]; then if [[ -e "$SHIM_PATH" && ! -L "$SHIM_PATH" ]]; then echo -e "${WARN} $SHIM_PATH exists and is not a symlink — leaving alone" else ln -sf "$INSTALL_PATH" "$SHIM_PATH" echo -e "${OK} Symlinked ${B}$SHIM_PATH${N} ${D}→ $INSTALL_PATH (/usr/sbin not in user PATH)${N}" fi else echo -e "${WARN} /usr/sbin not in user PATH and /usr/local/bin missing — ${B}apm${N} may not be reachable as a non-root user" fi fi # ── System group ───────────────────────────────────────────────────────────── _group_exists() { getent group "$1" &>/dev/null || grep -q "^$1:" /etc/group 2>/dev/null } _group_create() { if command -v groupadd &>/dev/null; then groupadd --system "$1" elif command -v addgroup &>/dev/null; then addgroup -S "$1" else echo -e "${WARN} Cannot create group — neither groupadd nor addgroup found" fi } _user_add_group() { if command -v usermod &>/dev/null; then usermod -aG "$1" "$2" elif command -v adduser &>/dev/null; then adduser "$2" "$1" else echo -e "${WARN} Cannot add user to group — neither usermod nor adduser found" fi } if ! _group_exists apm; then _group_create apm echo -e "${OK} Created group ${B}apm${N}" else echo -e "${INFO} Group ${B}apm${N} already exists" fi if [[ -n "${SUDO_USER:-}" ]]; then _user_add_group apm "$SUDO_USER" echo -e "${OK} Added ${B}$SUDO_USER${N} to ${B}apm${N} ${D}(re-login required)${N}" fi # ── Log file ────────────────────────────────────────────────────────────────── touch "$LOG_FILE" 2>/dev/null || true chown root:apm "$LOG_FILE" 2>/dev/null || true chmod 0664 "$LOG_FILE" 2>/dev/null || true echo -e "${OK} Log ${D}$LOG_FILE${N}" # ── Config ──────────────────────────────────────────────────────────────────── mkdir -p /etc/apm/apm.conf.d "$IPS_DIR" if [[ ! -f "$ETC_CONF" ]]; then cat > "$ETC_CONF" << 'CONF' # APM — Tom's Advanced Process Manager • /etc/apm/apm.conf # # Drop extra worker configs into apm.conf.d/ — loaded automatically on start. include apm.conf.d/*; # daemon { # gui_port 6789; # gui_bind 127.0.0.1; # gui_password secret; # telemetry true; # } # worker { # name myapp; # exec node; # params /opt/myapp/server.js; # path /opt/myapp; # restart true; # log /var/log/myapp.log; # } CONF echo -e "${OK} Config created ${D}$ETC_CONF${N}" else echo -e "${INFO} Config exists — not overwritten ${D}$ETC_CONF${N}" fi # ── Cloudflare IPs (Vanguard WAF) ───────────────────────────────────────────── echo -e "${INFO} Fetching Cloudflare IP lists..." _fetch() { local body if command -v curl &>/dev/null; then body="$(curl -fsSL --connect-timeout 5 "$1" 2>/dev/null)" || { echo -e "${WARN} Could not fetch $1"; return; } else body="$(wget -qO- --timeout=5 "$1" 2>/dev/null)" || { echo -e "${WARN} Could not fetch $1"; return; } fi printf '%s\n' "$body" | awk 'NF { print "allow_ip: " $1 ";" }' > "$2" } _fetch "https://www.cloudflare.com/ips-v4" "$IPS_DIR/cloudflare-v4.part" _fetch "https://www.cloudflare.com/ips-v6" "$IPS_DIR/cloudflare-v6.part" echo -e "${OK} IP lists updated" # ── Init system ─────────────────────────────────────────────────────────────── echo -e "${INFO} Detecting init system..." if command -v systemctl &>/dev/null; then cat > /etc/systemd/system/apm.service << 'UNIT' [Unit] Description=APM - Tom's Advanced Process Manager After=network.target [Service] Type=simple ExecStart=/usr/sbin/apm --daemon ExecStop=/usr/sbin/apm exit daemon Environment=TPMRUN=AtlasMode Environment=HOME=/root Restart=on-failure TimeoutStopSec=30 [Install] WantedBy=multi-user.target UNIT systemctl daemon-reload systemctl enable apm 2>/dev/null || true systemctl reset-failed apm 2>/dev/null || true systemctl restart apm 2>/dev/null || true sleep 1 if systemctl is-active --quiet apm 2>/dev/null; then echo -e "${OK} systemd service enabled and started" else echo -e "${WARN} systemd start failed — starting daemon directly" TPMRUN=AtlasMode "$INSTALL_PATH" --daemon & disown sleep 0.5 echo -e "${OK} Daemon started" fi elif command -v rc-update &>/dev/null; then cat > /etc/init.d/apm << 'OPENRC' #!/sbin/openrc-run description="APM - Tom's Advanced Process Manager" command="/usr/sbin/apm" command_args="--daemon" command_background=true pidfile="/root/.apm/apm.pid" export TPMRUN=AtlasMode export HOME=/root OPENRC chmod 0755 /etc/init.d/apm rc-update add apm default 2>/dev/null || true rc-service apm start 2>/dev/null || true echo -e "${OK} OpenRC service enabled and started" elif [[ -d /etc/init.d ]]; then cat > /etc/init.d/apm << 'SYSV' #!/bin/sh ### BEGIN INIT INFO # Provides: apm # Required-Start: $remote_fs $syslog $network # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: APM - Tom's Advanced Process Manager ### END INIT INFO APM=/usr/sbin/apm PIDFILE=/root/.apm/apm.pid case "$1" in start) HOME=/root TPMRUN=AtlasMode "$APM" --daemon & ;; stop) [ -f "$PIDFILE" ] && kill "$(cat "$PIDFILE")" ;; restart) $0 stop; sleep 1; $0 start ;; *) echo "Usage: $0 {start|stop|restart}"; exit 1 ;; esac SYSV chmod 0755 /etc/init.d/apm command -v update-rc.d &>/dev/null && update-rc.d apm defaults 2>/dev/null || true command -v chkconfig &>/dev/null && chkconfig apm on 2>/dev/null || true /etc/init.d/apm start 2>/dev/null || true echo -e "${OK} SysV init script enabled and started" else echo -e "${WARN} No init system — starting daemon manually" HOME=/root TPMRUN=AtlasMode "$INSTALL_PATH" --daemon & disown sleep 0.3 fi # ── Done ───────────────────────────────────────────────────────────────────── echo echo -e "$SEP" echo -e "${OK} ${B}Installation complete${N}" echo -e "$SEP" echo -e " ${D}Binary${N} $INSTALL_PATH" echo -e " ${D}Config${N} $ETC_CONF" echo -e " ${D}Log ${N} $LOG_FILE" echo echo -e " ${C}→${N} Edit config ${B}nano $ETC_CONF${N}" echo -e " ${C}→${N} List workers ${B}apm list${N}" echo -e " ${C}→${N} Web GUI ${B}apm gui${N}" echo