Repository files, collaboration, releases and project activity.
archforge-external-status / install.shmain
▤ install.sh10434 bytes
176 lines · Select a line number for a permalink
1
#!/usr/bin/env bash2
# ArchForge External Status — Debian 13 standalone installer.3
# Run from your ArchForge checkout: sudo ./install.sh4
set -Eeuo pipefail5
umask 0276
abort(){ echo "[ERROR] $*" >&2;exit 1; }7
[[ $EUID -eq 0 ]] || abort 'Run sudo ./install.sh'8
[[ -f /etc/os-release ]] || abort 'Cannot determine operating system'9
source /etc/os-release10
[[ ${ID:-} == debian && ${VERSION_ID:-} == 13 ]] || abort 'Only Debian 13 (trixie) is supported by the automatic installer'11
[[ -t 0 ]] || abort 'Use an interactive terminal to run the setup wizard'12
SRC=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)13
[[ -f "$SRC/package.json" && -f "$SRC/src/server.js" ]] || abort 'Run installer from the project root'14
[[ "$SRC" != /opt/archforge-status/app ]] || abort 'Run from the ArchForge checkout, not installed app directory'15
if [[ -f /etc/systemd/system/archforge-status.service ]] && systemctl is-active --quiet archforge-status; then16
echo '[*] Existing service detected. Checking/recovering admin setup only; no reinstall.'17
exec bash "$SRC/recover-admin.sh"18
fi19
printf '\n══════════════════════════════════════════════\n ArchForge External Status 1.0.3 — Debian 13\n Independent ArchForge-installed status monitoring\n══════════════════════════════════════════════\n\n'20
echo 'This installer installs Node.js 24, Caddy, SQLite, msmtp, and a systemd service.'21
echo 'The existing archforge.nl website and MySQL database will NOT be changed.'22
read -rp 'Continue? Type INSTALL: ' OK23
[[ "$OK" == INSTALL ]] || abort 'Cancelled'24
export DEBIAN_FRONTEND=noninteractive25
apt-get update26
apt-get install -y ca-certificates curl gnupg git rsync sqlite3 caddy msmtp python327
NODE_MAJOR=$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || echo 0)28
if (( NODE_MAJOR < 24 )); then29
echo '[*] Installing Node.js 24 from the signed NodeSource apt repository'30
install -d -m 755 /etc/apt/keyrings31
curl --fail --silent --show-error --location https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg32
chmod 644 /etc/apt/keyrings/nodesource.gpg33
ARCH=$(dpkg --print-architecture)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.list35
apt-get update36
apt-get install -y nodejs37
fi38
NODE_MAJOR=$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || echo 0)39
(( NODE_MAJOR >= 24 )) || abort 'Node.js 24+ is required'40
command -v caddy >/dev/null || abort 'Caddy installation failed'41
command -v sqlite3 >/dev/null || abort 'SQLite installation failed'42
command -v msmtp >/dev/null || abort 'msmtp installation failed'43
if ! id -u archforge-status >/dev/null 2>&1; then44
useradd --system --user-group --home-dir /var/lib/archforge-status --shell /usr/sbin/nologin archforge-status45
fi46
install -d -m 755 /opt/archforge-status /opt/archforge-status/app47
install -d -o archforge-status -g archforge-status -m 750 /var/lib/archforge-status48
install -d -m 750 /etc/archforge-status49
chown root:archforge-status /etc/archforge-status50
install -d -m 700 /var/backups/archforge-status51
rsync -a --delete --exclude='.git' --exclude='.env' --exclude='data' --exclude='node_modules' --exclude='backups' --chmod=D755,F644 "$SRC/" /opt/archforge-status/app/52
chown -R root:root /opt/archforge-status/app53
echo '[*] Running package validation before service installation'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'55
chmod 755 /opt/archforge-status /opt/archforge-status/app56
printf '%s\n' "$SRC" > /etc/archforge-status/source-path57
chmod 600 /etc/archforge-status/source-path58
CFG=/etc/archforge-status/status.env59
if [[ -f "$CFG" ]]; then60
echo '[*] Existing configuration preserved (no secret rotation)'61
else62
echo63
echo '--- Domain and monitored targets ---'64
read -rp 'Public status domain [status.archforge.nl]: ' DOMAIN; DOMAIN=${DOMAIN:-status.archforge.nl}65
[[ "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]{0,251}[a-zA-Z0-9])?$ && "$DOMAIN" == *.* ]] || abort 'Invalid domain'66
read -rp 'Main website HTTPS URL [https://archforge.nl]: ' WEBSITE; WEBSITE=${WEBSITE:-https://archforge.nl}67
[[ "$WEBSITE" =~ ^https://[a-zA-Z0-9.-]+/?$ ]] || abort 'Use https://example.com for website'68
read -rp 'Optional public API URL to monitor (Enter to disable): ' API69
read -rp 'Public ArchForge HTTPS URL ending /info/refs (leave empty to disable): ' GIT_HTTP70
read -rp 'ArchForge SSH host [archforge.nl]: ' SSH_HOST; SSH_HOST=${SSH_HOST:-archforge.nl}71
[[ "$SSH_HOST" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid ArchForge SSH host'72
read -rp 'ArchForge SSH port [22]: ' SSH_PORT; SSH_PORT=${SSH_PORT:-22}73
[[ "$SSH_PORT" =~ ^[0-9]{1,5}$ && "$SSH_PORT" -ge 1 && "$SSH_PORT" -le 65535 ]] || abort 'Invalid SSH port'74
echo75
echo '--- Notifications (optional, configurable later) ---'76
read -rp 'Discord webhook URL (Enter to skip): ' DISCORD77
[[ -z "$DISCORD" || "$DISCORD" =~ ^https://(discord.com|discordapp.com)/api/webhooks/ ]] || abort 'Discord URL must be an official webhook URL'78
read -rp 'SMTP server hostname (Enter to disable email): ' SMTP_HOST79
SMTP_PORT=587 SMTP_USER='' SMTP_PASS='' SMTP_FROM=''80
if [[ -n "$SMTP_HOST" ]]; then81
[[ "$SMTP_HOST" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid SMTP host'82
read -rp 'SMTP port [587]: ' SMTP_PORT; SMTP_PORT=${SMTP_PORT:-587}83
[[ "$SMTP_PORT" =~ ^[0-9]{1,5}$ && "$SMTP_PORT" -ge 1 && "$SMTP_PORT" -le 65535 ]] || abort 'Invalid SMTP port'84
read -rp 'SMTP username: ' SMTP_USER85
read -rsp 'SMTP password (hidden): ' SMTP_PASS;printf '\n'86
read -rp 'From email (e.g. status@archforge.nl): ' SMTP_FROM87
[[ "$SMTP_FROM" == *@* && "$SMTP_FROM" != *$'\n'* ]] || abort 'Invalid sender address'88
fi89
KEY=$(od -An -tx1 -N32 /dev/urandom | tr -d ' \n')90
# Never source this file as shell. Values are read as literal NAME=VALUE pairs by Node.91
(umask 077; cat > "$CFG" <<ENVFILE92
NODE_ENV=production93
PORT=300094
STATUS_LISTEN_HOST=127.0.0.195
STATUS_PUBLIC_URL=https://${DOMAIN}96
TARGET_WEBSITE=${WEBSITE}97
TARGET_API_URL=${API}98
TARGET_GIT_HTTP_URL=${GIT_HTTP}99
TARGET_GIT_SSH_HOST=${SSH_HOST}100
TARGET_GIT_SSH_PORT=${SSH_PORT}101
MONITOR_INTERVAL_SECONDS=60102
MONITOR_FAILURE_THRESHOLD=3103
MONITOR_RECOVERY_THRESHOLD=2104
MONITOR_TIMEOUT_MS=8000105
STATUS_DISCORD_WEBHOOK_URL=${DISCORD}106
SMTP_HOST=${SMTP_HOST}107
SMTP_PORT=${SMTP_PORT}108
SMTP_USER=${SMTP_USER}109
SMTP_PASSWORD=${SMTP_PASS}110
SMTP_FROM=${SMTP_FROM}111
SMTP_TLS=on112
STATUS_ENCRYPTION_KEY=${KEY}113
STATUS_DB_PATH=/var/lib/archforge-status/status.db114
ENVFILE115
)116
chown root:archforge-status "$CFG"117
chmod 640 "$CFG"118
unset KEY SMTP_PASS119
fi120
# Generate msmtp config from status.env only if enabled. Secrets never go into ArchForge.121
# Read settings without sourcing the dotenv file.122
get_conf(){ grep -m1 "^${1}=" "$CFG" | cut -d= -f2- || true; }123
SMTP_HOST_VALUE=$(get_conf SMTP_HOST)124
if [[ -n "$SMTP_HOST_VALUE" ]]; then125
smtp_quote(){ local x=$1;x=${x//\\/\\\\};x=${x//\"/\\\"};printf '"%s"' "$x"; }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)127
[[ "$SMTP_P" =~ ^[0-9]+$ ]] || abort 'Invalid SMTP port'128
{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'130
printf 'host %s\n' "$(smtp_quote "$SMTP_HOST_VALUE")"131
printf 'port %s\n' "$SMTP_P"132
printf 'from %s\n' "$(smtp_quote "$SMTP_F")"133
printf 'user %s\n' "$(smtp_quote "$SMTP_U")"134
printf 'password %s\n' "$(smtp_quote "$SMTP_W")"135
echo 'account default : status'136
} >/etc/archforge-status/msmtprc137
chown archforge-status:archforge-status /etc/archforge-status/msmtprc138
chmod 600 /etc/archforge-status/msmtprc139
unset SMTP_W140
else141
echo '[*] SMTP not configured. Email subscriptions will be disabled until configured.'142
fi143
install -m 644 "$SRC/scripts/archforge-status.service" /etc/systemd/system/archforge-status.service144
install -m 644 "$SRC/scripts/archforge-status-backup.service" /etc/systemd/system/archforge-status-backup.service145
install -m 644 "$SRC/scripts/archforge-status-backup.timer" /etc/systemd/system/archforge-status-backup.timer146
install -m 755 "$SRC/scripts/backup.sh" /usr/local/sbin/archforge-status-backup147
DOMAIN=$(get_conf STATUS_PUBLIC_URL);DOMAIN=${DOMAIN#https://};DOMAIN=${DOMAIN%%/*}148
[[ "$DOMAIN" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid domain in saved config'149
mkdir -p /etc/caddy/conf.d150
cat > /etc/caddy/conf.d/archforge-status.caddy <<CADDYFILE151
${DOMAIN} {152
encode zstd gzip153
reverse_proxy 127.0.0.1:3000154
}155
CADDYFILE156
if ! grep -Fq 'import /etc/caddy/conf.d/*.caddy' /etc/caddy/Caddyfile; then157
cp /etc/caddy/Caddyfile "/etc/caddy/Caddyfile.backup.$(date +%s)"158
printf '\nimport /etc/caddy/conf.d/*.caddy\n' >> /etc/caddy/Caddyfile159
fi160
caddy validate --config /etc/caddy/Caddyfile || abort 'Caddy configuration is invalid; inspect /etc/caddy/Caddyfile and its backup'161
systemctl daemon-reload162
# Create database and initial 2FA admin before starting the daemon.163
echo164
printf '\n--- Admin account + 2FA ---\n'165
cd /opt/archforge-status/app166
runuser -u archforge-status -- env STATUS_ENV_FILE="$CFG" NODE_ENV=production /usr/bin/node src/cli.js create-admin167
systemctl enable --now archforge-status.service168
systemctl enable --now archforge-status-backup.timer169
systemctl enable --now caddy170
systemctl reload caddy || systemctl restart caddy171
sleep 1172
systemctl is-active --quiet archforge-status || abort 'Service did not start: sudo journalctl -u archforge-status -n 50'173
echo174
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"175
echo 'For a guided domain and SSL check, run: sudo bash ./configure-domain.sh from your ArchForge checkout.'