Skip to content

Repository files, collaboration, releases and project activity.

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