// ========== 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;
})();