From acadf8db7c706d7c014fb383b4ed3ac10fa9279e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=BCckner?= Date: Mon, 8 Jun 2026 13:37:22 +0200 Subject: [PATCH] fix(caddy): prevent duplicate routes and make status/routes fetches independent POST /api/caddy/routes now returns 409 if the hostname already exists, preventing duplicate DB entries that cause Caddy's "ambiguous site definition" error. loadCaddyRoutes uses Promise.allSettled so a failure in the status check can no longer silently prevent the routes list from loading. --- server.ts | 2 ++ src/components/Settings.tsx | 26 +++++++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/server.ts b/server.ts index 6c76a73..1e9a9ea 100644 --- a/server.ts +++ b/server.ts @@ -1192,6 +1192,8 @@ async function startServer() { hostname?: string; upstream?: string; tls?: boolean; compress?: boolean; }; if (!hostname || !upstream) return res.status(400).json({ error: 'hostname and upstream are required.' }); + if (getCaddyRoutes().some(r => r.hostname === hostname.trim())) + return res.status(409).json({ error: `Route for ${hostname.trim()} already exists.` }); const route = addCaddyRoute(hostname.trim(), upstream.trim(), tls !== false, compress !== false); db.prepare('INSERT INTO logs (id, timestamp, type, message, deviceId, userId) VALUES (?, ?, ?, ?, ?, ?)') .run(uid('log'), new Date().toISOString(), 'system', diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 00dc9b8..886cd53 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -357,21 +357,25 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) { } async function loadCaddyRoutes() { - try { - const [statusRes, routesRes] = await Promise.all([ - authFetch('/api/caddy/status'), - authFetch('/api/caddy/routes'), - ]); - if (statusRes.ok) { - const s = await statusRes.json(); + const [statusResult, routesResult] = await Promise.allSettled([ + authFetch('/api/caddy/status'), + authFetch('/api/caddy/routes'), + ]); + if (statusResult.status === 'fulfilled' && statusResult.value.ok) { + try { + const s = await statusResult.value.json(); setCaddyStatus(s.available ? 'available' : 'unavailable'); + } catch { + setCaddyStatus('unavailable'); } - if (routesRes.ok) { - setCaddyRoutes(await routesRes.json()); - } - } catch { + } else { setCaddyStatus('unavailable'); } + if (routesResult.status === 'fulfilled' && routesResult.value.ok) { + try { + setCaddyRoutes(await routesResult.value.json()); + } catch {} + } } async function handleAddRoute() {