Repository files, collaboration, releases and project activity.
ARCHFORGE HISTORY
Blame · scripts/configure-domain.sh
Branch main · the Commit author is not necessarily a verified ArchForge account.
b8619f148earchadmin2026-10-11
1 #!/usr/bin/env bashb8619f148earchadmin2026-10-11
2 # Configure existing independent status server domain and Caddy managed ACME TLS.2109dd33b8archadmin2026-10-12
3 # Does not touch ArchForge checkout, SQLite, admin account or main ArchForge website.b8619f148earchadmin2026-10-11
4 set -Eeuo pipefailb8619f148earchadmin2026-10-11
5 umask 077b8619f148earchadmin2026-10-11
6 fail(){ printf '[ERROR] %s\n' "$*" >&2; exit 1; }b8619f148earchadmin2026-10-11
7 info(){ printf '[*] %s\n' "$*"; }b8619f148earchadmin2026-10-11
8 ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)b8619f148earchadmin2026-10-11
9 # shellcheck source=domain-lib.shb8619f148earchadmin2026-10-11
10 source "$ROOT_DIR/scripts/domain-lib.sh"b8619f148earchadmin2026-10-11
11 [[ $EUID -eq 0 ]] || fail 'Run: sudo bash ./configure-domain.sh (or bash as root)'b8619f148earchadmin2026-10-11
12 [[ -t 0 ]] || fail 'An interactive SSH terminal is required'b8619f148earchadmin2026-10-11
13 [[ -f /etc/os-release ]] || fail 'Cannot check operating system'b8619f148earchadmin2026-10-11
14 # No arbitrary environment path overrides for privileged install operations.b8619f148earchadmin2026-10-11
15 source /etc/os-releaseb8619f148earchadmin2026-10-11
16 [[ ${ID:-} == debian && ${VERSION_ID:-} == 13 ]] || fail 'This setup is for Debian 13 only'b8619f148earchadmin2026-10-11
17 b8619f148earchadmin2026-10-11
18 CFG=/etc/archforge-status/status.envb8619f148earchadmin2026-10-11
19 CADDYFILE=/etc/caddy/Caddyfileb8619f148earchadmin2026-10-11
20 SITEFILE=/etc/caddy/conf.d/archforge-status.caddyb8619f148earchadmin2026-10-11
21 [[ -f "$CFG" && ! -L "$CFG" ]] || fail 'Status configuration missing. Run install.sh before domain setup.'b8619f148earchadmin2026-10-11
22 [[ -f "$CADDYFILE" && ! -L "$CADDYFILE" ]] || fail 'Caddyfile missing. Install Caddy first.'b8619f148earchadmin2026-10-11
23 for prog in caddy systemctl curl dig python3; dob8619f148earchadmin2026-10-11
24 command -v "$prog" >/dev/null || fail "$prog is missing. Install with: apt-get install -y caddy curl dnsutils python3"b8619f148earchadmin2026-10-11
25 doneb8619f148earchadmin2026-10-11
26 mkdir -p /etc/caddy/conf.db8619f148earchadmin2026-10-11
27 [[ ! -L "$SITEFILE" ]] || fail 'Refusing to change a symlinked Caddy site configuration'b8619f148earchadmin2026-10-11
28 OLD_DOMAIN=$(af_read_public_domain "$CFG") || fail 'STATUS_PUBLIC_URL is invalid in status.env'b8619f148earchadmin2026-10-11
29 b8619f148earchadmin2026-10-11
30 printf '\n==============================================\n'b8619f148earchadmin2026-10-11
31 printf ' ArchForge Status — Domein & SSL Wizard\n Debian 13 • Caddy (automatic HTTPS)\n==============================================\n\n'b8619f148earchadmin2026-10-11
32 printf 'Current domain: %s\n' "$OLD_DOMAIN"b8619f148earchadmin2026-10-11
33 read -rp "New/confirmed status domain [${OLD_DOMAIN}]: " DOMAINb8619f148earchadmin2026-10-11
34 DOMAIN=${DOMAIN:-$OLD_DOMAIN}b8619f148earchadmin2026-10-11
35 DOMAIN=${DOMAIN,,}b8619f148earchadmin2026-10-11
36 af_valid_domain "$DOMAIN" || fail 'Invalid domain. Use a complete host such as status.archforge.nl.'b8619f148earchadmin2026-10-11
37 read -rp 'Public IPv4 of THIS Debian VPS (VPS 2): ' IPV4b8619f148earchadmin2026-10-11
38 af_valid_ipv4 "$IPV4" || fail 'Please enter a valid IPv4 address of VPS 2.'b8619f148earchadmin2026-10-11
39 python3 - "$IPV4" <<'PYIP' || fail 'VPS IP must be a public, globally routable IPv4 address.'b8619f148earchadmin2026-10-11
40 import ipaddress,sysb8619f148earchadmin2026-10-11
41 assert ipaddress.IPv4Address(sys.argv[1]).is_globalb8619f148earchadmin2026-10-11
42 PYIPb8619f148earchadmin2026-10-11
43 read -rp 'Public IPv6 of VPS 2 (Enter if no working IPv6): ' IPV6b8619f148earchadmin2026-10-11
44 [[ -z "$IPV6" ]] || af_valid_ipv6 "$IPV6" || fail 'Invalid IPv6 address'b8619f148earchadmin2026-10-11
45 read -rp 'ACME contact email (recommended, Enter to skip): ' EMAILb8619f148earchadmin2026-10-11
46 af_valid_email "$EMAIL" || fail 'Invalid email'b8619f148earchadmin2026-10-11
47 b8619f148earchadmin2026-10-11
48 printf '\nChecking public DNS for %s ...\n' "$DOMAIN"b8619f148earchadmin2026-10-11
49 A_RECORDS=$(af_dns_records A "$DOMAIN")b8619f148earchadmin2026-10-11
50 AAAA_RECORDS=$(af_dns_records AAAA "$DOMAIN")b8619f148earchadmin2026-10-11
51 printf 'DNS A: %s\n' "${A_RECORDS:-[not found]}"b8619f148earchadmin2026-10-11
52 printf 'DNS AAAA: %s\n' "${AAAA_RECORDS:-[not found]}"b8619f148earchadmin2026-10-11
53 if [[ -z "$A_RECORDS" ]] || [[ "$(printf '%s\n' "$A_RECORDS" | sort -u)" != "$IPV4" ]]; thenb8619f148earchadmin2026-10-11
54 printf '\n[DNS NOT READY] Add/change this record at your DNS provider:\n'b8619f148earchadmin2026-10-11
55 printf 'Type A | Host %s | Value %s\n' "$DOMAIN" "$IPV4"b8619f148earchadmin2026-10-11
56 printf 'Remove any other A records for this subdomain pointing elsewhere.\n'b8619f148earchadmin2026-10-11
57 printf 'Wait for DNS propagation, then rerun this script. No configuration changed.\n'b8619f148earchadmin2026-10-11
58 exit 2b8619f148earchadmin2026-10-11
59 fib8619f148earchadmin2026-10-11
60 if [[ -n "$AAAA_RECORDS" ]]; thenb8619f148earchadmin2026-10-11
61 [[ -n "$IPV6" ]] || fail 'DNS AAAA exists but no VPS 2 IPv6 was supplied. Remove incorrect AAAA or rerun with your real IPv6. No changes made.'b8619f148earchadmin2026-10-11
62 python3 - "$IPV6" "$AAAA_RECORDS" <<'PY' || fail 'DNS AAAA does not match the VPS 2 IPv6 address. No changes made.'b8619f148earchadmin2026-10-11
63 import ipaddress, sysb8619f148earchadmin2026-10-11
64 expected = ipaddress.IPv6Address(sys.argv[1])b8619f148earchadmin2026-10-11
65 found = [ipaddress.IPv6Address(line) for line in sys.argv[2].splitlines() if line.strip()]b8619f148earchadmin2026-10-11
66 assert found and all(value == expected for value in found)b8619f148earchadmin2026-10-11
67 PYb8619f148earchadmin2026-10-11
68 fib8619f148earchadmin2026-10-11
69 if [[ -n "$IPV6" && -z "$AAAA_RECORDS" ]]; thenb8619f148earchadmin2026-10-11
70 info 'No AAAA record found. IPv6 is optional; IPv4 HTTPS can work without it.'b8619f148earchadmin2026-10-11
71 fib8619f148earchadmin2026-10-11
72 b8619f148earchadmin2026-10-11
73 printf '\nCaddy needs TCP 80/443 reachable from the internet to issue a certificate.\n'b8619f148earchadmin2026-10-11
74 printf 'Set DNS directly to VPS 2 (not through VPS 1). Check the VPS provider firewall.\n'b8619f148earchadmin2026-10-11
75 if command -v ss >/dev/null; thenb8619f148earchadmin2026-10-11
76 printf '\nCurrent port listeners (check for nginx/apache conflicts):\n'b8619f148earchadmin2026-10-11
77 ss -ltnp | grep -E '(^State|:80[[:space:]]|:443[[:space:]])' || trueb8619f148earchadmin2026-10-11
78 fib8619f148earchadmin2026-10-11
79 if command -v ufw >/dev/null && ufw status | grep -q '^Status: active'; thenb8619f148earchadmin2026-10-11
80 read -rp 'UFW is active. Allow TCP 80 and 443? Type ALLOW: ' UFW_ANSWERb8619f148earchadmin2026-10-11
81 if [[ "$UFW_ANSWER" == ALLOW ]]; thenb8619f148earchadmin2026-10-11
82 ufw allow 80/tcpb8619f148earchadmin2026-10-11
83 ufw allow 443/tcpb8619f148earchadmin2026-10-11
84 elseb8619f148earchadmin2026-10-11
85 info 'UFW rules untouched; make sure both ports are reachable.'b8619f148earchadmin2026-10-11
86 fib8619f148earchadmin2026-10-11
87 fib8619f148earchadmin2026-10-11
88 printf '\nWill configure HTTPS for https://%s and update STATUS_PUBLIC_URL.\n' "$DOMAIN"b8619f148earchadmin2026-10-11
89 printf 'The status database, administrator and other secrets will be preserved.\n'b8619f148earchadmin2026-10-11
90 read -rp 'Type ENABLE to apply and start Caddy: ' CONFIRMb8619f148earchadmin2026-10-11
91 [[ "$CONFIRM" == ENABLE ]] || fail 'Cancelled without changing application or Caddy settings.'b8619f148earchadmin2026-10-11
92 b8619f148earchadmin2026-10-11
93 # Backup all impacted files before changing them. Restore on validation/service failure.b8619f148earchadmin2026-10-11
94 TEMP=$(mktemp -d /root/.archforge-tls-setup.XXXXXXXX)b8619f148earchadmin2026-10-11
95 chmod 700 "$TEMP"b8619f148earchadmin2026-10-11
96 cleanup(){ rm -rf -- "$TEMP"; }b8619f148earchadmin2026-10-11
97 trap cleanup EXITb8619f148earchadmin2026-10-11
98 cp -a -- "$CFG" "$TEMP/status.env"b8619f148earchadmin2026-10-11
99 cp -a -- "$CADDYFILE" "$TEMP/Caddyfile"b8619f148earchadmin2026-10-11
100 if [[ -f "$SITEFILE" ]]; then cp -a -- "$SITEFILE" "$TEMP/site.caddy"; fib8619f148earchadmin2026-10-11
101 restore_previous() {b8619f148earchadmin2026-10-11
102 printf '[!] Domain setup failed; restoring previous local configuration.\n' >&2b8619f148earchadmin2026-10-11
103 cp -a -- "$TEMP/status.env" "$CFG"b8619f148earchadmin2026-10-11
104 cp -a -- "$TEMP/Caddyfile" "$CADDYFILE"b8619f148earchadmin2026-10-11
105 if [[ -f "$TEMP/site.caddy" ]]; then cp -a -- "$TEMP/site.caddy" "$SITEFILE"; else rm -f -- "$SITEFILE"; fib8619f148earchadmin2026-10-11
106 systemctl reload caddy >/dev/null 2>&1 || trueb8619f148earchadmin2026-10-11
107 }b8619f148earchadmin2026-10-11
108 if ! af_render_caddy "$DOMAIN" "$EMAIL" > "$SITEFILE"; thenb8619f148earchadmin2026-10-11
109 restore_previousb8619f148earchadmin2026-10-11
110 fail 'Unable to generate safe Caddy configuration'b8619f148earchadmin2026-10-11
111 fib8619f148earchadmin2026-10-11
112 chmod 644 "$SITEFILE"b8619f148earchadmin2026-10-11
113 if ! grep -Eq '^[[:space:]]*import[[:space:]]+/etc/caddy/conf\.d/\*\.caddy[[:space:]]*$' "$CADDYFILE"; thenb8619f148earchadmin2026-10-11
114 printf '\nimport /etc/caddy/conf.d/*.caddy\n' >> "$CADDYFILE"b8619f148earchadmin2026-10-11
115 fib8619f148earchadmin2026-10-11
116 if ! caddy validate --config "$CADDYFILE"; thenb8619f148earchadmin2026-10-11
117 restore_previousb8619f148earchadmin2026-10-11
118 fail 'Caddy validation failed. Inspect other domain bindings and Caddyfile imports.'b8619f148earchadmin2026-10-11
119 fib8619f148earchadmin2026-10-11
120 if ! systemctl enable --now caddy || ! systemctl reload caddy; thenb8619f148earchadmin2026-10-11
121 restore_previousb8619f148earchadmin2026-10-11
122 fail 'Caddy could not start/reload. Check port 80/443 conflicts and journalctl -u caddy.'b8619f148earchadmin2026-10-11
123 fib8619f148earchadmin2026-10-11
124 if ! af_set_public_domain "$CFG" "$DOMAIN"; thenb8619f148earchadmin2026-10-11
125 restore_previousb8619f148earchadmin2026-10-11
126 fail 'Could not update STATUS_PUBLIC_URL. Application secrets were preserved.'b8619f148earchadmin2026-10-11
127 fib8619f148earchadmin2026-10-11
128 if systemctl is-active --quiet archforge-status; thenb8619f148earchadmin2026-10-11
129 if ! systemctl restart archforge-status; thenb8619f148earchadmin2026-10-11
130 restore_previousb8619f148earchadmin2026-10-11
131 systemctl restart archforge-status || trueb8619f148earchadmin2026-10-11
132 fail 'Status service restart failed. Old domain settings restored.'b8619f148earchadmin2026-10-11
133 fib8619f148earchadmin2026-10-11
134 elseb8619f148earchadmin2026-10-11
135 info 'archforge-status is not active. Start it separately once admin setup is complete.'b8619f148earchadmin2026-10-11
136 fib8619f148earchadmin2026-10-11
137 b8619f148earchadmin2026-10-11
138 info 'Caddy accepts the domain configuration and has started certificate management.'b8619f148earchadmin2026-10-11
139 info 'Verifying a trusted HTTPS certificate from the local Caddy instance...'b8619f148earchadmin2026-10-11
140 TLS_OK=falseb8619f148earchadmin2026-10-11
141 for attempt in 1 2 3 4 5; dob8619f148earchadmin2026-10-11
142 if curl --silent --show-error --fail --max-time 8 --resolve "${DOMAIN}:443:127.0.0.1" "https://${DOMAIN}/healthz" -o /dev/null 2>"$TEMP/curl.err"; thenb8619f148earchadmin2026-10-11
143 TLS_OK=trueb8619f148earchadmin2026-10-11
144 breakb8619f148earchadmin2026-10-11
145 fib8619f148earchadmin2026-10-11
146 sleep 3b8619f148earchadmin2026-10-11
147 doneb8619f148earchadmin2026-10-11
148 printf '\n==============================================\n'b8619f148earchadmin2026-10-11
149 printf ' Domain configured: https://%s\n' "$DOMAIN"b8619f148earchadmin2026-10-11
150 printf ' Admin panel: https://%s/admin/login\n' "$DOMAIN"b8619f148earchadmin2026-10-11
151 if [[ "$TLS_OK" == true ]]; thenb8619f148earchadmin2026-10-11
152 printf ' TLS: locally verified (trusted certificate, live /healthz)\n'b8619f148earchadmin2026-10-11
153 elseb8619f148earchadmin2026-10-11
154 printf ' TLS: NOT YET VERIFIED — issuance may still be pending!\n'b8619f148earchadmin2026-10-11
155 printf ' Check DNS, public TCP 80/443, provider firewall and Caddy logs.\n'b8619f148earchadmin2026-10-11
156 printf ' Logs: journalctl -u caddy -n 80 --no-pager\n'b8619f148earchadmin2026-10-11
157 printf ' Direct check: curl -Iv https://%s\n' "$DOMAIN"b8619f148earchadmin2026-10-11
158 fib8619f148earchadmin2026-10-11
159 printf '==============================================\n'b8619f148earchadmin2026-10-11
160 # DNS correctness and validation succeeded, but TLS handshake may need time/ports.b8619f148earchadmin2026-10-11
161 # Do not claim the public site is already reachable in that case.