feat: add phase 8.5 Nginx to Caddy migration wrapper and enhance post-check script for direct access handling

This commit is contained in:
S
2026-03-02 22:45:49 -06:00
parent 9224b91374
commit 14a5773a2d
3 changed files with 180 additions and 22 deletions

7
phase8_5_nginx_to_caddy.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
# Backward-compat wrapper: phase 8.5 was renamed to phase 7.5.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "[WARN] phase8_5_nginx_to_caddy.sh was renamed to phase7_5_nginx_to_caddy.sh" >&2
exec "${SCRIPT_DIR}/phase7_5_nginx_to_caddy.sh" "$@"

View File

@@ -42,6 +42,72 @@ PHASE8_STATE_FILE="${PHASE8_STATE_DIR}/phase8_github_repo_state.json"
UNRAID_DOCKER_NETWORK_NAME="br0" UNRAID_DOCKER_NETWORK_NAME="br0"
# Compose files live in a centralized project directory. # Compose files live in a centralized project directory.
CADDY_COMPOSE_DIR="${UNRAID_COMPOSE_DIR}/caddy" CADDY_COMPOSE_DIR="${UNRAID_COMPOSE_DIR}/caddy"
PHASE8_GITEA_ROUTE_BEGIN="# BEGIN_PHASE8_GITEA_ROUTE"
PHASE8_GITEA_ROUTE_END="# END_PHASE8_GITEA_ROUTE"
wait_for_https_public() {
local host="$1" max_secs="${2:-30}"
local elapsed=0
while [[ $elapsed -lt $max_secs ]]; do
if curl -sf -o /dev/null "https://${host}/api/v1/version" 2>/dev/null; then
return 0
fi
sleep 2
elapsed=$((elapsed + 2))
done
return 1
}
wait_for_https_via_resolve() {
local host="$1" ip="$2" max_secs="${3:-300}"
local elapsed=0
log_info "Waiting for HTTPS via direct Caddy path (--resolve ${host}:443:${ip})..."
while [[ $elapsed -lt $max_secs ]]; do
if curl -skf --resolve "${host}:443:${ip}" "https://${host}/api/v1/version" >/dev/null 2>&1; then
log_success "HTTPS reachable via Caddy IP (after ${elapsed}s)"
return 0
fi
sleep 2
elapsed=$((elapsed + 2))
done
log_error "Timeout waiting for HTTPS via --resolve (${host} -> ${ip}) after ${max_secs}s"
if ssh_exec UNRAID "docker ps --format '{{.Names}}' | grep -qx 'caddy'" >/dev/null 2>&1; then
log_warn "Recent Caddy logs (tail 80):"
ssh_exec UNRAID "docker logs --tail 80 caddy 2>&1" || true
fi
return 1
}
caddyfile_has_domain_block() {
local file="$1" domain="$2"
awk -v domain="$domain" '
function trim(s) {
sub(/^[[:space:]]+/, "", s)
sub(/[[:space:]]+$/, "", s)
return s
}
{
line = $0
if (line ~ /^[[:space:]]*#/) next
pos = index(line, "{")
if (pos <= 0) next
labels = trim(substr(line, 1, pos - 1))
if (labels == "" || labels ~ /^\(/) next
gsub(/[[:space:]]+/, "", labels)
n = split(labels, parts, ",")
for (i = 1; i <= n; i++) {
if (parts[i] == domain) {
found = 1
}
}
}
END {
exit(found ? 0 : 1)
}
' "$file"
}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helper: persist original GitHub repo settings for teardown symmetry # Helper: persist original GitHub repo settings for teardown symmetry
@@ -145,28 +211,62 @@ fi
# Step 2: Render + deploy Caddyfile # Step 2: Render + deploy Caddyfile
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
log_step 2 "Deploying Caddyfile..." log_step 2 "Deploying Caddyfile..."
if ssh_exec UNRAID "test -f '${CADDY_DATA_PATH}/Caddyfile'" 2>/dev/null; then GITEA_CONTAINER_IP="${UNRAID_GITEA_IP}"
log_info "Caddyfile already exists — skipping" export GITEA_CONTAINER_IP GITEA_DOMAIN CADDY_DOMAIN
else CADDYFILE_UPDATED=0
TMPFILE=$(mktemp)
GITEA_CONTAINER_IP="${UNRAID_GITEA_IP}"
export GITEA_CONTAINER_IP GITEA_DOMAIN CADDY_DOMAIN
# Build TLS block based on TLS_MODE # Build TLS block based on TLS_MODE
if [[ "$TLS_MODE" == "cloudflare" ]]; then if [[ "$TLS_MODE" == "cloudflare" ]]; then
TLS_BLOCK=" tls { TLS_BLOCK=" tls {
dns cloudflare {env.CF_API_TOKEN} dns cloudflare {env.CF_API_TOKEN}
}" }"
else else
TLS_BLOCK=" tls ${SSL_CERT_PATH} ${SSL_KEY_PATH}" TLS_BLOCK=" tls ${SSL_CERT_PATH} ${SSL_KEY_PATH}"
fi
export TLS_BLOCK
if ssh_exec UNRAID "test -f '${CADDY_DATA_PATH}/Caddyfile'" 2>/dev/null; then
TMP_EXISTING=$(mktemp)
TMP_UPDATED=$(mktemp)
TMP_ROUTE_BLOCK=$(mktemp)
ssh_exec UNRAID "cat '${CADDY_DATA_PATH}/Caddyfile'" > "$TMP_EXISTING"
if caddyfile_has_domain_block "$TMP_EXISTING" "$GITEA_DOMAIN"; then
log_info "Caddyfile already has a route for ${GITEA_DOMAIN} — preserving existing file"
else
log_warn "Caddyfile exists but has no explicit route for ${GITEA_DOMAIN}"
log_info "Appending managed Gitea route block"
{
echo
echo "${PHASE8_GITEA_ROUTE_BEGIN}"
echo "${GITEA_DOMAIN} {"
printf '%s\n' "$TLS_BLOCK"
echo
echo " reverse_proxy ${GITEA_CONTAINER_IP}:3000"
echo "}"
echo "${PHASE8_GITEA_ROUTE_END}"
echo
} > "$TMP_ROUTE_BLOCK"
# Remove a stale managed block (if present), then append refreshed block.
sed "/^${PHASE8_GITEA_ROUTE_BEGIN}\$/,/^${PHASE8_GITEA_ROUTE_END}\$/d" "$TMP_EXISTING" > "$TMP_UPDATED"
cat "$TMP_UPDATED" "$TMP_ROUTE_BLOCK" > "${TMP_UPDATED}.final"
scp_to UNRAID "${TMP_UPDATED}.final" "${CADDY_DATA_PATH}/Caddyfile"
log_success "Appended managed Gitea route to existing Caddyfile"
CADDYFILE_UPDATED=1
fi fi
export TLS_BLOCK
rm -f "$TMP_EXISTING" "$TMP_UPDATED" "$TMP_ROUTE_BLOCK" "${TMP_UPDATED}.final"
else
TMPFILE=$(mktemp)
render_template "${SCRIPT_DIR}/templates/Caddyfile.tpl" "$TMPFILE" \ render_template "${SCRIPT_DIR}/templates/Caddyfile.tpl" "$TMPFILE" \
"\${CADDY_DOMAIN} \${GITEA_DOMAIN} \${TLS_BLOCK} \${GITEA_CONTAINER_IP}" "\${CADDY_DOMAIN} \${GITEA_DOMAIN} \${TLS_BLOCK} \${GITEA_CONTAINER_IP}"
scp_to UNRAID "$TMPFILE" "${CADDY_DATA_PATH}/Caddyfile" scp_to UNRAID "$TMPFILE" "${CADDY_DATA_PATH}/Caddyfile"
rm -f "$TMPFILE" rm -f "$TMPFILE"
log_success "Caddyfile deployed" log_success "Caddyfile deployed"
CADDYFILE_UPDATED=1
fi fi
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -221,7 +321,14 @@ fi
log_step 4 "Starting Caddy container..." log_step 4 "Starting Caddy container..."
CONTAINER_STATUS=$(ssh_exec UNRAID "docker ps --filter name=caddy --format '{{.Status}}'" 2>/dev/null || true) CONTAINER_STATUS=$(ssh_exec UNRAID "docker ps --filter name=caddy --format '{{.Status}}'" 2>/dev/null || true)
if [[ "$CONTAINER_STATUS" == *"Up"* ]]; then if [[ "$CONTAINER_STATUS" == *"Up"* ]]; then
log_info "Caddy container already running — skipping" log_info "Caddy container already running"
if [[ "$CADDYFILE_UPDATED" -eq 1 ]]; then
log_info "Caddyfile changed — restarting caddy to apply updated config"
ssh_exec UNRAID "docker restart caddy >/dev/null"
log_success "Caddy container restarted with new config"
else
log_info "Caddyfile unchanged — restart not required"
fi
else else
ssh_exec UNRAID "cd '${CADDY_COMPOSE_DIR}' && docker compose up -d 2>/dev/null || docker-compose up -d" ssh_exec UNRAID "cd '${CADDY_COMPOSE_DIR}' && docker compose up -d 2>/dev/null || docker-compose up -d"
log_success "Caddy container started" log_success "Caddy container started"
@@ -232,9 +339,13 @@ fi
# Caddy auto-obtains certs — poll until HTTPS responds. # Caddy auto-obtains certs — poll until HTTPS responds.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
log_step 5 "Waiting for HTTPS (Caddy auto-provisions cert)..." log_step 5 "Waiting for HTTPS (Caddy auto-provisions cert)..."
wait_for_http "https://${GITEA_DOMAIN}/api/v1/version" 120 if wait_for_https_public "${GITEA_DOMAIN}" 30; then
log_success "HTTPS verified through current domain routing — https://${GITEA_DOMAIN} works"
log_success "HTTPS verified — https://${GITEA_DOMAIN} works" else
log_warn "Public-domain routing to Caddy is not ready yet"
wait_for_https_via_resolve "${GITEA_DOMAIN}" "${UNRAID_CADDY_IP}" 300
log_success "HTTPS verified via direct Caddy path; public routing can be completed later"
fi
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Step 6: Mark GitHub repos as offsite backup only # Step 6: Mark GitHub repos as offsite backup only

View File

@@ -16,7 +16,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "${SCRIPT_DIR}/lib/common.sh" source "${SCRIPT_DIR}/lib/common.sh"
load_env load_env
require_vars GITEA_DOMAIN GITEA_ADMIN_TOKEN GITEA_ORG_NAME \ require_vars GITEA_DOMAIN UNRAID_CADDY_IP GITEA_ADMIN_TOKEN GITEA_ORG_NAME \
GITHUB_USERNAME GITHUB_TOKEN \ GITHUB_USERNAME GITHUB_TOKEN \
REPO_NAMES REPO_NAMES
@@ -37,16 +37,44 @@ run_check() {
fi fi
} }
ACCESS_MODE="public"
if ! curl -sf -o /dev/null "https://${GITEA_DOMAIN}/api/v1/version" 2>/dev/null; then
ACCESS_MODE="direct"
log_warn "Public routing to ${GITEA_DOMAIN} not reachable from control plane"
log_warn "Using direct Caddy-IP checks via --resolve (${UNRAID_CADDY_IP})"
else
log_info "Using public-domain checks for ${GITEA_DOMAIN}"
fi
curl_https() {
if [[ "$ACCESS_MODE" == "direct" ]]; then
curl -sk --resolve "${GITEA_DOMAIN}:443:${UNRAID_CADDY_IP}" "$@"
else
curl -s "$@"
fi
}
curl_http() {
if [[ "$ACCESS_MODE" == "direct" ]]; then
curl -s --resolve "${GITEA_DOMAIN}:80:${UNRAID_CADDY_IP}" "$@"
else
curl -s "$@"
fi
}
# Check 1: HTTPS works # Check 1: HTTPS works
run_check "HTTPS returns 200 at https://${GITEA_DOMAIN}" \ # shellcheck disable=SC2329
curl -sf -o /dev/null "https://${GITEA_DOMAIN}/api/v1/version" check_https_version() {
curl_https -f -o /dev/null "https://${GITEA_DOMAIN}/api/v1/version"
}
run_check "HTTPS returns 200 at https://${GITEA_DOMAIN}" check_https_version
# Check 2: HTTP redirects to HTTPS (returns 301) # Check 2: HTTP redirects to HTTPS (returns 301)
# shellcheck disable=SC2329 # shellcheck disable=SC2329
check_redirect() { check_redirect() {
local http_code local http_code
http_code=$(curl -sI -o /dev/null -w "%{http_code}" "http://${GITEA_DOMAIN}/") http_code=$(curl_http -I -o /dev/null -w "%{http_code}" "http://${GITEA_DOMAIN}/")
[[ "$http_code" == "301" ]] [[ "$http_code" == "301" || "$http_code" == "308" ]]
} }
run_check "HTTP → HTTPS redirect (301)" check_redirect run_check "HTTP → HTTPS redirect (301)" check_redirect
@@ -54,17 +82,29 @@ run_check "HTTP → HTTPS redirect (301)" check_redirect
# shellcheck disable=SC2329 # shellcheck disable=SC2329
check_ssl_cert() { check_ssl_cert() {
# Verify openssl can connect and the cert is issued by a recognized CA # Verify openssl can connect and the cert is issued by a recognized CA
local connect_target
if [[ "$ACCESS_MODE" == "direct" ]]; then
connect_target="${UNRAID_CADDY_IP}:443"
else
connect_target="${GITEA_DOMAIN}:443"
fi
local issuer local issuer
issuer=$(echo | openssl s_client -connect "${GITEA_DOMAIN}:443" -servername "${GITEA_DOMAIN}" 2>/dev/null | openssl x509 -noout -issuer 2>/dev/null || echo "") issuer=$(echo | openssl s_client -connect "${connect_target}" -servername "${GITEA_DOMAIN}" 2>/dev/null | openssl x509 -noout -issuer 2>/dev/null || echo "")
# Check that the issuer is not empty (meaning cert is valid) # Check that the issuer is not empty (meaning cert is valid)
[[ -n "$issuer" ]] [[ -n "$issuer" ]]
} }
run_check "SSL certificate is valid" check_ssl_cert run_check "SSL certificate is valid" check_ssl_cert
# Check 4: All repos accessible via HTTPS # Check 4: All repos accessible via HTTPS
# shellcheck disable=SC2329
check_repo_access() {
local repo="$1"
curl_https -f -o /dev/null -H "Authorization: token ${GITEA_ADMIN_TOKEN}" \
"https://${GITEA_DOMAIN}/api/v1/repos/${GITEA_ORG_NAME}/${repo}"
}
for repo in "${REPOS[@]}"; do for repo in "${REPOS[@]}"; do
run_check "Repo ${repo} accessible at https://${GITEA_DOMAIN}/${GITEA_ORG_NAME}/${repo}" \ run_check "Repo ${repo} accessible at https://${GITEA_DOMAIN}/${GITEA_ORG_NAME}/${repo}" \
curl -sf -o /dev/null -H "Authorization: token ${GITEA_ADMIN_TOKEN}" "https://${GITEA_DOMAIN}/api/v1/repos/${GITEA_ORG_NAME}/${repo}" check_repo_access "$repo"
done done
# Check 5: GitHub repos are marked as offsite backup # Check 5: GitHub repos are marked as offsite backup