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 bash2
# Configure existing independent status server domain and Caddy managed ACME TLS.3
# Does not touch ArchForge checkout, SQLite, admin account or main ArchForge website.4
set -Eeuo pipefail5
umask 0776
fail(){ printf '[ERROR] %s\n' "$*" >&2; exit 1; }7
info(){ printf '[*] %s\n' "$*"; }8
ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)9
# shellcheck source=domain-lib.sh10
source "$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.15
source /etc/os-release16
[[ ${ID:-} == debian && ${VERSION_ID:-} == 13 ]] || fail 'This setup is for Debian 13 only'18
CFG=/etc/archforge-status/status.env19
CADDYFILE=/etc/caddy/Caddyfile20
SITEFILE=/etc/caddy/conf.d/archforge-status.caddy21
[[ -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.'23
for prog in caddy systemctl curl dig python3; do24
command -v "$prog" >/dev/null || fail "$prog is missing. Install with: apt-get install -y caddy curl dnsutils python3"25
done26
mkdir -p /etc/caddy/conf.d27
[[ ! -L "$SITEFILE" ]] || fail 'Refusing to change a symlinked Caddy site configuration'28
OLD_DOMAIN=$(af_read_public_domain "$CFG") || fail 'STATUS_PUBLIC_URL is invalid in status.env'30
printf '\n==============================================\n'31
printf ' ArchForge Status — Domein & SSL Wizard\n Debian 13 • Caddy (automatic HTTPS)\n==============================================\n\n'32
printf 'Current domain: %s\n' "$OLD_DOMAIN"33
read -rp "New/confirmed status domain [${OLD_DOMAIN}]: " DOMAIN34
DOMAIN=${DOMAIN:-$OLD_DOMAIN}35
DOMAIN=${DOMAIN,,}36
af_valid_domain "$DOMAIN" || fail 'Invalid domain. Use a complete host such as status.archforge.nl.'37
read -rp 'Public IPv4 of THIS Debian VPS (VPS 2): ' IPV438
af_valid_ipv4 "$IPV4" || fail 'Please enter a valid IPv4 address of VPS 2.'39
python3 - "$IPV4" <<'PYIP' || fail 'VPS IP must be a public, globally routable IPv4 address.'40
import ipaddress,sys41
assert ipaddress.IPv4Address(sys.argv[1]).is_global42
PYIP43
read -rp 'Public IPv6 of VPS 2 (Enter if no working IPv6): ' IPV644
[[ -z "$IPV6" ]] || af_valid_ipv6 "$IPV6" || fail 'Invalid IPv6 address'45
read -rp 'ACME contact email (recommended, Enter to skip): ' EMAIL46
af_valid_email "$EMAIL" || fail 'Invalid email'48
printf '\nChecking public DNS for %s ...\n' "$DOMAIN"49
A_RECORDS=$(af_dns_records A "$DOMAIN")50
AAAA_RECORDS=$(af_dns_records AAAA "$DOMAIN")51
printf 'DNS A: %s\n' "${A_RECORDS:-[not found]}"52
printf 'DNS AAAA: %s\n' "${AAAA_RECORDS:-[not found]}"53
if [[ -z "$A_RECORDS" ]] || [[ "$(printf '%s\n' "$A_RECORDS" | sort -u)" != "$IPV4" ]]; then54
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 259
fi60
if [[ -n "$AAAA_RECORDS" ]]; then61
[[ -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.'63
import ipaddress, sys64
expected = ipaddress.IPv6Address(sys.argv[1])65
found = [ipaddress.IPv6Address(line) for line in sys.argv[2].splitlines() if line.strip()]66
assert found and all(value == expected for value in found)67
PY68
fi69
if [[ -n "$IPV6" && -z "$AAAA_RECORDS" ]]; then70
info 'No AAAA record found. IPv6 is optional; IPv4 HTTPS can work without it.'71
fi73
printf '\nCaddy needs TCP 80/443 reachable from the internet to issue a certificate.\n'74
printf 'Set DNS directly to VPS 2 (not through VPS 1). Check the VPS provider firewall.\n'75
if command -v ss >/dev/null; then76
printf '\nCurrent port listeners (check for nginx/apache conflicts):\n'77
ss -ltnp | grep -E '(^State|:80[[:space:]]|:443[[:space:]])' || true78
fi79
if command -v ufw >/dev/null && ufw status | grep -q '^Status: active'; then80
read -rp 'UFW is active. Allow TCP 80 and 443? Type ALLOW: ' UFW_ANSWER81
if [[ "$UFW_ANSWER" == ALLOW ]]; then82
ufw allow 80/tcp83
ufw allow 443/tcp84
else85
info 'UFW rules untouched; make sure both ports are reachable.'86
fi87
fi88
printf '\nWill configure HTTPS for https://%s and update STATUS_PUBLIC_URL.\n' "$DOMAIN"89
printf 'The status database, administrator and other secrets will be preserved.\n'90
read -rp 'Type ENABLE to apply and start Caddy: ' CONFIRM91
[[ "$CONFIRM" == ENABLE ]] || fail 'Cancelled without changing application or Caddy settings.'93
# Backup all impacted files before changing them. Restore on validation/service failure.94
TEMP=$(mktemp -d /root/.archforge-tls-setup.XXXXXXXX)95
chmod 700 "$TEMP"96
cleanup(){ rm -rf -- "$TEMP"; }97
trap cleanup EXIT98
cp -a -- "$CFG" "$TEMP/status.env"99
cp -a -- "$CADDYFILE" "$TEMP/Caddyfile"100
if [[ -f "$SITEFILE" ]]; then cp -a -- "$SITEFILE" "$TEMP/site.caddy"; fi101
restore_previous() {102
printf '[!] Domain setup failed; restoring previous local configuration.\n' >&2103
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"; fi106
systemctl reload caddy >/dev/null 2>&1 || true107
}108
if ! af_render_caddy "$DOMAIN" "$EMAIL" > "$SITEFILE"; then109
restore_previous110
fail 'Unable to generate safe Caddy configuration'111
fi112
chmod 644 "$SITEFILE"113
if ! grep -Eq '^[[:space:]]*import[[:space:]]+/etc/caddy/conf\.d/\*\.caddy[[:space:]]*$' "$CADDYFILE"; then114
printf '\nimport /etc/caddy/conf.d/*.caddy\n' >> "$CADDYFILE"115
fi116
if ! caddy validate --config "$CADDYFILE"; then117
restore_previous118
fail 'Caddy validation failed. Inspect other domain bindings and Caddyfile imports.'119
fi120
if ! systemctl enable --now caddy || ! systemctl reload caddy; then121
restore_previous122
fail 'Caddy could not start/reload. Check port 80/443 conflicts and journalctl -u caddy.'123
fi124
if ! af_set_public_domain "$CFG" "$DOMAIN"; then125
restore_previous126
fail 'Could not update STATUS_PUBLIC_URL. Application secrets were preserved.'127
fi128
if systemctl is-active --quiet archforge-status; then129
if ! systemctl restart archforge-status; then130
restore_previous131
systemctl restart archforge-status || true132
fail 'Status service restart failed. Old domain settings restored.'133
fi134
else135
info 'archforge-status is not active. Start it separately once admin setup is complete.'136
fi138
info 'Caddy accepts the domain configuration and has started certificate management.'139
info 'Verifying a trusted HTTPS certificate from the local Caddy instance...'140
TLS_OK=false141
for attempt in 1 2 3 4 5; do142
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"; then143
TLS_OK=true144
break145
fi146
sleep 3147
done148
printf '\n==============================================\n'149
printf ' Domain configured: https://%s\n' "$DOMAIN"150
printf ' Admin panel: https://%s/admin/login\n' "$DOMAIN"151
if [[ "$TLS_OK" == true ]]; then152
printf ' TLS: locally verified (trusted certificate, live /healthz)\n'153
else154
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"158
fi159
printf '==============================================\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.