Skip to content

Repository files, collaboration, releases and project activity.

ARCHFORGE HISTORY

Blame · scripts/fix-https.sh

Branch main · the Commit author is not necessarily a verified ArchForge account.

c50a429023archadmin2026-10-111 #!/usr/bin/env bash
c50a429023archadmin2026-10-112 # Fix the stock :80 Caddy site which can leave status.archforge.nl unreachable on HTTPS.
c50a429023archadmin2026-10-113 # Intentional: never touch the status app, its environment, SQLite, or admin credentials.
c50a429023archadmin2026-10-114 set -Eeuo pipefail
c50a429023archadmin2026-10-115 umask 077
c50a429023archadmin2026-10-116 log() { printf '[*] %s\n' "$*"; }
c50a429023archadmin2026-10-117 error() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
c50a429023archadmin2026-10-118 [[ $EUID -eq 0 ]] || error 'Run as root: sudo bash ./fix-https.sh'
c50a429023archadmin2026-10-119 [[ -r /etc/os-release ]] || error 'Cannot determine Linux distribution'
c50a429023archadmin2026-10-1110 # shellcheck disable=SC1091
c50a429023archadmin2026-10-1111 source /etc/os-release
c50a429023archadmin2026-10-1112 [[ ${ID:-} == debian && ${VERSION_ID:-} == 13 ]] || error 'This repair is for Debian 13 only'
c50a429023archadmin2026-10-1113 for binary in caddy systemctl ss curl python3; do
c50a429023archadmin2026-10-1114  command -v "$binary" >/dev/null 2>&1 || error "Missing required program: $binary"
c50a429023archadmin2026-10-1115 done
c50a429023archadmin2026-10-1116 
c50a429023archadmin2026-10-1117 ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
c50a429023archadmin2026-10-1118 # shellcheck source=domain-lib.sh
c50a429023archadmin2026-10-1119 source "$ROOT/scripts/domain-lib.sh"
c50a429023archadmin2026-10-1120 CFG=/etc/archforge-status/status.env
c50a429023archadmin2026-10-1121 CADDYFILE=/etc/caddy/Caddyfile
c50a429023archadmin2026-10-1122 SITEFILE=/etc/caddy/conf.d/archforge-status.caddy
c50a429023archadmin2026-10-1123 [[ -f "$CFG" && ! -L "$CFG" ]] || error 'Status configuration not found or symbolic link'
c50a429023archadmin2026-10-1124 [[ -f "$CADDYFILE" && ! -L "$CADDYFILE" ]] || error 'Caddyfile not found or symbolic link'
c50a429023archadmin2026-10-1125 [[ ! -L "$SITEFILE" ]] || error 'Refusing symlinked Caddy site file'
c50a429023archadmin2026-10-1126 DOMAIN=$(af_read_public_domain "$CFG") || error 'Invalid STATUS_PUBLIC_URL in existing status configuration'
c50a429023archadmin2026-10-1127 [[ "$DOMAIN" == status.archforge.nl ]] || error "Configured domain is '$DOMAIN', expected status.archforge.nl; no changes made"
c50a429023archadmin2026-10-1128 
c50a429023archadmin2026-10-1129 # The user has only the Debian default :80 site and our status snippet. Never
c50a429023archadmin2026-10-1130 # replace multi-site or otherwise customized Caddy instances on a shared VPS.
c50a429023archadmin2026-10-1131 if [[ -d /etc/caddy/conf.d ]]; then
c50a429023archadmin2026-10-1132  OTHER=$(find /etc/caddy/conf.d -maxdepth 1 -type f -name '*.caddy' ! -name 'archforge-status.caddy' -print -quit)
c50a429023archadmin2026-10-1133  [[ -z "$OTHER" ]] || error "Other Caddy site exists: $OTHER. No changes made."
c50a429023archadmin2026-10-1134 fi
c50a429023archadmin2026-10-1135 CURRENT_JSON=$(mktemp /tmp/archforge-caddy-adapt.XXXXXXXX)
c50a429023archadmin2026-10-1136 trap 'rm -f "$CURRENT_JSON"' EXIT
c50a429023archadmin2026-10-1137 if ! caddy adapt --config "$CADDYFILE" > "$CURRENT_JSON" 2>/dev/null; then
c50a429023archadmin2026-10-1138  error 'Existing Caddy config does not parse. No changes made.'
c50a429023archadmin2026-10-1139 fi
c50a429023archadmin2026-10-1140 if ! python3 - "$CURRENT_JSON" "$DOMAIN" <<'PY'
c50a429023archadmin2026-10-1141 import json, sys
c50a429023archadmin2026-10-1142 conf=json.load(open(sys.argv[1]))
c50a429023archadmin2026-10-1143 domain=sys.argv[2]
c50a429023archadmin2026-10-1144 http=conf.get('apps',{}).get('http',{})
c50a429023archadmin2026-10-1145 for server in http.get('servers',{}).values():
c50a429023archadmin2026-10-1146  for route in server.get('routes',[]):
c50a429023archadmin2026-10-1147  for matcher in route.get('match',[]):
c50a429023archadmin2026-10-1148  for host in matcher.get('host',[]):
c50a429023archadmin2026-10-1149  if host != domain:
c50a429023archadmin2026-10-1150  print(f'Another HTTPS site exists: {host}',file=sys.stderr)
c50a429023archadmin2026-10-1151  sys.exit(1)
c50a429023archadmin2026-10-1152 PY
c50a429023archadmin2026-10-1153 then
c50a429023archadmin2026-10-1154  error 'Caddy serves another named site. Refusing to overwrite shared config.'
c50a429023archadmin2026-10-1155 fi
c50a429023archadmin2026-10-1156 
c50a429023archadmin2026-10-1157 if ! curl --silent --show-error --fail --max-time 5 http://127.0.0.1:3000/healthz -o /dev/null; then
c50a429023archadmin2026-10-1158  error 'ArchForge Status is not running on local port 3000. No Caddy changes made.'
c50a429023archadmin2026-10-1159 fi
c50a429023archadmin2026-10-1160 
c50a429023archadmin2026-10-1161 log "Repairing HTTPS for $DOMAIN (Node.js backend is healthy)."
c50a429023archadmin2026-10-1162 BACKUP="/root/archforge-caddy-before-https-fix-$(date -u +%Y%m%d-%H%M%S)"
c50a429023archadmin2026-10-1163 mkdir -m 700 -- "$BACKUP"
c50a429023archadmin2026-10-1164 cp -a -- "$CADDYFILE" "$BACKUP/Caddyfile"
c50a429023archadmin2026-10-1165 if [[ -f "$SITEFILE" ]]; then cp -a -- "$SITEFILE" "$BACKUP/archforge-status.caddy"; fi
c50a429023archadmin2026-10-1166 printf 'Backup directory: %s\n' "$BACKUP"
c50a429023archadmin2026-10-1167 
c50a429023archadmin2026-10-1168 RESTORED=false
c50a429023archadmin2026-10-1169 rollback() {
c50a429023archadmin2026-10-1170  printf '[!] Restoring previous Caddy configuration from %s\n' "$BACKUP" >&2
c50a429023archadmin2026-10-1171  cp -a -- "$BACKUP/Caddyfile" "$CADDYFILE"
c50a429023archadmin2026-10-1172  systemctl restart caddy >/dev/null 2>&1 || true
c50a429023archadmin2026-10-1173  RESTORED=true
c50a429023archadmin2026-10-1174 }
c50a429023archadmin2026-10-1175 
c50a429023archadmin2026-10-1176 # Use the existing configured site if it is a straightforward reverse proxy;
c50a429023archadmin2026-10-1177 # otherwise generate the minimal known-safe Caddyfile without any extra directives.
c50a429023archadmin2026-10-1178 TMP=$(mktemp /etc/caddy/.archforge-caddy.XXXXXXXX)
c50a429023archadmin2026-10-1179 if [[ -f "$SITEFILE" ]] && \
c50a429023archadmin2026-10-1180  grep -Eq '^status\.archforge\.nl[[:space:]]*\{' "$SITEFILE" && \
c50a429023archadmin2026-10-1181  grep -Eq '^[[:space:]]*reverse_proxy[[:space:]]+127\.0\.0\.1:3000[[:space:]]*$' "$SITEFILE"; then
c50a429023archadmin2026-10-1182  cp -- "$SITEFILE" "$TMP"
c50a429023archadmin2026-10-1183 else
c50a429023archadmin2026-10-1184  af_render_caddy "$DOMAIN" '' > "$TMP"
c50a429023archadmin2026-10-1185 fi
c50a429023archadmin2026-10-1186 chmod 644 "$TMP"
c50a429023archadmin2026-10-1187 chown root:root "$TMP"
c50a429023archadmin2026-10-1188 if ! caddy validate --config "$TMP" --adapter caddyfile >/dev/null; then
c50a429023archadmin2026-10-1189  rm -f "$TMP"
c50a429023archadmin2026-10-1190  error 'New direct HTTPS configuration did not validate. Old files untouched.'
c50a429023archadmin2026-10-1191 fi
c50a429023archadmin2026-10-1192 
c50a429023archadmin2026-10-1193 # Force the domain block directly into the main Caddyfile. This intentionally
c50a429023archadmin2026-10-1194 # replaces only the stock HTTP :80 page on this dedicated status VPS.
c50a429023archadmin2026-10-1195 if ! mv -f -- "$TMP" "$CADDYFILE"; then
c50a429023archadmin2026-10-1196  rm -f "$TMP" || true
c50a429023archadmin2026-10-1197  error 'Could not write Caddyfile. Backup remains available.'
c50a429023archadmin2026-10-1198 fi
c50a429023archadmin2026-10-1199 if ! systemctl restart caddy; then
c50a429023archadmin2026-10-11100  log 'Caddy restart failed; printing errors and rolling back.'
c50a429023archadmin2026-10-11101  journalctl -u caddy -n 30 --no-pager || true
c50a429023archadmin2026-10-11102  rollback
c50a429023archadmin2026-10-11103  error 'Caddy failed to restart with HTTPS config.'
c50a429023archadmin2026-10-11104 fi
c50a429023archadmin2026-10-11105 sleep 2
c50a429023archadmin2026-10-11106 has_443() { ss -ltn | grep -Eq '(:|\])443[[:space:]]'; }
c50a429023archadmin2026-10-11107 if ! has_443; then
c50a429023archadmin2026-10-11108  log 'Service did not bind 443. Trying explicit reload of the new Caddyfile.'
c50a429023archadmin2026-10-11109  caddy reload --config "$CADDYFILE" >/dev/null 2>&1 || true
c50a429023archadmin2026-10-11110  sleep 2
c50a429023archadmin2026-10-11111 fi
c50a429023archadmin2026-10-11112 if ! has_443; then
c50a429023archadmin2026-10-11113  printf '\n[!] Caddy STILL DOES NOT LISTEN on TCP 443. This is not a firewall issue.\n' >&2
c50a429023archadmin2026-10-11114  systemctl show caddy -p ExecStart -p FragmentPath --no-pager >&2 || true
c50a429023archadmin2026-10-11115  journalctl -u caddy -n 40 --no-pager >&2 || true
c50a429023archadmin2026-10-11116  rollback
c50a429023archadmin2026-10-11117  error 'HTTPS listener missing. Previous configuration restored. Above is the actual Caddy failure.'
c50a429023archadmin2026-10-11118 fi
c50a429023archadmin2026-10-11119 
c50a429023archadmin2026-10-11120 log 'SUCCESS: Caddy now listens on TCP 443.'
c50a429023archadmin2026-10-11121 log 'Checking whether an ACME certificate is ready...'
c50a429023archadmin2026-10-11122 TLS_OK=false
c50a429023archadmin2026-10-11123 for _ in 1 2 3 4 5 6; do
c50a429023archadmin2026-10-11124  if curl --silent --show-error --fail --max-time 7 --resolve "$DOMAIN:443:127.0.0.1" "https://$DOMAIN/healthz" -o /dev/null 2>/dev/null; then
c50a429023archadmin2026-10-11125  TLS_OK=true
c50a429023archadmin2026-10-11126  break
c50a429023archadmin2026-10-11127  fi
c50a429023archadmin2026-10-11128  sleep 3
c50a429023archadmin2026-10-11129 done
c50a429023archadmin2026-10-11130 printf '\n===============================================\n'
c50a429023archadmin2026-10-11131 printf ' Domain: https://%s\n' "$DOMAIN"
c50a429023archadmin2026-10-11132 printf ' Admin: https://%s/admin/login\n' "$DOMAIN"
c50a429023archadmin2026-10-11133 printf ' HTTPS port: LISTENING (443/tcp)\n'
c50a429023archadmin2026-10-11134 if [[ "$TLS_OK" == true ]]; then
c50a429023archadmin2026-10-11135  printf ' Certificate: VERIFIED (valid TLS and status endpoint)\n'
c50a429023archadmin2026-10-11136 else
c50a429023archadmin2026-10-11137  printf ' Certificate: PENDING / NOT YET VERIFIED\n'
c50a429023archadmin2026-10-11138  printf ' Caddy is listening, but ACME or public DNS/connectivity must finish.\n'
c50a429023archadmin2026-10-11139  printf ' Recent Caddy logs:\n'
c50a429023archadmin2026-10-11140  journalctl -u caddy -n 25 --no-pager || true
c50a429023archadmin2026-10-11141 fi
c50a429023archadmin2026-10-11142 printf ' Backup: %s\n' "$BACKUP"
c50a429023archadmin2026-10-11143 printf '===============================================\n'