Repository files, collaboration, releases and project activity.
ARCHFORGE HISTORY
Blame · install.sh
Branch main · the Commit author is not necessarily a verified ArchForge account.
e59b7f7cc6archadmin2026-10-11
1 #!/usr/bin/env bashe59b7f7cc6archadmin2026-10-11
2 # ArchForge External Status — Debian 13 standalone installer.2109dd33b8archadmin2026-10-12
3 # Run from your ArchForge checkout: sudo ./install.she59b7f7cc6archadmin2026-10-11
4 set -Eeuo pipefaile59b7f7cc6archadmin2026-10-11
5 umask 027e59b7f7cc6archadmin2026-10-11
6 abort(){ echo "[ERROR] $*" >&2;exit 1; }e59b7f7cc6archadmin2026-10-11
7 [[ $EUID -eq 0 ]] || abort 'Run sudo ./install.sh'e59b7f7cc6archadmin2026-10-11
8 [[ -f /etc/os-release ]] || abort 'Cannot determine operating system'e59b7f7cc6archadmin2026-10-11
9 source /etc/os-releasee59b7f7cc6archadmin2026-10-11
10 [[ ${ID:-} == debian && ${VERSION_ID:-} == 13 ]] || abort 'Only Debian 13 (trixie) is supported by the automatic installer'e59b7f7cc6archadmin2026-10-11
11 [[ -t 0 ]] || abort 'Use an interactive terminal to run the setup wizard'e59b7f7cc6archadmin2026-10-11
12 SRC=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)e59b7f7cc6archadmin2026-10-11
13 [[ -f "$SRC/package.json" && -f "$SRC/src/server.js" ]] || abort 'Run installer from the project root'2109dd33b8archadmin2026-10-12
14 [[ "$SRC" != /opt/archforge-status/app ]] || abort 'Run from the ArchForge checkout, not installed app directory'f1897d33c0archadmin2026-10-11
15 if [[ -f /etc/systemd/system/archforge-status.service ]] && systemctl is-active --quiet archforge-status; thenf1897d33c0archadmin2026-10-11
16 echo '[*] Existing service detected. Checking/recovering admin setup only; no reinstall.'f1897d33c0archadmin2026-10-11
17 exec bash "$SRC/recover-admin.sh"f1897d33c0archadmin2026-10-11
18 fi2109dd33b8archadmin2026-10-12
19 printf '\n══════════════════════════════════════════════\n ArchForge External Status 1.0.3 — Debian 13\n Independent ArchForge-installed status monitoring\n══════════════════════════════════════════════\n\n'e59b7f7cc6archadmin2026-10-11
20 echo 'This installer installs Node.js 24, Caddy, SQLite, msmtp, and a systemd service.'e59b7f7cc6archadmin2026-10-11
21 echo 'The existing archforge.nl website and MySQL database will NOT be changed.'e59b7f7cc6archadmin2026-10-11
22 read -rp 'Continue? Type INSTALL: ' OKe59b7f7cc6archadmin2026-10-11
23 [[ "$OK" == INSTALL ]] || abort 'Cancelled'e59b7f7cc6archadmin2026-10-11
24 export DEBIAN_FRONTEND=noninteractivee59b7f7cc6archadmin2026-10-11
25 apt-get updatee59b7f7cc6archadmin2026-10-11
26 apt-get install -y ca-certificates curl gnupg git rsync sqlite3 caddy msmtp python3e59b7f7cc6archadmin2026-10-11
27 NODE_MAJOR=$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || echo 0)e59b7f7cc6archadmin2026-10-11
28 if (( NODE_MAJOR < 24 )); thene59b7f7cc6archadmin2026-10-11
29 echo '[*] Installing Node.js 24 from the signed NodeSource apt repository'e59b7f7cc6archadmin2026-10-11
30 install -d -m 755 /etc/apt/keyringse59b7f7cc6archadmin2026-10-11
31 curl --fail --silent --show-error --location https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpge59b7f7cc6archadmin2026-10-11
32 chmod 644 /etc/apt/keyrings/nodesource.gpge59b7f7cc6archadmin2026-10-11
33 ARCH=$(dpkg --print-architecture)e59b7f7cc6archadmin2026-10-11
34 printf 'deb [arch=%s signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main\n' "$ARCH" > /etc/apt/sources.list.d/nodesource.liste59b7f7cc6archadmin2026-10-11
35 apt-get updatee59b7f7cc6archadmin2026-10-11
36 apt-get install -y nodejse59b7f7cc6archadmin2026-10-11
37 fie59b7f7cc6archadmin2026-10-11
38 NODE_MAJOR=$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || echo 0)e59b7f7cc6archadmin2026-10-11
39 (( NODE_MAJOR >= 24 )) || abort 'Node.js 24+ is required'e59b7f7cc6archadmin2026-10-11
40 command -v caddy >/dev/null || abort 'Caddy installation failed'e59b7f7cc6archadmin2026-10-11
41 command -v sqlite3 >/dev/null || abort 'SQLite installation failed'e59b7f7cc6archadmin2026-10-11
42 command -v msmtp >/dev/null || abort 'msmtp installation failed'e59b7f7cc6archadmin2026-10-11
43 if ! id -u archforge-status >/dev/null 2>&1; thene59b7f7cc6archadmin2026-10-11
44 useradd --system --user-group --home-dir /var/lib/archforge-status --shell /usr/sbin/nologin archforge-statuse59b7f7cc6archadmin2026-10-11
45 fie59b7f7cc6archadmin2026-10-11
46 install -d -m 755 /opt/archforge-status /opt/archforge-status/appe59b7f7cc6archadmin2026-10-11
47 install -d -o archforge-status -g archforge-status -m 750 /var/lib/archforge-statuse59b7f7cc6archadmin2026-10-11
48 install -d -m 750 /etc/archforge-statuse59b7f7cc6archadmin2026-10-11
49 chown root:archforge-status /etc/archforge-statuse59b7f7cc6archadmin2026-10-11
50 install -d -m 700 /var/backups/archforge-statuse59b7f7cc6archadmin2026-10-11
51 rsync -a --delete --exclude='.git' --exclude='.env' --exclude='data' --exclude='node_modules' --exclude='backups' --chmod=D755,F644 "$SRC/" /opt/archforge-status/app/e59b7f7cc6archadmin2026-10-11
52 chown -R root:root /opt/archforge-status/appe59b7f7cc6archadmin2026-10-11
53 echo '[*] Running package validation before service installation'e59b7f7cc6archadmin2026-10-11
54 (cd /opt/archforge-status/app && /usr/bin/node --check src/server.js && /usr/bin/node --test tests/*.test.js) || abort 'Application self-tests failed; installation halted'e59b7f7cc6archadmin2026-10-11
55 chmod 755 /opt/archforge-status /opt/archforge-status/appe59b7f7cc6archadmin2026-10-11
56 printf '%s\n' "$SRC" > /etc/archforge-status/source-pathe59b7f7cc6archadmin2026-10-11
57 chmod 600 /etc/archforge-status/source-pathe59b7f7cc6archadmin2026-10-11
58 CFG=/etc/archforge-status/status.enve59b7f7cc6archadmin2026-10-11
59 if [[ -f "$CFG" ]]; thene59b7f7cc6archadmin2026-10-11
60 echo '[*] Existing configuration preserved (no secret rotation)'e59b7f7cc6archadmin2026-10-11
61 elsee59b7f7cc6archadmin2026-10-11
62 echoe59b7f7cc6archadmin2026-10-11
63 echo '--- Domain and monitored targets ---'e59b7f7cc6archadmin2026-10-11
64 read -rp 'Public status domain [status.archforge.nl]: ' DOMAIN; DOMAIN=${DOMAIN:-status.archforge.nl}e59b7f7cc6archadmin2026-10-11
65 [[ "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]{0,251}[a-zA-Z0-9])?$ && "$DOMAIN" == *.* ]] || abort 'Invalid domain'e59b7f7cc6archadmin2026-10-11
66 read -rp 'Main website HTTPS URL [https://archforge.nl]: ' WEBSITE; WEBSITE=${WEBSITE:-https://archforge.nl}e59b7f7cc6archadmin2026-10-11
67 [[ "$WEBSITE" =~ ^https://[a-zA-Z0-9.-]+/?$ ]] || abort 'Use https://example.com for website'e59b7f7cc6archadmin2026-10-11
68 read -rp 'Optional public API URL to monitor (Enter to disable): ' API2109dd33b8archadmin2026-10-12
69 read -rp 'Public ArchForge HTTPS URL ending /info/refs (leave empty to disable): ' GIT_HTTP2109dd33b8archadmin2026-10-12
70 read -rp 'ArchForge SSH host [archforge.nl]: ' SSH_HOST; SSH_HOST=${SSH_HOST:-archforge.nl}2109dd33b8archadmin2026-10-12
71 [[ "$SSH_HOST" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid ArchForge SSH host'2109dd33b8archadmin2026-10-12
72 read -rp 'ArchForge SSH port [22]: ' SSH_PORT; SSH_PORT=${SSH_PORT:-22}e59b7f7cc6archadmin2026-10-11
73 [[ "$SSH_PORT" =~ ^[0-9]{1,5}$ && "$SSH_PORT" -ge 1 && "$SSH_PORT" -le 65535 ]] || abort 'Invalid SSH port'e59b7f7cc6archadmin2026-10-11
74 echoe59b7f7cc6archadmin2026-10-11
75 echo '--- Notifications (optional, configurable later) ---'e59b7f7cc6archadmin2026-10-11
76 read -rp 'Discord webhook URL (Enter to skip): ' DISCORDe59b7f7cc6archadmin2026-10-11
77 [[ -z "$DISCORD" || "$DISCORD" =~ ^https://(discord.com|discordapp.com)/api/webhooks/ ]] || abort 'Discord URL must be an official webhook URL'e59b7f7cc6archadmin2026-10-11
78 read -rp 'SMTP server hostname (Enter to disable email): ' SMTP_HOSTe59b7f7cc6archadmin2026-10-11
79 SMTP_PORT=587 SMTP_USER='' SMTP_PASS='' SMTP_FROM=''e59b7f7cc6archadmin2026-10-11
80 if [[ -n "$SMTP_HOST" ]]; thene59b7f7cc6archadmin2026-10-11
81 [[ "$SMTP_HOST" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid SMTP host'e59b7f7cc6archadmin2026-10-11
82 read -rp 'SMTP port [587]: ' SMTP_PORT; SMTP_PORT=${SMTP_PORT:-587}e59b7f7cc6archadmin2026-10-11
83 [[ "$SMTP_PORT" =~ ^[0-9]{1,5}$ && "$SMTP_PORT" -ge 1 && "$SMTP_PORT" -le 65535 ]] || abort 'Invalid SMTP port'e59b7f7cc6archadmin2026-10-11
84 read -rp 'SMTP username: ' SMTP_USERe59b7f7cc6archadmin2026-10-11
85 read -rsp 'SMTP password (hidden): ' SMTP_PASS;printf '\n'e59b7f7cc6archadmin2026-10-11
86 read -rp 'From email (e.g. status@archforge.nl): ' SMTP_FROMe59b7f7cc6archadmin2026-10-11
87 [[ "$SMTP_FROM" == *@* && "$SMTP_FROM" != *$'\n'* ]] || abort 'Invalid sender address'e59b7f7cc6archadmin2026-10-11
88 fie59b7f7cc6archadmin2026-10-11
89 KEY=$(od -An -tx1 -N32 /dev/urandom | tr -d ' \n')e59b7f7cc6archadmin2026-10-11
90 # Never source this file as shell. Values are read as literal NAME=VALUE pairs by Node.e59b7f7cc6archadmin2026-10-11
91 (umask 077; cat > "$CFG" <<ENVFILEe59b7f7cc6archadmin2026-10-11
92 NODE_ENV=productione59b7f7cc6archadmin2026-10-11
93 PORT=3000e59b7f7cc6archadmin2026-10-11
94 STATUS_LISTEN_HOST=127.0.0.1e59b7f7cc6archadmin2026-10-11
95 STATUS_PUBLIC_URL=https://${DOMAIN}e59b7f7cc6archadmin2026-10-11
96 TARGET_WEBSITE=${WEBSITE}e59b7f7cc6archadmin2026-10-11
97 TARGET_API_URL=${API}e59b7f7cc6archadmin2026-10-11
98 TARGET_GIT_HTTP_URL=${GIT_HTTP}e59b7f7cc6archadmin2026-10-11
99 TARGET_GIT_SSH_HOST=${SSH_HOST}e59b7f7cc6archadmin2026-10-11
100 TARGET_GIT_SSH_PORT=${SSH_PORT}e59b7f7cc6archadmin2026-10-11
101 MONITOR_INTERVAL_SECONDS=60e59b7f7cc6archadmin2026-10-11
102 MONITOR_FAILURE_THRESHOLD=3e59b7f7cc6archadmin2026-10-11
103 MONITOR_RECOVERY_THRESHOLD=2e59b7f7cc6archadmin2026-10-11
104 MONITOR_TIMEOUT_MS=8000e59b7f7cc6archadmin2026-10-11
105 STATUS_DISCORD_WEBHOOK_URL=${DISCORD}e59b7f7cc6archadmin2026-10-11
106 SMTP_HOST=${SMTP_HOST}e59b7f7cc6archadmin2026-10-11
107 SMTP_PORT=${SMTP_PORT}e59b7f7cc6archadmin2026-10-11
108 SMTP_USER=${SMTP_USER}e59b7f7cc6archadmin2026-10-11
109 SMTP_PASSWORD=${SMTP_PASS}e59b7f7cc6archadmin2026-10-11
110 SMTP_FROM=${SMTP_FROM}e59b7f7cc6archadmin2026-10-11
111 SMTP_TLS=one59b7f7cc6archadmin2026-10-11
112 STATUS_ENCRYPTION_KEY=${KEY}e59b7f7cc6archadmin2026-10-11
113 STATUS_DB_PATH=/var/lib/archforge-status/status.dbe59b7f7cc6archadmin2026-10-11
114 ENVFILEe59b7f7cc6archadmin2026-10-11
115 )e59b7f7cc6archadmin2026-10-11
116 chown root:archforge-status "$CFG"e59b7f7cc6archadmin2026-10-11
117 chmod 640 "$CFG"e59b7f7cc6archadmin2026-10-11
118 unset KEY SMTP_PASSe59b7f7cc6archadmin2026-10-11
119 fi2109dd33b8archadmin2026-10-12
120 # Generate msmtp config from status.env only if enabled. Secrets never go into ArchForge.e59b7f7cc6archadmin2026-10-11
121 # Read settings without sourcing the dotenv file.e59b7f7cc6archadmin2026-10-11
122 get_conf(){ grep -m1 "^${1}=" "$CFG" | cut -d= -f2- || true; }e59b7f7cc6archadmin2026-10-11
123 SMTP_HOST_VALUE=$(get_conf SMTP_HOST)e59b7f7cc6archadmin2026-10-11
124 if [[ -n "$SMTP_HOST_VALUE" ]]; thene59b7f7cc6archadmin2026-10-11
125 smtp_quote(){ local x=$1;x=${x//\\/\\\\};x=${x//\"/\\\"};printf '"%s"' "$x"; }e59b7f7cc6archadmin2026-10-11
126 SMTP_P=$(get_conf SMTP_PORT);SMTP_U=$(get_conf SMTP_USER);SMTP_W=$(get_conf SMTP_PASSWORD);SMTP_F=$(get_conf SMTP_FROM)e59b7f7cc6archadmin2026-10-11
127 [[ "$SMTP_P" =~ ^[0-9]+$ ]] || abort 'Invalid SMTP port'e59b7f7cc6archadmin2026-10-11
128 {e59b7f7cc6archadmin2026-10-11
129 echo 'defaults';echo 'auth on';echo 'tls on';echo 'tls_starttls on';echo 'tls_trust_file /etc/ssl/certs/ca-certificates.crt';echo 'account status'e59b7f7cc6archadmin2026-10-11
130 printf 'host %s\n' "$(smtp_quote "$SMTP_HOST_VALUE")"e59b7f7cc6archadmin2026-10-11
131 printf 'port %s\n' "$SMTP_P"e59b7f7cc6archadmin2026-10-11
132 printf 'from %s\n' "$(smtp_quote "$SMTP_F")"e59b7f7cc6archadmin2026-10-11
133 printf 'user %s\n' "$(smtp_quote "$SMTP_U")"e59b7f7cc6archadmin2026-10-11
134 printf 'password %s\n' "$(smtp_quote "$SMTP_W")"e59b7f7cc6archadmin2026-10-11
135 echo 'account default : status'e59b7f7cc6archadmin2026-10-11
136 } >/etc/archforge-status/msmtprce59b7f7cc6archadmin2026-10-11
137 chown archforge-status:archforge-status /etc/archforge-status/msmtprce59b7f7cc6archadmin2026-10-11
138 chmod 600 /etc/archforge-status/msmtprce59b7f7cc6archadmin2026-10-11
139 unset SMTP_We59b7f7cc6archadmin2026-10-11
140 elsee59b7f7cc6archadmin2026-10-11
141 echo '[*] SMTP not configured. Email subscriptions will be disabled until configured.'e59b7f7cc6archadmin2026-10-11
142 fie59b7f7cc6archadmin2026-10-11
143 install -m 644 "$SRC/scripts/archforge-status.service" /etc/systemd/system/archforge-status.servicee59b7f7cc6archadmin2026-10-11
144 install -m 644 "$SRC/scripts/archforge-status-backup.service" /etc/systemd/system/archforge-status-backup.servicee59b7f7cc6archadmin2026-10-11
145 install -m 644 "$SRC/scripts/archforge-status-backup.timer" /etc/systemd/system/archforge-status-backup.timere59b7f7cc6archadmin2026-10-11
146 install -m 755 "$SRC/scripts/backup.sh" /usr/local/sbin/archforge-status-backupe59b7f7cc6archadmin2026-10-11
147 DOMAIN=$(get_conf STATUS_PUBLIC_URL);DOMAIN=${DOMAIN#https://};DOMAIN=${DOMAIN%%/*}e59b7f7cc6archadmin2026-10-11
148 [[ "$DOMAIN" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid domain in saved config'e59b7f7cc6archadmin2026-10-11
149 mkdir -p /etc/caddy/conf.de59b7f7cc6archadmin2026-10-11
150 cat > /etc/caddy/conf.d/archforge-status.caddy <<CADDYFILEe59b7f7cc6archadmin2026-10-11
151 ${DOMAIN} {e59b7f7cc6archadmin2026-10-11
152 encode zstd gzipe59b7f7cc6archadmin2026-10-11
153 reverse_proxy 127.0.0.1:3000e59b7f7cc6archadmin2026-10-11
154 }e59b7f7cc6archadmin2026-10-11
155 CADDYFILEe59b7f7cc6archadmin2026-10-11
156 if ! grep -Fq 'import /etc/caddy/conf.d/*.caddy' /etc/caddy/Caddyfile; thene59b7f7cc6archadmin2026-10-11
157 cp /etc/caddy/Caddyfile "/etc/caddy/Caddyfile.backup.$(date +%s)"e59b7f7cc6archadmin2026-10-11
158 printf '\nimport /etc/caddy/conf.d/*.caddy\n' >> /etc/caddy/Caddyfilee59b7f7cc6archadmin2026-10-11
159 fie59b7f7cc6archadmin2026-10-11
160 caddy validate --config /etc/caddy/Caddyfile || abort 'Caddy configuration is invalid; inspect /etc/caddy/Caddyfile and its backup'e59b7f7cc6archadmin2026-10-11
161 systemctl daemon-reloade59b7f7cc6archadmin2026-10-11
162 # Create database and initial 2FA admin before starting the daemon.e59b7f7cc6archadmin2026-10-11
163 echoe59b7f7cc6archadmin2026-10-11
164 printf '\n--- Admin account + 2FA ---\n'e59b7f7cc6archadmin2026-10-11
165 cd /opt/archforge-status/appe59b7f7cc6archadmin2026-10-11
166 runuser -u archforge-status -- env STATUS_ENV_FILE="$CFG" NODE_ENV=production /usr/bin/node src/cli.js create-admine59b7f7cc6archadmin2026-10-11
167 systemctl enable --now archforge-status.servicee59b7f7cc6archadmin2026-10-11
168 systemctl enable --now archforge-status-backup.timere59b7f7cc6archadmin2026-10-11
169 systemctl enable --now caddye59b7f7cc6archadmin2026-10-11
170 systemctl reload caddy || systemctl restart caddye59b7f7cc6archadmin2026-10-11
171 sleep 1e59b7f7cc6archadmin2026-10-11
172 systemctl is-active --quiet archforge-status || abort 'Service did not start: sudo journalctl -u archforge-status -n 50'e59b7f7cc6archadmin2026-10-11
173 echoe59b7f7cc6archadmin2026-10-11
174 printf '══════════════════════════════════════════════\n Installation finished.\n Public status: https://%s\n Admin login: https://%s/admin/login\n Local health: http://127.0.0.1:3000/healthz\n Logs: sudo journalctl -u archforge-status -f\n══════════════════════════════════════════════\n' "$DOMAIN" "$DOMAIN"2109dd33b8archadmin2026-10-12
175 echo 'For a guided domain and SSL check, run: sudo bash ./configure-domain.sh from your ArchForge checkout.'