/** * DashCaddy Installer Wizard * A step-by-step installation wizard for DashCaddy */ // Wizard State const state = { currentStep: 0, platform: null, paths: { install: '', dockerData: '', caddyConfig: '' }, dependencies: { docker: { installed: false, version: null, checking: true }, caddy: { installed: false, version: null, checking: true } }, // Tier selection tier: 'intermediate', // DNS configuration dns: { enabled: false, serverUrl: '', username: '', password: '', token: '', testResult: null }, // Dashboard branding branding: { name: 'DashCaddy', title: 'DashCaddy Dashboard', primaryColor: '#6366f1', logoSourcePath: '', dashboardPort: 8080, apiPort: 3001 }, // Access mode domainMode: 'local', // 'local' | 'public' | 'custom-tld' domain: { publicDomain: '', tld: '.home', email: '', caName: 'DashCaddy Local CA' }, // Detected network IPs network: { lanIP: '', tailscaleIP: '', detected: false }, installation: { status: 'pending', // pending, running, complete, error progress: 0, currentTask: '', completedTasks: [], error: null }, result: { dashboardUrl: '', installPath: '', health: null }, // Uninstall mode uninstallMode: false, uninstall: { step: 'confirm', // 'confirm' | 'progress' | 'complete' installPath: '', config: null, detected: false, detecting: false, preserveSettings: true, preserveCA: true, status: 'pending', // pending, running, complete, error progress: 0, currentTask: '', completedTasks: [], error: null, settingsPath: null } }; // Step definitions (8 steps) const steps = [ { id: 'welcome', title: 'Welcome' }, { id: 'dependencies', title: 'Dependencies' }, { id: 'folder', title: 'Install Path' }, { id: 'tier', title: 'Tier' }, { id: 'access', title: 'Access' }, { id: 'dns', title: 'DNS' }, { id: 'dashboard', title: 'Dashboard' }, { id: 'install', title: 'Install' }, { id: 'complete', title: 'Complete' } ]; // DOM Elements let root; // Initialize wizard async function initWizard() { root = document.getElementById('root'); // Check platform try { const result = await window.electronAPI.checkPlatform(); if (result.success) { state.platform = result.data; setDefaultPaths(); } } catch (err) { console.error('Platform detection failed:', err); } // Detect network IPs try { const netResult = await window.electronAPI.detectNetwork(); if (netResult.success) { state.network.lanIP = netResult.data.lanIP || ''; state.network.tailscaleIP = netResult.data.tailscaleIP || ''; state.network.detected = true; } } catch (err) { console.warn('Network detection failed:', err); } // Setup event listeners for installation progress setupEventListeners(); // Render initial state render(); } // Set default paths based on platform function setDefaultPaths() { const os = state.platform?.os || 'windows'; if (os === 'windows') { state.paths.install = 'C:\\DashCaddy'; state.paths.dockerData = 'C:\\DashCaddy\\docker-data'; state.paths.caddyConfig = 'C:\\DashCaddy\\caddy'; } else if (os === 'macos') { state.paths.install = '/Applications/DashCaddy'; state.paths.dockerData = '/Applications/DashCaddy/docker-data'; state.paths.caddyConfig = '/Applications/DashCaddy/caddy'; } else { state.paths.install = '/opt/dashcaddy'; state.paths.dockerData = '/opt/dashcaddy/docker-data'; state.paths.caddyConfig = '/opt/dashcaddy/caddy'; } } // Setup IPC event listeners function setupEventListeners() { window.electronAPI.onStepProgress((data) => { state.installation.progress = data.progress; state.installation.currentTask = data.task; render(); }); window.electronAPI.onStepComplete((data) => { state.installation.completedTasks.push(data.step); render(); }); window.electronAPI.onStepError((data) => { state.installation.status = 'error'; state.installation.error = data.error; render(); }); window.electronAPI.onInstallationComplete((data) => { state.installation.status = 'complete'; state.result.dashboardUrl = data.dashboardUrl; state.result.installPath = data.installPath; state.result.health = data.health || null; state.currentStep = 8; // Move to complete step render(); }); // Uninstall progress listeners window.electronAPI.onUninstallProgress((data) => { state.uninstall.progress = data.progress; state.uninstall.currentTask = data.task; render(); }); window.electronAPI.onUninstallStepComplete((data) => { state.uninstall.completedTasks.push(data.step); render(); }); window.electronAPI.onUninstallComplete((data) => { state.uninstall.status = 'complete'; state.uninstall.step = 'complete'; state.uninstall.settingsPath = data.settingsPath || null; render(); }); window.electronAPI.onUninstallError((data) => { state.uninstall.status = 'error'; state.uninstall.error = data.error; render(); }); } // Navigation function nextStep() { if (state.currentStep < steps.length - 1) { state.currentStep++; // Trigger actions on step enter switch (state.currentStep) { case 1: // Dependencies checkDependencies(); break; case 7: // Installation startInstallation(); break; } render(); } } function prevStep() { if (state.currentStep > 0) { state.currentStep--; render(); } } // Check dependencies async function checkDependencies() { state.dependencies.docker.checking = true; state.dependencies.caddy.checking = true; render(); try { // Pass user-configured caddy path so the checker can find caddy.exe there const caddySearchPaths = []; if (state.paths.caddyConfig) { caddySearchPaths.push(state.paths.caddyConfig); } if (state.paths.install) { const sep = state.platform?.os === 'windows' ? '\\' : '/'; caddySearchPaths.push(state.paths.install + sep + 'caddy'); caddySearchPaths.push(state.paths.install); } const result = await window.electronAPI.checkDependencies({ caddySearchPaths }); if (result.success) { state.dependencies.docker = { installed: result.data.docker.installed, version: result.data.docker.version, checking: false }; state.dependencies.caddy = { installed: result.data.caddy.installed, version: result.data.caddy.version, checking: false }; } } catch (err) { console.error('Dependency check failed:', err); state.dependencies.docker.checking = false; state.dependencies.caddy.checking = false; } render(); } // Install dependency async function installDependency(type) { state.dependencies[type].checking = true; render(); try { if (type === 'docker') { await window.electronAPI.installDocker(); } else { await window.electronAPI.installCaddy({ targetPath: state.paths.caddyConfig || null }); } await checkDependencies(); } catch (err) { console.error(`${type} installation failed:`, err); state.dependencies[type].checking = false; render(); } } // Select folder async function selectFolder(pathKey) { try { const result = await window.electronAPI.selectFolder(); if (result.success && result.path) { state.paths[pathKey] = result.path; // Auto-set child paths if main install path changes if (pathKey === 'install') { const sep = state.platform?.os === 'windows' ? '\\' : '/'; state.paths.dockerData = result.path + sep + 'docker-data'; state.paths.caddyConfig = result.path + sep + 'caddy'; } render(); } } catch (err) { console.error('Folder selection failed:', err); } } // Tier selection function selectTier(tierId) { state.tier = tierId; // Reset DNS if downgrading from advanced if (tierId !== 'advanced') { state.dns.enabled = false; } render(); } // Access mode functions function selectDomainMode(mode) { state.domainMode = mode; render(); } function updateDomain(field, value) { state.domain[field] = value; if (field === 'tld' && value && !value.startsWith('.')) { state.domain.tld = '.' + value; } } // DNS functions function toggleDNS(enabled) { state.dns.enabled = enabled; render(); } function updateDNS(field, value) { state.dns[field] = value; } async function testDNSConnection() { state.dns.testResult = null; render(); try { const result = await window.electronAPI.testDNSConnection({ server: state.dns.serverUrl, username: state.dns.username, password: state.dns.password, token: state.dns.token }); state.dns.testResult = { success: result.success && result.data?.connected, message: result.data?.message || (result.success ? 'Connected' : result.error) }; } catch (err) { state.dns.testResult = { success: false, message: err.message }; } render(); } // Branding functions function updateBranding(field, value) { state.branding[field] = value; // Re-render for color to sync both inputs if (field === 'primaryColor') render(); } async function selectLogo() { try { const result = await window.electronAPI.selectFile({ title: 'Select Dashboard Logo', filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'svg'] }] }); if (result.success && result.path) { state.branding.logoSourcePath = result.path; render(); } } catch (err) { console.error('Logo selection failed:', err); } } // Start installation async function startInstallation() { state.installation.status = 'running'; state.installation.progress = 0; state.installation.completedTasks = []; state.installation.error = null; render(); try { await window.electronAPI.runInstallation({ installPath: state.paths.install, dockerDataPath: state.paths.dockerData, caddyConfigPath: state.paths.caddyConfig, tier: state.tier, dashboardPort: state.branding.dashboardPort, apiPort: state.branding.apiPort, domainMode: state.domainMode, domain: state.domain, lanIP: state.network.lanIP, tailscaleIP: state.network.tailscaleIP, branding: { name: state.branding.name, title: state.branding.title, primaryColor: state.branding.primaryColor, logoSourcePath: state.branding.logoSourcePath || null }, dns: state.dns.enabled ? { server: state.dns.serverUrl, username: state.dns.username, password: state.dns.password, token: state.dns.token } : null, autoStart: true }); } catch (err) { state.installation.status = 'error'; state.installation.error = err.message; render(); } } // Open dashboard async function openDashboard() { try { await window.electronAPI.openDashboard(state.branding.dashboardPort || 8080); } catch (err) { console.error('Failed to open dashboard:', err); } } // Render functions function render() { root.innerHTML = renderContainer(); } function renderContainer() { if (state.uninstallMode) { return `
The unified management platform for your home lab. This wizard will guide you through the installation process.
DashCaddy requires Docker and Caddy to be installed. We'll check and install them for you.
${docker.checking ? 'Checking...' : docker.installed ? `Version ${docker.version}` : 'Not installed'}
${caddy.checking ? 'Checking...' : caddy.installed ? `Version ${caddy.version}` : 'Not installed'}
Select where DashCaddy and its components should be installed.
Main folder where DashCaddy will be installed
Where Docker containers will store their data (volumes)
Where Caddyfile and SSL certificates will be stored
Select the level of functionality you need. You can upgrade later.
${tier.description}
Choose how you want to reach your dashboard and services on the network.
${mode.description}
${mode.note}
The domain where your dashboard will be accessible
Used for certificate expiry notifications (required by Let's Encrypt)
Your dashboard will be at dashcaddy${escapeHtml(state.domain.tld)}, services at <name>${escapeHtml(state.domain.tld)}
Name for your internal Certificate Authority
If you add a DNS server later, you can configure it from the DashCaddy dashboard.
DashCaddy can optionally manage a local DNS server for additional services. You can configure this later.
You'll need to manually configure your DNS server to resolve *${escapeHtml(state.domain.tld)} domains to this machine's IP address.
Configure your Technitium DNS server connection for automatic DNS record management.
Technitium DNS server address including port
Stored encrypted on disk
Alternative to username/password authentication
Customize your DashCaddy dashboard appearance and ports.
Displayed in the dashboard header
PNG or SVG, recommended 200x120px. Leave empty for default logo.
Port where the dashboard will be accessible (default: 8080)
Port for the DashCaddy API server (default: 3001)
Please wait while we set up DashCaddy on your system.
${currentTask || 'Starting installation...'}
${escapeHtml(state.branding.name)} has been successfully installed on your system.
This will remove DashCaddy from your system. Services will be stopped and files removed.
${u.detecting ? `Please wait while DashCaddy is being removed...
DashCaddy has been removed from your system.
${preserved.length > 0 ? `These will be automatically restored when you reinstall to the same path.