From 987413ab3a4a393c8001382aa307b99db0e165b3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Apr 2026 16:24:24 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20bug:=20edge-control=20`add=5Froute`=20ta?= =?UTF-8?q?rgets=20non-existent=20Caddy=20server=20`edge`=20=E2=80=94=20re?= =?UTF-8?q?gistration=20succeeds=20in=20registry=20but=20traffic=20never?= =?UTF-8?q?=20routes=20(#789)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install.sh: use Caddy `servers { name edge }` global option so the emitted Caddyfile produces a predictably-named server - lib/caddy.sh: add `_discover_server_name` that queries the admin API for the first server listening on :80/:443 — add_route and remove_route use dynamic discovery instead of hardcoding `/servers/edge/` - lib/caddy.sh: add_route, remove_route, and reload_caddy now check HTTP status codes (≥400 → return 1 with error message) instead of only checking curl exit code Co-Authored-By: Claude Opus 4.6 (1M context) --- tools/edge-control/install.sh | 10 +++- tools/edge-control/lib/caddy.sh | 85 +++++++++++++++++++++++++-------- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/tools/edge-control/install.sh b/tools/edge-control/install.sh index 68880ab..4453a5a 100755 --- a/tools/edge-control/install.sh +++ b/tools/edge-control/install.sh @@ -225,13 +225,19 @@ EOF chmod 600 "$GANDI_ENV" # Create Caddyfile with admin API and wildcard cert +# The "servers" global option names the auto-generated server "edge" so that +# lib/caddy.sh (which discovers the server dynamically) finds a predictable +# name — defense-in-depth alongside the dynamic discovery in add_route. CADDYFILE="/etc/caddy/Caddyfile" -cat > "$CADDYFILE" < "$CADDYFILE" <<'CADDYEOF' # Caddy configuration for edge control plane # Admin API enabled on 127.0.0.1:2019 { admin localhost:2019 + servers { + name edge + } } # Default site (reverse proxy for edge tunnels will be added dynamically) @@ -240,7 +246,7 @@ cat > "$CADDYFILE" </dev/null || { diff --git a/tools/edge-control/lib/caddy.sh b/tools/edge-control/lib/caddy.sh index 69970cf..1e16cdc 100755 --- a/tools/edge-control/lib/caddy.sh +++ b/tools/edge-control/lib/caddy.sh @@ -19,6 +19,24 @@ CADDY_ADMIN_URL="${CADDY_ADMIN_URL:-http://127.0.0.1:2019}" # Domain suffix for projects DOMAIN_SUFFIX="${DOMAIN_SUFFIX:-disinto.ai}" +# Discover the Caddy server name that listens on :80/:443 +# Usage: _discover_server_name +_discover_server_name() { + local server_name + server_name=$(curl -sS "${CADDY_ADMIN_URL}/config/apps/http/servers" \ + | jq -r 'to_entries | map(select(.value.listen[]? | test(":(80|443)$"))) | .[0].key // empty') || { + echo "Error: could not query Caddy admin API for servers" >&2 + return 1 + } + + if [ -z "$server_name" ]; then + echo "Error: could not find a Caddy server listening on :80/:443" >&2 + return 1 + fi + + echo "$server_name" +} + # Add a route for a project # Usage: add_route add_route() { @@ -26,6 +44,9 @@ add_route() { local port="$2" local fqdn="${project}.${DOMAIN_SUFFIX}" + local server_name + server_name=$(_discover_server_name) || return 1 + # Build the route configuration (partial config) local route_config route_config=$(cat <&1) || { + -d "$route_config") || { echo "Error: failed to add route for ${fqdn}" >&2 - echo "Response: ${response}" >&2 return 1 } + status=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + if [ "$status" -ge 400 ]; then + echo "Error: Caddy admin API returned ${status}: ${body}" >&2 + return 1 + fi echo "Added route: ${fqdn} → 127.0.0.1:${port}" >&2 } @@ -78,31 +104,45 @@ remove_route() { local project="$1" local fqdn="${project}.${DOMAIN_SUFFIX}" - # First, get current routes - local routes_json - routes_json=$(curl -s "${CADDY_ADMIN_URL}/config/apps/http/servers/edge/routes" 2>&1) || { + local server_name + server_name=$(_discover_server_name) || return 1 + + # First, get current routes, checking HTTP status + local response status body + response=$(curl -sS -w '\n%{http_code}' \ + "${CADDY_ADMIN_URL}/config/apps/http/servers/${server_name}/routes") || { echo "Error: failed to get current routes" >&2 return 1 } + status=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + if [ "$status" -ge 400 ]; then + echo "Error: Caddy admin API returned ${status}: ${body}" >&2 + return 1 + fi # Find the route index that matches our fqdn using jq local route_index - route_index=$(echo "$routes_json" | jq -r "to_entries[] | select(.value.match[]?.host[]? == \"${fqdn}\") | .key" 2>/dev/null | head -1) + route_index=$(echo "$body" | jq -r "to_entries[] | select(.value.match[]?.host[]? == \"${fqdn}\") | .key" 2>/dev/null | head -1) if [ -z "$route_index" ] || [ "$route_index" = "null" ]; then echo "Warning: route for ${fqdn} not found" >&2 return 0 fi - # Delete the route at the found index - local response - response=$(curl -s -X DELETE \ - "${CADDY_ADMIN_URL}/config/apps/http/servers/edge/routes/${route_index}" \ - -H "Content-Type: application/json" 2>&1) || { + # Delete the route at the found index, checking HTTP status + response=$(curl -sS -w '\n%{http_code}' -X DELETE \ + "${CADDY_ADMIN_URL}/config/apps/http/servers/${server_name}/routes/${route_index}" \ + -H "Content-Type: application/json") || { echo "Error: failed to remove route for ${fqdn}" >&2 - echo "Response: ${response}" >&2 return 1 } + status=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + if [ "$status" -ge 400 ]; then + echo "Error: Caddy admin API returned ${status}: ${body}" >&2 + return 1 + fi echo "Removed route: ${fqdn}" >&2 } @@ -110,13 +150,18 @@ remove_route() { # Reload Caddy to apply configuration changes # Usage: reload_caddy reload_caddy() { - local response - response=$(curl -s -X POST \ - "${CADDY_ADMIN_URL}/reload" 2>&1) || { + local response status body + response=$(curl -sS -w '\n%{http_code}' -X POST \ + "${CADDY_ADMIN_URL}/reload") || { echo "Error: failed to reload Caddy" >&2 - echo "Response: ${response}" >&2 return 1 } + status=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + if [ "$status" -ge 400 ]; then + echo "Error: Caddy reload returned ${status}: ${body}" >&2 + return 1 + fi echo "Caddy reloaded" >&2 }