// ========== SERVICE INFRASTRUCTURE ========== // Caddy config generation, DNS record creation, and service registration. (function () { // ===== LOAD EXISTING CAs ===== async function loadExistingCAs(caddyfilePath) { try { const response = await fetch(`/api/v1/caddy/cas?caddyfilePath=${encodeURIComponent(caddyfilePath)}`); if (!response.ok) { throw new Error(`Failed to load CAs: ${response.status}`); } const result = await response.json(); if (result.status === 'success') { const select = document.getElementById('existing-ca-select'); select.innerHTML = ''; if (result.data.cas.length === 0) { select.innerHTML = ''; } else { select.innerHTML = ''; result.data.cas.forEach(ca => { const option = document.createElement('option'); if (typeof ca === 'object') { option.value = ca.id; option.textContent = ca.displayName || ca.name; } else { option.value = ca; option.textContent = ca; } select.appendChild(option); }); } return result.data.cas; } else { throw new Error(result.message); } } catch (error) { console.error('Error loading CAs:', error); const select = document.getElementById('existing-ca-select'); select.innerHTML = ''; return []; } } // ===== GENERATE CADDY CONFIG ===== function generateCaddyConfig(config) { const { subdomain, port, ip, sslType, caName, existingCa, enableAuth, enableCors, customHeaders, upstreamPath, healthCheck, timeout, tailscaleOnly } = config; let caddyConfig = `${buildDomain(subdomain)} {\n`; // Tailscale-only access restriction if (tailscaleOnly) { caddyConfig += ` @blocked not remote_ip 100.64.0.0/10\n`; caddyConfig += ` respond @blocked "Access denied. Tailscale connection required." 403\n`; } // SSL Configuration switch (sslType) { case 'letsencrypt': break; case 'caddy-managed': caddyConfig += ` tls internal\n`; break; case 'existing-ca': if (existingCa) { caddyConfig += ` tls {\n ca ${existingCa}\n }\n`; } break; case 'custom-ca': if (caName) { caddyConfig += ` tls {\n ca ${caName}\n }\n`; } break; } // Authentication if (enableAuth) { caddyConfig += ` basicauth {\n admin $2a$14$hashed_password_here\n }\n`; } // CORS Headers if (enableCors) { caddyConfig += ` header {\n`; caddyConfig += ` Access-Control-Allow-Origin "*"\n`; caddyConfig += ` Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"\n`; caddyConfig += ` Access-Control-Allow-Headers "Content-Type, Authorization"\n`; caddyConfig += ` }\n`; } // Custom Headers if (customHeaders) { try { const headers = JSON.parse(customHeaders); caddyConfig += ` header {\n`; Object.entries(headers).forEach(([key, value]) => { caddyConfig += ` ${key} "${value}"\n`; }); caddyConfig += ` }\n`; } catch (e) { console.warn('Invalid JSON in custom headers'); } } // Health Check if (healthCheck) { caddyConfig += ` health_uri ${healthCheck}\n`; } // Reverse Proxy caddyConfig += ` reverse_proxy ${ip}:${port} {\n`; if (upstreamPath && upstreamPath !== '/') { caddyConfig += ` rewrite ${upstreamPath}\n`; } if (timeout && timeout !== 30) { caddyConfig += ` transport http {\n`; caddyConfig += ` dial_timeout ${timeout}s\n`; caddyConfig += ` response_header_timeout ${timeout}s\n`; caddyConfig += ` }\n`; } caddyConfig += ` }\n`; caddyConfig += `}\n`; return caddyConfig; } // ===== CREATE DNS RECORD ===== async function createDnsRecord(subdomain, ip, ttl = DC.DEFAULTS.TTL) { const dnsToken = window.getToken(getPrimaryDnsId(), 'admin'); if (!dnsToken) { throw new Error('DNS admin token not configured. Please set it in the Tokens menu.'); } const domain = buildDomain(subdomain); const response = await secureFetch('/api/v1/dns/record', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ domain: domain, ip: ip, ttl: ttl, token: dnsToken, server: SITE.dnsIp }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`DNS API Error: ${response.status} - ${errorText}`); } const result = await response.json(); if (!result.success) { throw new Error(`DNS Error: ${result.error || 'Unknown error'}`); } return result; } // ===== ADD SERVICE TO CONFIG ===== async function addServiceToConfig(serviceConfig) { const newService = { id: serviceConfig.subdomain, name: serviceConfig.name, logo: serviceConfig.logo || `/assets/${serviceConfig.subdomain}.png` }; try { const response = await secureFetch('/api/v1/services', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newService) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to save service'); } await window.loadServices(); window.buildGrid(); return newService; } catch (error) { console.error('Failed to add service to config:', error); throw error; } } // ===== ADD TO CADDYFILE ===== async function addToCaddyfile(config) { const subdomain = document.getElementById('service-subdomain-input').value.trim(); const ip = document.getElementById('service-ip-input').value.trim() || 'localhost'; const port = document.getElementById('service-port-input').value.trim() || '80'; const response = await secureFetch('/api/v1/site', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ domain: buildDomain(subdomain), upstream: `${ip}:${port}`, config: config }) }); const result = await response.json(); if (!response.ok || !result.success) { throw new Error(result.error || `Caddy API Error: ${response.status}`); } return result; } // ===== WINDOW EXPORTS ===== window.loadExistingCAs = loadExistingCAs; window.generateCaddyConfig = generateCaddyConfig; window.createDnsRecord = createDnsRecord; window.addServiceToConfig = addServiceToConfig; window.addToCaddyfile = addToCaddyfile; })();