Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
245
status/js/core/service-infrastructure.js
Normal file
245
status/js/core/service-infrastructure.js
Normal file
@@ -0,0 +1,245 @@
|
||||
// ========== 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;
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user