fix: bug: edge-control add_route targets non-existent Caddy server edge — registration succeeds in registry but traffic never routes (#789) #790

Merged
dev-bot merged 2 commits from fix/issue-789 into main 2026-04-15 16:37:20 +00:00
2 changed files with 73 additions and 22 deletions
Showing only changes of commit 987413ab3a - Show all commits

View file

@ -225,13 +225,19 @@ EOF
chmod 600 "$GANDI_ENV" chmod 600 "$GANDI_ENV"
# Create Caddyfile with admin API and wildcard cert # 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" CADDYFILE="/etc/caddy/Caddyfile"
cat > "$CADDYFILE" <<EOF cat > "$CADDYFILE" <<'CADDYEOF'
# Caddy configuration for edge control plane # Caddy configuration for edge control plane
# Admin API enabled on 127.0.0.1:2019 # Admin API enabled on 127.0.0.1:2019
{ {
admin localhost:2019 admin localhost:2019
servers {
name edge
}
} }
# Default site (reverse proxy for edge tunnels will be added dynamically) # Default site (reverse proxy for edge tunnels will be added dynamically)
@ -240,7 +246,7 @@ cat > "$CADDYFILE" <<EOF
dns gandi {env.GANDI_API_KEY} dns gandi {env.GANDI_API_KEY}
} }
} }
EOF CADDYEOF
# Start Caddy # Start Caddy
systemctl restart caddy 2>/dev/null || { systemctl restart caddy 2>/dev/null || {

View file

@ -19,6 +19,24 @@ CADDY_ADMIN_URL="${CADDY_ADMIN_URL:-http://127.0.0.1:2019}"
# Domain suffix for projects # Domain suffix for projects
DOMAIN_SUFFIX="${DOMAIN_SUFFIX:-disinto.ai}" 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 # Add a route for a project
# Usage: add_route <project> <port> # Usage: add_route <project> <port>
add_route() { add_route() {
@ -26,6 +44,9 @@ add_route() {
local port="$2" local port="$2"
local fqdn="${project}.${DOMAIN_SUFFIX}" local fqdn="${project}.${DOMAIN_SUFFIX}"
local server_name
server_name=$(_discover_server_name) || return 1
# Build the route configuration (partial config) # Build the route configuration (partial config)
local route_config local route_config
route_config=$(cat <<EOF route_config=$(cat <<EOF
@ -58,16 +79,21 @@ add_route() {
EOF EOF
) )
# Append route using POST /config/apps/http/servers/edge/routes # Append route via admin API, checking HTTP status
local response local response status body
response=$(curl -s -X POST \ response=$(curl -sS -w '\n%{http_code}' -X POST \
"${CADDY_ADMIN_URL}/config/apps/http/servers/edge/routes" \ "${CADDY_ADMIN_URL}/config/apps/http/servers/${server_name}/routes" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$route_config" 2>&1) || { -d "$route_config") || {
echo "Error: failed to add route for ${fqdn}" >&2 echo "Error: failed to add route for ${fqdn}" >&2
echo "Response: ${response}" >&2
return 1 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 echo "Added route: ${fqdn} → 127.0.0.1:${port}" >&2
} }
@ -78,31 +104,45 @@ remove_route() {
local project="$1" local project="$1"
local fqdn="${project}.${DOMAIN_SUFFIX}" local fqdn="${project}.${DOMAIN_SUFFIX}"
# First, get current routes local server_name
local routes_json server_name=$(_discover_server_name) || return 1
routes_json=$(curl -s "${CADDY_ADMIN_URL}/config/apps/http/servers/edge/routes" 2>&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 echo "Error: failed to get current routes" >&2
return 1 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 # Find the route index that matches our fqdn using jq
local route_index 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 if [ -z "$route_index" ] || [ "$route_index" = "null" ]; then
echo "Warning: route for ${fqdn} not found" >&2 echo "Warning: route for ${fqdn} not found" >&2
return 0 return 0
fi fi
# Delete the route at the found index # Delete the route at the found index, checking HTTP status
local response response=$(curl -sS -w '\n%{http_code}' -X DELETE \
response=$(curl -s -X DELETE \ "${CADDY_ADMIN_URL}/config/apps/http/servers/${server_name}/routes/${route_index}" \
"${CADDY_ADMIN_URL}/config/apps/http/servers/edge/routes/${route_index}" \ -H "Content-Type: application/json") || {
-H "Content-Type: application/json" 2>&1) || {
echo "Error: failed to remove route for ${fqdn}" >&2 echo "Error: failed to remove route for ${fqdn}" >&2
echo "Response: ${response}" >&2
return 1 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 echo "Removed route: ${fqdn}" >&2
} }
@ -110,13 +150,18 @@ remove_route() {
# Reload Caddy to apply configuration changes # Reload Caddy to apply configuration changes
# Usage: reload_caddy # Usage: reload_caddy
reload_caddy() { reload_caddy() {
local response local response status body
response=$(curl -s -X POST \ response=$(curl -sS -w '\n%{http_code}' -X POST \
"${CADDY_ADMIN_URL}/reload" 2>&1) || { "${CADDY_ADMIN_URL}/reload") || {
echo "Error: failed to reload Caddy" >&2 echo "Error: failed to reload Caddy" >&2
echo "Response: ${response}" >&2
return 1 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 echo "Caddy reloaded" >&2
} }