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.
This commit is contained in:
Brückner
2026-06-08 13:37:22 +02:00
parent 250c347f58
commit acadf8db7c
2 changed files with 17 additions and 11 deletions

View File

@ -1192,6 +1192,8 @@ async function startServer() {
hostname?: string; upstream?: string; tls?: boolean; compress?: boolean; hostname?: string; upstream?: string; tls?: boolean; compress?: boolean;
}; };
if (!hostname || !upstream) return res.status(400).json({ error: 'hostname and upstream are required.' }); 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); const route = addCaddyRoute(hostname.trim(), upstream.trim(), tls !== false, compress !== false);
db.prepare('INSERT INTO logs (id, timestamp, type, message, deviceId, userId) VALUES (?, ?, ?, ?, ?, ?)') db.prepare('INSERT INTO logs (id, timestamp, type, message, deviceId, userId) VALUES (?, ?, ?, ?, ?, ?)')
.run(uid('log'), new Date().toISOString(), 'system', .run(uid('log'), new Date().toISOString(), 'system',

View File

@ -357,21 +357,25 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
} }
async function loadCaddyRoutes() { async function loadCaddyRoutes() {
try { const [statusResult, routesResult] = await Promise.allSettled([
const [statusRes, routesRes] = await Promise.all([
authFetch('/api/caddy/status'), authFetch('/api/caddy/status'),
authFetch('/api/caddy/routes'), authFetch('/api/caddy/routes'),
]); ]);
if (statusRes.ok) { if (statusResult.status === 'fulfilled' && statusResult.value.ok) {
const s = await statusRes.json(); try {
const s = await statusResult.value.json();
setCaddyStatus(s.available ? 'available' : 'unavailable'); setCaddyStatus(s.available ? 'available' : 'unavailable');
}
if (routesRes.ok) {
setCaddyRoutes(await routesRes.json());
}
} catch { } catch {
setCaddyStatus('unavailable'); setCaddyStatus('unavailable');
} }
} else {
setCaddyStatus('unavailable');
}
if (routesResult.status === 'fulfilled' && routesResult.value.ok) {
try {
setCaddyRoutes(await routesResult.value.json());
} catch {}
}
} }
async function handleAddRoute() { async function handleAddRoute() {