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:
Sami
2026-03-05 02:26:12 -08:00
commit f61e85d9a7
337 changed files with 75282 additions and 0 deletions
@@ -0,0 +1,375 @@
const fs = require('fs');
const path = require('path');
const { DEFAULT_PORTS } = require('../shared/constants');
/**
* CaddyfileGenerator - Creates Caddyfile and docker-compose.yml for DashCaddy
*
* Generates production-grade configs that match the patterns used by the
* running DashCaddy deployment (CORS snippets, admin origins, PKI, etc.)
*/
class CaddyfileGenerator {
/**
* Normalize a Windows path to forward slashes for Caddyfile/Docker Compose
*/
_p(s) {
return s.replace(/\\/g, '/');
}
/**
* Generate the CORS snippet block used by the API
*/
_corsSnippets() {
return `(cors-preflight) {
@preflight method OPTIONS
header @preflight {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
Access-Control-Allow-Headers "*"
Access-Control-Max-Age "600"
}
respond @preflight 204
}
(cors-allow) {
header {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
Access-Control-Allow-Headers "*"
Access-Control-Max-Age "600"
}
}
`;
}
/**
* Generate the dashcaddy_auth snippet for SSO
*/
_authSnippet(apiPort) {
return `# DashCaddy SSO auth snippet
(dashcaddy_auth) {
forward_auth localhost:${apiPort} {
uri /api/auth/gate/{args[0]}
copy_headers Authorization X-Api-Key X-App-Cookie X-Emby-Token X-Plex-Token
}
}
`;
}
/**
* Generate the dashboard site block content (shared across all modes)
*/
_dashboardBlock(dashboardPath, tier, apiPort) {
let block = '';
block += ` root * ${this._p(dashboardPath)}\n`;
block += ` encode gzip\n\n`;
// API proxy for intermediate/advanced tiers
if (tier !== 'basic') {
block += ` # API proxy to Docker container\n`;
block += ` handle /api/* {\n`;
block += ` reverse_proxy localhost:${apiPort}\n`;
block += ` }\n\n`;
}
// SPA fallback with file_server
block += ` # Static site + SPA fallback\n`;
block += ` handle {\n`;
block += ` @notFile not file {path}\n`;
block += ` rewrite @notFile /index.html\n`;
block += ` file_server\n`;
block += ` }\n`;
return block;
}
/**
* Generate Caddyfile for local mode (IP:Port, no TLS)
*/
generateCaddyfile(options) {
const {
dashboardPath,
port = 8080,
tier = 'basic',
apiPort = DEFAULT_PORTS.API
} = options;
const adminPort = DEFAULT_PORTS.CADDY_ADMIN;
let caddyfile = `# DashCaddy Caddyfile - Local Mode
# Generated by DashCaddy Installer
{
admin localhost:${adminPort} {
origins localhost localhost:${adminPort} host.docker.internal host.docker.internal:${adminPort}
}
auto_https off
}
`;
if (tier !== 'basic') {
caddyfile += this._corsSnippets() + '\n';
}
caddyfile += `:${port} {\n`;
caddyfile += this._dashboardBlock(dashboardPath, tier, apiPort);
caddyfile += `}\n`;
return caddyfile;
}
/**
* Generate Caddyfile for public domain mode (Let's Encrypt TLS)
*/
generateCaddyfileWithDomain(options) {
const {
dashboardPath,
domain,
email = '',
tier = 'basic',
apiPort = DEFAULT_PORTS.API
} = options;
const adminPort = DEFAULT_PORTS.CADDY_ADMIN;
let caddyfile = `# DashCaddy Caddyfile - Public Domain Mode
# Generated by DashCaddy Installer
{
admin localhost:${adminPort} {
origins localhost localhost:${adminPort} host.docker.internal host.docker.internal:${adminPort}
}
`;
if (email) {
caddyfile += ` email ${email}\n`;
}
caddyfile += `}\n\n`;
if (tier !== 'basic') {
caddyfile += this._corsSnippets() + '\n';
caddyfile += this._authSnippet(apiPort) + '\n';
}
caddyfile += `${domain} {\n`;
caddyfile += this._dashboardBlock(dashboardPath, tier, apiPort);
caddyfile += `}\n`;
return caddyfile;
}
/**
* Generate Caddyfile for custom TLD mode (internal CA)
*/
generateCaddyfileCustomTLD(options) {
const {
dashboardPath,
installPath,
tld = '.home',
caName = 'DashCaddy Local CA',
tier = 'basic',
apiPort = DEFAULT_PORTS.API
} = options;
const adminPort = DEFAULT_PORTS.CADDY_ADMIN;
const dashboardDomain = `dashcaddy${tld}`;
const certsPath = installPath ? this._p(path.join(installPath, 'certs')) : './certs';
let caddyfile = `# DashCaddy Caddyfile - Custom TLD Mode (${tld})
# Generated by DashCaddy Installer
{
storage file_system {
root ${certsPath}
}
admin localhost:${adminPort} {
origins localhost localhost:${adminPort} host.docker.internal host.docker.internal:${adminPort}
}
pki {
ca local {
name "${caName}"
root_cn "${caName} Root CA"
intermediate_cn "${caName} Intermediate CA"
}
}
}
`;
if (tier !== 'basic') {
caddyfile += this._corsSnippets() + '\n';
caddyfile += this._authSnippet(apiPort) + '\n';
}
caddyfile += `# Dashboard - ${dashboardDomain}\n`;
caddyfile += `${dashboardDomain} {\n`;
caddyfile += ` tls internal\n\n`;
caddyfile += this._dashboardBlock(dashboardPath, tier, apiPort);
caddyfile += `}\n`;
return caddyfile;
}
/**
* Write Caddyfile to disk
*/
async writeCaddyfile(content, outputPath) {
try {
await fs.promises.writeFile(outputPath, content, 'utf8');
return { success: true, path: outputPath };
} catch (error) {
return { success: false, error: error.message };
}
}
/**
* Create complete Caddyfile setup
* @param {string} installPath - Installation directory
* @param {Object} options - Configuration options
*/
async createCaddyfileSetup(installPath, options = {}) {
try {
const dashboardPath = path.join(installPath, 'sites', 'status');
const caddyfilePath = path.join(installPath, 'Caddyfile');
let content;
if (options.domainMode === 'public') {
content = this.generateCaddyfileWithDomain({
dashboardPath,
domain: options.publicDomain,
email: options.email,
tier: options.tier,
apiPort: options.apiPort
});
} else if (options.domainMode === 'custom-tld') {
content = this.generateCaddyfileCustomTLD({
dashboardPath,
installPath,
tld: options.tld,
caName: options.caName || 'DashCaddy Local CA',
tier: options.tier,
apiPort: options.apiPort
});
} else {
content = this.generateCaddyfile({
dashboardPath,
...options
});
}
const result = await this.writeCaddyfile(content, caddyfilePath);
if (!result.success) {
throw new Error(result.error);
}
return {
success: true,
path: caddyfilePath,
content
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Generate docker-compose.yml for running the API server
* @param {string} installPath - Installation directory
* @param {Object} options - Configuration options
* @param {number} options.apiPort - API server port
* @param {string} options.lanIP - Host LAN IP address
* @param {string} options.tailscaleIP - Host Tailscale IP address
* @param {string} options.domainMode - Domain mode (local, public, custom-tld)
*/
generateDockerCompose(installPath, options = {}) {
const apiPort = options.apiPort || DEFAULT_PORTS.API;
const adminPort = DEFAULT_PORTS.CADDY_ADMIN;
const p = this._p.bind(this);
// Core volume mounts
let volumes = ` - ${p(installPath)}/Caddyfile:/caddyfile:rw
- ${p(installPath)}/services.json:/app/services.json:rw
- ${p(installPath)}/dns-credentials.json:/app/dns-credentials.json:rw
- ${p(installPath)}/config.json:/app/config.json:rw
- ${p(installPath)}/credentials.json:/app/credentials.json:rw
- ${p(installPath)}/.encryption-key:/app/.encryption-key:rw
- ${p(installPath)}/notifications.json:/app/notifications.json:rw
- ${p(installPath)}/sites/status/assets:/app/assets:rw
- /var/run/docker.sock:/var/run/docker.sock`;
// Add CA cert mount for custom-tld mode
if (options.domainMode === 'custom-tld') {
volumes += `\n - ${p(installPath)}/certs/pki/authorities/local:/app/pki:ro`;
}
// Environment variables
let envVars = ` - CADDYFILE_PATH=/caddyfile
- CADDY_ADMIN_URL=http://host.docker.internal:${adminPort}
- ASSETS_PATH=/app/assets
- CREDENTIALS_FILE=/app/credentials.json
- NODE_ENV=production`;
if (options.domainMode === 'custom-tld') {
envVars += `\n - CA_CERT_PATH=/app/pki/root.crt`;
}
if (options.lanIP) {
envVars += `\n - HOST_LAN_IP=${options.lanIP}`;
}
if (options.tailscaleIP) {
envVars += `\n - HOST_TAILSCALE_IP=${options.tailscaleIP}`;
}
const dockerCompose = `services:
dashcaddy-api:
build: .
container_name: dashcaddy-api
ports:
- "${apiPort}:${apiPort}"
volumes:
${volumes}
environment:
${envVars}
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
`;
return dockerCompose;
}
/**
* Write docker-compose.yml to disk
* @param {string} installPath - Installation directory
* @param {Object} options - Configuration options
*/
async createDockerCompose(installPath, options = {}) {
try {
const content = this.generateDockerCompose(installPath, options);
const apiDir = path.join(installPath, 'sites', 'dashcaddy-api');
await fs.promises.mkdir(apiDir, { recursive: true });
const outputPath = path.join(apiDir, 'docker-compose.yml');
await fs.promises.writeFile(outputPath, content, 'utf8');
return {
success: true,
path: outputPath,
content
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
module.exports = CaddyfileGenerator;