Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
246 lines
6.9 KiB
JavaScript
246 lines
6.9 KiB
JavaScript
// ========== 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 = '<option value="">No CAs found in Caddyfile</option>';
|
|
} else {
|
|
select.innerHTML = '<option value="">Select existing CA...</option>';
|
|
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 = '<option value="">Error loading CAs</option>';
|
|
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('dns2', '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;
|
|
|
|
})();
|