[grade=B] Align premium marketing and documentation
This commit is contained in:
@@ -117,7 +117,7 @@ export default function AboutPage() {
|
||||
{ name: "Express", role: "REST API + WebSocket", icon: "⚡" },
|
||||
{ name: "MCP", role: "AI Protocol Layer", icon: "🤖" },
|
||||
{ name: "Stripe", role: "Payment Processing", icon: "💳" },
|
||||
{ name: "Jest", role: "Testing (1770+ tests)", icon: "✅" },
|
||||
{ name: "Jest", role: "Testing (2200+ tests)", icon: "✅" },
|
||||
].map((tech) => (
|
||||
<div
|
||||
key={tech.name}
|
||||
@@ -140,9 +140,9 @@ export default function AboutPage() {
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||
{[
|
||||
{ value: "93", label: "App Templates" },
|
||||
{ value: "1770+", label: "Test Cases" },
|
||||
{ value: "260+", label: "API Endpoints" },
|
||||
{ value: "77", label: "App Templates" },
|
||||
{ value: "2200+", label: "Test Cases" },
|
||||
{ value: "350+", label: "API Endpoints" },
|
||||
{ value: "31", label: "Languages" },
|
||||
].map((stat) => (
|
||||
<div key={stat.label} className="text-center">
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function DocsFirstServicePage() {
|
||||
|
||||
<h2>Three ways to add a service</h2>
|
||||
|
||||
<h3>1. Pick from 93 one-click templates</h3>
|
||||
<h3>1. Pick from 77 one-click templates</h3>
|
||||
<p>
|
||||
The template library covers the most popular self-hosted applications — media servers, dashboards,
|
||||
databases, note apps, automation tools, and more. Each template bundles sane defaults for ports,
|
||||
@@ -62,7 +62,7 @@ export default function DocsFirstServicePage() {
|
||||
<h3>Step 1: Open the template library</h3>
|
||||
<p>
|
||||
From the dashboard sidebar, click <strong>New Service → From Template</strong>. The template library
|
||||
opens with a searchable grid of 93 applications.
|
||||
opens with a searchable grid of 77 applications.
|
||||
</p>
|
||||
|
||||
<h3>Step 2: Find Plex</h3>
|
||||
|
||||
+223
-317
@@ -8,375 +8,281 @@ export default function DocsInstallationPage() {
|
||||
<Navbar />
|
||||
<DocsLayout
|
||||
title="Installation Guide"
|
||||
intro="DashCaddy ships as a Docker-based deployment with a guided installer that handles dependency checks, configuration generation, and first launch. This guide covers prerequisites, the installer flow, and what to expect on first run."
|
||||
intro="DashCaddy installs in three commands on any Linux box — a desktop, a NUC, a Raspberry Pi 5, or a VPS. The installer handles Docker, Caddy, data volumes, the setup wizard, and your first login. This guide walks you through picking a hostname, running the installer, what the wizard does, and how to harden the host right after."
|
||||
>
|
||||
<h2>System requirements</h2>
|
||||
<p>Before installing DashCaddy, verify that your host meets these minimum requirements:</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-surface-700">
|
||||
<th className="py-3 pr-4 font-semibold text-surface-200">Component</th>
|
||||
<th className="py-3 pr-4 font-semibold text-surface-200">Minimum</th>
|
||||
<th className="py-3 font-semibold text-surface-200">Recommended</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-surface-300">
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Operating System</td>
|
||||
<td className="py-3 pr-4">Linux (Ubuntu 20.04+, Debian 11+, CentOS 8+)</td>
|
||||
<td className="py-3">Ubuntu 22.04 LTS or Debian 12</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">CPU</td>
|
||||
<td className="py-3 pr-4">2 cores</td>
|
||||
<td className="py-3">4+ cores</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">RAM</td>
|
||||
<td className="py-3 pr-4">2 GB</td>
|
||||
<td className="py-3">4+ GB (8 GB for 20+ services)</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Disk</td>
|
||||
<td className="py-3 pr-4">10 GB free</td>
|
||||
<td className="py-3">50+ GB SSD</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Docker</td>
|
||||
<td className="py-3 pr-4">20.10+</td>
|
||||
<td className="py-3">Latest stable</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Docker Compose</td>
|
||||
<td className="py-3 pr-4">v2.0+</td>
|
||||
<td className="py-3">Latest stable</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Node.js</td>
|
||||
<td className="py-3 pr-4">20.x LTS</td>
|
||||
<td className="py-3">20.x LTS or 22.x LTS</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Caddy</td>
|
||||
<td className="py-3 pr-4">2.6+ with Admin API</td>
|
||||
<td className="py-3">Latest stable</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-3 pr-4">Technitium DNS</td>
|
||||
<td className="py-3 pr-4">Optional</td>
|
||||
<td className="py-3">Latest stable (for auto DNS)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Quick start with start.sh</h2>
|
||||
<h2>Before you start</h2>
|
||||
<p>
|
||||
The fastest path to a running DashCaddy is the bundled <code>start.sh</code> script. It performs environment
|
||||
checks, pulls the required containers, generates configuration, and brings the stack up in a single command.
|
||||
You only need three things to install DashCaddy:
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>A Linux host that stays on</strong> — Ubuntu 22.04 LTS, Debian 12, or any recent Linux distribution. 2 GB RAM and a few GB of disk are enough to start.</li>
|
||||
<li><strong>Root or sudo access</strong> — the installer writes to <code>/etc/dashcaddy</code>, <code>/opt/dockerdata</code>, and the system Docker daemon.</li>
|
||||
<li><strong>A hostname</strong> — a domain you own, a custom-TLD like <code>dashcaddy.home</code>, or just your machine's IP for local-only use.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Pick your hostname</h2>
|
||||
<p>
|
||||
DashCaddy uses a single base hostname and creates subdomains under it for every service you add (for example
|
||||
<code> plex.dashcaddy.home</code>, <code>homeassistant.dashcaddy.home</code>). Choose the option that matches
|
||||
your situation:
|
||||
</p>
|
||||
|
||||
<h3>Step 1: Clone the repository</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl -fsSL https://get.dashcaddy.net | bash
|
||||
cd dashcaddy`}</code></pre>
|
||||
|
||||
<h3>Step 2: Make the launcher executable</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`chmod +x start.sh`}</code></pre>
|
||||
|
||||
<h3>Step 3: Run the installer</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`./start.sh`}</code></pre>
|
||||
|
||||
<h3>Option A — A real domain you own (recommended)</h3>
|
||||
<p>
|
||||
The script is idempotent — re-running it will reconcile the stack rather than clobber an existing install.
|
||||
If DashCaddy is already running, <code>start.sh</code> detects this and offers to update configuration or
|
||||
restart services instead of reinstalling from scratch.
|
||||
If you own <code>example.com</code>, register <code>dashcaddy.example.com</code> as a DNS A record pointing at
|
||||
your host's public IP. You can buy a domain for about $10/year from any registrar (Namecheap, Cloudflare,
|
||||
Porkbun, Google Domains). With a real domain, DashCaddy automatically gets a trusted Let's Encrypt
|
||||
certificate on first launch — no certificate warnings, no client setup.
|
||||
</p>
|
||||
|
||||
<h3>What start.sh does</h3>
|
||||
<p>Under the hood, the script performs these steps:</p>
|
||||
<ol>
|
||||
<li><strong>Dependency validation</strong> — checks for Docker, Docker Compose, Node.js, and Caddy</li>
|
||||
<li><strong>Port availability check</strong> — verifies ports 80, 443, and 2019 (Caddy Admin API) are free</li>
|
||||
<li><strong>Configuration generation</strong> — creates <code>.env</code> file with sensible defaults</li>
|
||||
<li><strong>Docker image pull</strong> — fetches the DashCaddy API and dashboard images</li>
|
||||
<li><strong>Container startup</strong> — launches the stack via Docker Compose</li>
|
||||
<li><strong>Health check</strong> — waits for the API to respond on <code>/healthz</code></li>
|
||||
<li><strong>Dashboard URL display</strong> — prints the access URL and initial admin credentials</li>
|
||||
</ol>
|
||||
|
||||
<h2>Docker Compose configuration</h2>
|
||||
<h3>Option B — A custom TLD on your home network</h3>
|
||||
<p>
|
||||
If you prefer to inspect or customize the Docker Compose configuration before launching, here's the
|
||||
default <code>docker-compose.yml</code> that <code>start.sh</code> generates:
|
||||
Use a domain like <code>dashcaddy.home</code>, <code>dashcaddy.lan</code>, or <code>dashcaddy.local</code>.
|
||||
These will not resolve on the public internet, which is exactly what you want for a home server. Two ways to
|
||||
make them resolve:
|
||||
</p>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`version: '3.8'
|
||||
|
||||
services:
|
||||
dashcaddy-api:
|
||||
image: samiahmed7777/dashcaddy-api:latest
|
||||
container_name: dashcaddy-api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3001:3001" # API port
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./config:/app/config
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DASHCADDY_PORT=3001
|
||||
- CADDY_ADMIN_URL=http://caddy:2019
|
||||
- TECHNITIUM_API_URL=http://technitium:5380
|
||||
- TECHNITIUM_API_TOKEN=\${TECHNITIUM_API_TOKEN}
|
||||
- JWT_SECRET=\${JWT_SECRET}
|
||||
- DASHCA_ENABLED=true
|
||||
depends_on:
|
||||
- caddy
|
||||
- technitium
|
||||
networks:
|
||||
- dashcaddy-net
|
||||
|
||||
dashcaddy-dashboard:
|
||||
image: samiahmed7777/dashcaddy-dashboard:latest
|
||||
container_name: dashcaddy-dashboard
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000" # Dashboard port
|
||||
environment:
|
||||
- NEXT_PUBLIC_API_URL=http://localhost:3001
|
||||
depends_on:
|
||||
- dashcaddy-api
|
||||
networks:
|
||||
- dashcaddy-net
|
||||
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "2019:2019" # Admin API
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
|
||||
- ./caddy/data:/data
|
||||
- ./caddy/config:/config
|
||||
networks:
|
||||
- dashcaddy-net
|
||||
|
||||
technitium:
|
||||
image: technitium/dns-server:latest
|
||||
container_name: technitium
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5380:5380" # Web console
|
||||
- "53:53/udp"
|
||||
- "53:53/tcp"
|
||||
volumes:
|
||||
- ./technitium:/etc/dns
|
||||
environment:
|
||||
- DNS_SERVER_DOMAIN=local
|
||||
networks:
|
||||
- dashcaddy-net
|
||||
|
||||
networks:
|
||||
dashcaddy-net:
|
||||
driver: bridge`}</code></pre>
|
||||
|
||||
<h2>Environment variables reference</h2>
|
||||
<ul>
|
||||
<li><strong>Router DNS</strong> — most home routers let you add a local DNS entry. Point <code>*.dashcaddy.home</code> at your server's LAN IP. All devices on your network pick it up automatically.</li>
|
||||
<li><strong>Tailscale MagicDNS</strong> — install Tailscale on the host and your devices. Their MagicDNS gives every machine a stable <code>name.tailnet.ts.net</code> name, no DNS editing needed.</li>
|
||||
</ul>
|
||||
<p>
|
||||
DashCaddy's behavior is controlled through environment variables in the <code>.env</code> file. Here's
|
||||
a complete reference:
|
||||
Custom TLDs require installing DashCaddy's internal CA certificate (DashCA) on each device that connects.
|
||||
The setup wizard walks you through this on first run.
|
||||
</p>
|
||||
|
||||
<h3>Option C — A direct IP address</h3>
|
||||
<p>
|
||||
If you don't want to set up DNS at all, you can reach DashCaddy at <code>https://192.168.x.x:port</code>.
|
||||
This works for a single machine on a LAN, but the URL changes when the host gets a new IP and you lose
|
||||
subdomain support. Useful for a quick test, not a permanent setup.
|
||||
</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-surface-700">
|
||||
<th className="py-3 pr-4 font-semibold text-surface-200">Variable</th>
|
||||
<th className="py-3 pr-4 font-semibold text-surface-200">Default</th>
|
||||
<th className="py-3 font-semibold text-surface-200">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-surface-300">
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">NODE_ENV</td>
|
||||
<td className="py-3 pr-4">production</td>
|
||||
<td className="py-3">Runtime environment (development or production)</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">DASHCADDY_PORT</td>
|
||||
<td className="py-3 pr-4">3001</td>
|
||||
<td className="py-3">Port for the DashCaddy API server</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">CADDY_ADMIN_URL</td>
|
||||
<td className="py-3 pr-4">http://caddy:2019</td>
|
||||
<td className="py-3">Caddy Admin API endpoint</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">TECHNITIUM_API_URL</td>
|
||||
<td className="py-3 pr-4">http://technitium:5380</td>
|
||||
<td className="py-3">Technitium DNS API endpoint</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">TECHNITIUM_API_TOKEN</td>
|
||||
<td className="py-3 pr-4">(required)</td>
|
||||
<td className="py-3">API token for Technitium DNS authentication</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">JWT_SECRET</td>
|
||||
<td className="py-3 pr-4">(auto-generated)</td>
|
||||
<td className="py-3">Secret key for JWT token signing</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">DASHCA_ENABLED</td>
|
||||
<td className="py-3 pr-4">true</td>
|
||||
<td className="py-3">Enable internal certificate authority</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">BASE_DOMAIN</td>
|
||||
<td className="py-3 pr-4">local</td>
|
||||
<td className="py-3">Base domain for service hostnames</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">PROMETHEUS_ENABLED</td>
|
||||
<td className="py-3 pr-4">true</td>
|
||||
<td className="py-3">Expose Prometheus metrics at /metrics</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4 font-mono text-xs">LOG_LEVEL</td>
|
||||
<td className="py-3 pr-4">info</td>
|
||||
<td className="py-3">Logging verbosity (debug, info, warn, error)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-3 pr-4 font-mono text-xs">DATA_DIR</td>
|
||||
<td className="py-3 pr-4">./data</td>
|
||||
<td className="py-3">Path for persistent state storage</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>The Smart Defaults Wizard</h2>
|
||||
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
|
||||
<p className="text-surface-300">
|
||||
<strong className="text-brand-400">Smart Defaults Wizard:</strong> On first launch, the dashboard opens to the Smart Defaults Wizard. It surveys your host and pre-fills sensible choices so you can go from install to a working deployment in minutes. Every default is editable — the wizard simply gives you a known-good starting point instead of a blank slate.
|
||||
<strong className="text-brand-400">No port forwarding?</strong> If your ISP puts you behind CGNAT or you
|
||||
don't want to open ports 80 and 443, DashCaddy works fine behind a Tailscale tunnel or Cloudflare
|
||||
Tunnel. Set up the tunnel first, point your hostname at the tunnel's origin, then run the installer
|
||||
with <code>--no-caddy</code> and let the tunnel handle HTTPS termination.
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<p>The wizard performs these tasks automatically:</p>
|
||||
<ul>
|
||||
<li><strong>Caddy detection</strong> — detects the Caddy Admin API endpoint and validates connectivity</li>
|
||||
<li><strong>Technitium detection</strong> — detects Technitium DNS (if installed) and configures the API token and zone</li>
|
||||
<li><strong>Domain suggestion</strong> — proposes a base domain and naming convention for new services</li>
|
||||
<li><strong>DashCA initialization</strong> — initializes the internal certificate authority and offers the root certificate for download</li>
|
||||
<li><strong>Admin account creation</strong> — creates the initial admin account and offers TOTP 2FA enrollment</li>
|
||||
<li><strong>Network scanning</strong> — scans for existing Docker containers and offers to adopt them via Service Discovery</li>
|
||||
</ul>
|
||||
|
||||
<h2>Manual setup</h2>
|
||||
<h2>Run the installer</h2>
|
||||
<p>
|
||||
If you want direct control over paths, services, Caddy, and DNS integration, you can deploy manually
|
||||
instead of using <code>start.sh</code>:
|
||||
Open a terminal on the host you want to install DashCaddy on and run one of these:
|
||||
</p>
|
||||
|
||||
<h3>Three-command install (most users)</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# 1. Download and run the installer
|
||||
curl -fsSL https://get.dashcaddy.net | bash
|
||||
|
||||
# 2. Answer the prompts (domain, install location, storage size)
|
||||
# Defaults are sensible — just press Enter to accept.
|
||||
|
||||
# 3. Open the URL the installer prints at the end`}</code></pre>
|
||||
|
||||
<h3>Two-keystroke install (no typing)</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Install with a public domain — only thing you type is the domain
|
||||
curl -fsSL https://get.dashcaddy.net | bash -s -- --domain dashcaddy.example.com
|
||||
|
||||
# Or use a custom TLD (your router / Tailscale handles DNS)
|
||||
curl -fsSL https://get.dashcaddy.net | bash -s -- --domain dashcaddy.home
|
||||
|
||||
# Or skip DNS entirely and use a local address (good for a first test)
|
||||
curl -fsSL https://get.dashcaddy.net | bash -s -- quick`}</code></pre>
|
||||
|
||||
<h3>What the installer does</h3>
|
||||
<p>
|
||||
The installer is a single shell script that runs in seven steps, all idempotent — re-running it on an existing
|
||||
install reconciles the stack rather than clobbering your config:
|
||||
</p>
|
||||
<ol>
|
||||
<li>Run the installer: <code>curl -fsSL https://get.dashcaddy.net | bash</code></li>
|
||||
<li>Install the API dependencies: <code>cd dashcaddy && npm ci</code></li>
|
||||
<li>Prepare Caddy and confirm the Admin API is reachable on port 2019</li>
|
||||
<li>Prepare Technitium DNS if you want automatic DNS changes (optional)</li>
|
||||
<li>Copy <code>.env.example</code> to <code>.env</code> and configure environment variables</li>
|
||||
<li>Start the DashCaddy API: <code>npm run start</code> (or use your process manager like systemd or PM2)</li>
|
||||
<li>Serve the dashboard through Caddy by adding a reverse proxy rule to your Caddyfile</li>
|
||||
<li><strong>Detect your environment</strong> — operating system, package manager, free disk space, available memory, public and LAN IP addresses</li>
|
||||
<li><strong>Install prerequisites</strong> — Docker Engine and the Docker Compose plugin if missing</li>
|
||||
<li><strong>Write configuration</strong> — generates <code>/etc/dashcaddy/</code>, <code>/opt/dockerdata/</code>, and the DashCaddy config file with sensible defaults</li>
|
||||
<li><strong>Pull the DashCaddy container image</strong> — fetches the latest release from the public registry</li>
|
||||
<li><strong>Start the DashCaddy container</strong> — launches <code>dashcaddy-api</code> on port 3001, mounts persistent data volumes</li>
|
||||
<li><strong>Health check</strong> — waits for the API to respond on <code>/healthz</code> before continuing</li>
|
||||
<li><strong>Print the access URL</strong> — shows the dashboard address and any follow-up commands</li>
|
||||
</ol>
|
||||
|
||||
<h2>Common installation issues</h2>
|
||||
<p>If you encounter problems during installation, check this troubleshooting table:</p>
|
||||
<p>
|
||||
If the installer ever hits a problem, it stops at the step that failed and tells you exactly which command to
|
||||
re-run to retry. It does not silently leave a half-installed state behind.
|
||||
</p>
|
||||
|
||||
<h2>The first-run setup wizard</h2>
|
||||
<p>
|
||||
When you open the dashboard URL the installer printed, you land on the setup wizard. It runs once and writes
|
||||
your configuration to disk — every choice is editable later from the settings page.
|
||||
</p>
|
||||
|
||||
<p>The wizard walks through these screens:</p>
|
||||
<ol>
|
||||
<li><strong>Pick a configuration profile</strong> — <em>Professional Home Lab</em> (recommended for most users), <em>Simple</em> (minimal config), or <em>Public</em> (for VPS deployments exposed to the internet)</li>
|
||||
<li><strong>Configure your network</strong> — your domain, the LAN IP that'll be used for service routing, and whether you want to use Tailscale for remote access</li>
|
||||
<li><strong>Set storage limits</strong> — max disk usage for Docker data and how much headroom to keep free</li>
|
||||
<li><strong>Review your choices</strong> — a summary screen with the resolved values so you can sanity-check before saving</li>
|
||||
<li><strong>Disk-safety confirmation</strong> — confirms you understand that bound storage will be created and what happens on uninstall</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
After the wizard saves, you're asked to create the first admin account. Pick a strong password — this
|
||||
account controls every service DashCaddy manages.
|
||||
</p>
|
||||
|
||||
<h2>Set up two-factor authentication</h2>
|
||||
<p>
|
||||
Right after creating your admin account, the dashboard prompts you to enroll a TOTP authenticator
|
||||
(Google Authenticator, Authy, 1Password, or any RFC 6238 app). Scan the QR code with your phone, enter the
|
||||
6-digit code to confirm, and store the recovery codes somewhere safe — they are the only way back into your
|
||||
account if you lose your phone.
|
||||
</p>
|
||||
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
|
||||
<p className="text-surface-300">
|
||||
<strong className="text-brand-400">Don't skip 2FA.</strong> DashCaddy can manage DNS records, start and
|
||||
stop containers, and read credentials for the services it deploys. A leaked password without a second
|
||||
factor is a full compromise. A leaked password with 2FA is just a login screen.
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<h2>Install the DashCA root certificate (custom-TLD installs only)</h2>
|
||||
<p>
|
||||
If you chose Option B (a custom TLD like <code>dashcaddy.home</code>) or Option C (a direct IP), your browser
|
||||
will warn that the certificate is not trusted — because it isn't, to anyone outside your network. DashCaddy
|
||||
ships an internal certificate authority called <strong>DashCA</strong> so internal hostnames get a real TLS
|
||||
certificate instead of self-signed warnings.
|
||||
</p>
|
||||
<p>
|
||||
Visit the <strong>DashCA</strong> page in the dashboard and follow the per-platform instructions:
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>macOS</strong> — open the downloaded <code>.crt</code>, add it to the System keychain, set it to “Always Trust”</li>
|
||||
<li><strong>Windows</strong> — double-click the <code>.crt</code>, install to “Trusted Root Certification Authorities”</li>
|
||||
<li><strong>Linux</strong> — copy to <code>/usr/local/share/ca-certificates/</code> and run <code>sudo update-ca-certificates</code></li>
|
||||
<li><strong>iOS / Android</strong> — download the profile, install it via Settings, then enable full trust for the CA</li>
|
||||
</ul>
|
||||
<p>
|
||||
You need to install the root certificate on every device that connects to your DashCaddy services, not just
|
||||
the server. There's no way around this for custom-TLD installs — TLS is what keeps your traffic private.
|
||||
</p>
|
||||
|
||||
<h2>Harden the host right after install</h2>
|
||||
<p>
|
||||
DashCaddy protects the services it manages, but it runs on top of a normal Linux box that's still exposed
|
||||
on your network. Spend five minutes on these four steps:
|
||||
</p>
|
||||
|
||||
<h3>1. Enable the firewall</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# UFW (Ubuntu / Debian)
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw allow ssh # if you SSH in
|
||||
sudo ufw allow 80/tcp # HTTP (Let's Encrypt + redirect)
|
||||
sudo ufw allow 443/tcp # HTTPS
|
||||
sudo ufw enable
|
||||
|
||||
# If you put DashCaddy behind a tunnel (Tailscale / Cloudflare), skip 80/443 above
|
||||
# and only allow the tunnel interface — see your tunnel provider's docs`}</code></pre>
|
||||
|
||||
<h3>2. Disable password SSH login</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Edit /etc/ssh/sshd_config and set:
|
||||
PasswordAuthentication no
|
||||
PermitRootLogin prohibit-password # or "no" if you use a sudo user
|
||||
|
||||
sudo systemctl restart sshd`}</code></pre>
|
||||
|
||||
<h3>3. Turn on automatic security updates</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Debian / Ubuntu
|
||||
sudo apt install -y unattended-upgrades
|
||||
sudo dpkg-reconfigure -plow unattended-upgrades # answer "Yes"`}</code></pre>
|
||||
|
||||
<h3>4. Set up backups</h3>
|
||||
<p>
|
||||
All your DashCaddy state — services, config, encrypted credentials, DashCA — lives in
|
||||
<code> /opt/dockerdata</code> and <code>/etc/dashcaddy</code>. Snapshot those two directories somewhere off
|
||||
the host. Any tool works: <code>rsync</code> to a NAS, <code>restic</code> to Backblaze B2, a systemd timer
|
||||
that tars them up nightly. The dashboard's Backup button exports a single JSON file you can store
|
||||
anywhere if you want a no-setup option.
|
||||
</p>
|
||||
|
||||
<h2>Verify the install</h2>
|
||||
<p>
|
||||
After the wizard finishes and you've logged in, run through this checklist to confirm everything is wired
|
||||
up correctly:
|
||||
</p>
|
||||
<ol>
|
||||
<li><strong>Container is running</strong> — <code>docker ps</code> should show <code>dashcaddy-api</code> with status <code>Up</code></li>
|
||||
<li><strong>API is healthy</strong> — from the host, <code>curl http://localhost:3001/healthz</code> returns <code>{`{"status":"alive"}`}</code> and <code>/readyz</code> returns 200 with all checks passing</li>
|
||||
<li><strong>Dashboard responds</strong> — opening the URL the installer printed shows the dashboard, not a browser error</li>
|
||||
<li><strong>TLS works</strong> — the address bar shows a padlock with no warnings (a real cert if you used Option A; trusted if you used Option B with DashCA installed)</li>
|
||||
<li><strong>2FA works</strong> — log out and back in with your password + TOTP code</li>
|
||||
<li><strong>Updates work</strong> — the dashboard shows the current version in the footer and the update button is enabled</li>
|
||||
</ol>
|
||||
|
||||
<h2>Troubleshooting a fresh install</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-surface-700">
|
||||
<th className="py-3 pr-4 font-semibold text-surface-200">Symptom</th>
|
||||
<th className="py-3 pr-4 font-semibold text-surface-200">Cause</th>
|
||||
<th className="py-3 font-semibold text-surface-200">Solution</th>
|
||||
<th className="py-3 pr-4 font-semibold text-surface-200">Likely cause</th>
|
||||
<th className="py-3 font-semibold text-surface-200">Fix</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-surface-300">
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Port 80 or 443 already in use</td>
|
||||
<td className="py-3 pr-4">Another web server (nginx, Apache) is running</td>
|
||||
<td className="py-3">Stop the conflicting service or change Caddy's ports in docker-compose.yml</td>
|
||||
<td className="py-3 pr-4">Installer says “Docker not found”</td>
|
||||
<td className="py-3 pr-4">Docker Engine isn't installed or you're not in the docker group</td>
|
||||
<td className="py-3">Run <code>sudo usermod -aG docker $USER</code>, log out, log back in. Re-run the installer.</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Docker permission denied</td>
|
||||
<td className="py-3 pr-4">Current user not in docker group</td>
|
||||
<td className="py-3">Run <code>sudo usermod -aG docker $USER</code> and log out/in</td>
|
||||
<td className="py-3 pr-4">Installer fails on “port 80/443 already in use”</td>
|
||||
<td className="py-3 pr-4">Another web server (nginx, Apache, another Caddy) is bound to those ports</td>
|
||||
<td className="py-3">Stop the conflicting service, or move it to different ports and re-run the installer</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Caddy Admin API unreachable</td>
|
||||
<td className="py-3 pr-4">Caddy not running or Admin API disabled</td>
|
||||
<td className="py-3">Ensure Caddy is running with <code>admin :2019</code> in its config</td>
|
||||
<td className="py-3 pr-4">Dashboard URL gives a connection refused</td>
|
||||
<td className="py-3 pr-4">Container failed to start, or the host firewall is blocking the port</td>
|
||||
<td className="py-3"><code>docker ps</code> — if the container exited, <code>docker logs dashcaddy-api</code> shows why. If running, check the firewall.</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Technitium API token invalid</td>
|
||||
<td className="py-3 pr-4">Token not set or expired</td>
|
||||
<td className="py-3">Generate a new token in Technitium web console and update .env</td>
|
||||
<td className="py-3 pr-4">Browser shows “Your connection is not private”</td>
|
||||
<td className="py-3 pr-4">Custom-TLD install with no DashCA trust, OR the domain doesn't point at this host</td>
|
||||
<td className="py-3">For custom TLDs, install the DashCA root certificate. For real domains, check DNS: <code>dig +short your-hostname</code> should return the host's public IP.</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Dashboard shows 502 Bad Gateway</td>
|
||||
<td className="py-3 pr-4">API server not responding</td>
|
||||
<td className="py-3">Check <code>docker logs dashcaddy-api</code> for errors</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Out of memory during deployment</td>
|
||||
<td className="py-3 pr-4">Insufficient RAM for container workloads</td>
|
||||
<td className="py-3">Increase host RAM or reduce concurrent service deployments</td>
|
||||
<td className="py-3 pr-4">Let's Encrypt certificate never issues</td>
|
||||
<td className="py-3 pr-4">Domain doesn't resolve publicly, OR port 80 is blocked by your ISP</td>
|
||||
<td className="py-3">Verify the A record with <code>dig</code>. If you're on CGNAT, switch to Tailscale Funnel or Cloudflare Tunnel.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-3 pr-4">Certificate trust errors in browser</td>
|
||||
<td className="py-3 pr-4">DashCA root cert not installed on client</td>
|
||||
<td className="py-3">Download root cert from DashCA page and install on client device</td>
|
||||
<td className="py-3 pr-4">Setup wizard loops back to step 1</td>
|
||||
<td className="py-3 pr-4">Browser stored a stale config from a previous install</td>
|
||||
<td className="py-3">Open DevTools → Application → Local Storage → clear the <code>dashcaddy-setup</code> key, then reload</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Post-install verification</h2>
|
||||
<p>After the stack is up, verify each layer with these commands:</p>
|
||||
<h2>Uninstall</h2>
|
||||
<p>
|
||||
If you want to remove DashCaddy cleanly, the installer has an uninstall mode that stops the container,
|
||||
removes the systemd unit, and (optionally) deletes the data volumes:
|
||||
</p>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Remove DashCaddy but keep your config and data (you can reinstall later)
|
||||
curl -fsSL https://get.dashcaddy.net | bash -s -- --uninstall
|
||||
|
||||
<h3>1. Check container status</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`docker ps --filter "name=dashcaddy"
|
||||
# Expected: dashcaddy-api, dashcaddy-dashboard, caddy, technitium all running`}</code></pre>
|
||||
|
||||
<h3>2. Verify API health</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl http://localhost:3001/healthz
|
||||
# Expected: {"status":"ok","version":"1.0.0"}
|
||||
|
||||
curl http://localhost:3001/readyz
|
||||
# Expected: {"status":"ready","checks":{"caddy":true,"technitium":true}}`}</code></pre>
|
||||
|
||||
<h3>3. Test Caddy Admin API</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl http://localhost:2019/config/
|
||||
# Expected: JSON configuration object`}</code></pre>
|
||||
|
||||
<h3>4. Verify Technitium DNS</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl http://localhost:5380/api/dns/zones/list?token=YOUR_TOKEN
|
||||
# Expected: List of DNS zones`}</code></pre>
|
||||
|
||||
<h3>5. Check Prometheus metrics</h3>
|
||||
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`curl http://localhost:3001/metrics
|
||||
# Expected: Prometheus-formatted metrics output`}</code></pre>
|
||||
|
||||
<h3>6. Access the dashboard</h3>
|
||||
<p>Open your browser and navigate to <code>http://localhost:3000</code> (or your configured domain). You should see the DashCaddy dashboard login screen.</p>
|
||||
# Remove everything including config and data (irreversible)
|
||||
curl -fsSL https://get.dashcaddy.net | bash -s -- --uninstall --purge`}</code></pre>
|
||||
|
||||
<h2>Next steps</h2>
|
||||
<p>
|
||||
Once install checks pass, head to the <a href="/docs/first-service" className="text-brand-400 hover:text-brand-300 underline">Deploy Your First Service</a> guide to
|
||||
bring your first application online.
|
||||
With DashCaddy installed and hardened, head to the <a href="/docs/first-service" className="text-brand-400 hover:text-brand-300 underline">Deploy Your First Service</a> guide
|
||||
to bring an application online through the App Selector.
|
||||
</p>
|
||||
<p>
|
||||
If you need to understand the architecture in more depth, see the <a href="/docs/overview" className="text-brand-400 hover:text-brand-300 underline">Product Overview</a>.
|
||||
If you want to understand the architecture before adding services, see the <a href="/docs/overview" className="text-brand-400 hover:text-brand-300 underline">Product Overview</a>.
|
||||
For problems during or after install, the <a href="/docs/troubleshooting" className="text-brand-400 hover:text-brand-300 underline">Troubleshooting</a> page
|
||||
walks each layer bottom-up.
|
||||
</p>
|
||||
</DocsLayout>
|
||||
<Footer />
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function DocsIntegrationsPage() {
|
||||
Docker (and Docker Compose) is the runtime foundation for every deployment workflow, container lifecycle action,
|
||||
service discovery sweep, and template-based launch. DashCaddy communicates with the Docker daemon over the
|
||||
Unix socket (<code>/var/run/docker.sock</code>) to start, stop, restart, inspect, and adopt containers, and to
|
||||
deploy the <strong>93 one-click application templates</strong> from the catalog.
|
||||
deploy the <strong>77 one-click application templates</strong> from the catalog.
|
||||
</p>
|
||||
<p>
|
||||
The daemon connection is established at startup. If the socket is missing or permissioned for a different user,
|
||||
|
||||
@@ -162,7 +162,7 @@ export default function DocsOverviewPage() {
|
||||
<tr className="border-b border-surface-800">
|
||||
<td className="py-3 pr-4">Docker deployment</td>
|
||||
<td className="py-3 pr-4">Manual compose files</td>
|
||||
<td className="py-3 pr-4">93 one-click templates</td>
|
||||
<td className="py-3 pr-4">77 one-click templates</td>
|
||||
<td className="py-3">Templates + Recipes</td>
|
||||
</tr>
|
||||
<tr className="border-b border-surface-800">
|
||||
@@ -217,7 +217,7 @@ export default function DocsOverviewPage() {
|
||||
<td className="py-3 pr-4">Pricing</td>
|
||||
<td className="py-3 pr-4">Time + complexity</td>
|
||||
<td className="py-3 pr-4">Free forever</td>
|
||||
<td className="py-3">$20–$99 one-time</td>
|
||||
<td className="py-3">$20–$99 / license duration</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -250,8 +250,8 @@ export default function DocsOverviewPage() {
|
||||
|
||||
<h2>Premium licensing</h2>
|
||||
<p>
|
||||
DashCaddy Premium is a one-time purchase (not a subscription) that unlocks advanced orchestration
|
||||
features. Pricing tiers:
|
||||
DashCaddy Premium is a fixed-duration license. You buy the duration you want; Premium features unlock
|
||||
for that duration and deactivate when it expires. You purchase a new license to renew. Pricing tiers:
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>$20</strong> — 30-day license</li>
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function DocsPremiumPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Dashboard & web UI</td><td>✓</td><td>✓</td></tr>
|
||||
<tr><td>93 application templates</td><td>✓</td><td>✓</td></tr>
|
||||
<tr><td>77 application templates</td><td>✓</td><td>✓</td></tr>
|
||||
<tr><td>Caddy reverse proxy + auto HTTPS</td><td>✓</td><td>✓</td></tr>
|
||||
<tr><td>Caddyfile-as-Code builder</td><td>✓</td><td>✓</td></tr>
|
||||
<tr><td>Technitium DNS automation</td><td>✓</td><td>✓</td></tr>
|
||||
@@ -161,8 +161,10 @@ curl -X POST -H "Authorization: Bearer ***" \\
|
||||
|
||||
<h2>Pricing</h2>
|
||||
<p>
|
||||
Premium is sold as one-time payments for fixed license durations. There is a single Premium tier — no ladder
|
||||
of plans to navigate. Longer durations are discounted relative to the monthly rate.
|
||||
Premium is sold as a fixed-duration license. You pick the duration that fits your needs; the license unlocks
|
||||
Premium features for that period and then expires. You manually purchase a new license when the current
|
||||
one expires. There is a single Premium tier — no ladder of plans to navigate. Longer durations are
|
||||
discounted relative to the short-term rate.
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -186,7 +188,7 @@ curl -X POST -H "Authorization: Bearer ***" \\
|
||||
|
||||
<h2>Plan terms</h2>
|
||||
<ul>
|
||||
<li><strong>One-time payments</strong> — no auto-renewing subscription; your license runs for the purchased duration and then expires.</li>
|
||||
<li><strong>Fixed-duration license</strong> — your license runs for the purchased duration and then expires. You re-purchase manually when you want to renew.</li>
|
||||
<li><strong>One Premium tier</strong> — every duration unlocks the same features.</li>
|
||||
<li><strong>One active machine per license</strong> — a license is bound to a single host at a time.</li>
|
||||
<li><strong>7-day grace period</strong> after expiry — services keep running while you renew; Premium features are read-only during grace.</li>
|
||||
|
||||
@@ -40,11 +40,11 @@ export default function FeaturesPage() {
|
||||
id: 'app-deployment',
|
||||
title: 'App Deployment',
|
||||
description:
|
||||
'Deploy from a library of 93 pre-configured Docker templates with one click. Automatic configuration, smart defaults, and service discovery that auto-detects running containers and lets you adopt them instantly. From zero to live in seconds.',
|
||||
'Deploy from a library of 77 pre-configured Docker templates with one click. Automatic configuration, smart defaults, and service discovery that auto-detects running containers and lets you adopt them instantly. From zero to live in seconds.',
|
||||
icon: '🚀',
|
||||
features: [
|
||||
{ icon: '⚡', label: 'One-click deployment' },
|
||||
{ icon: '📦', label: '93 app templates' },
|
||||
{ icon: '📦', label: '77 app templates' },
|
||||
{ icon: '⚙️', label: 'Automatic configuration' },
|
||||
{ icon: '🔍', label: 'Service Discovery & auto-adopt' },
|
||||
{ icon: '🧙', label: 'Smart Defaults Wizard' },
|
||||
@@ -350,7 +350,7 @@ export default function FeaturesPage() {
|
||||
</div>
|
||||
<ul className="space-y-3">
|
||||
{[
|
||||
'93 one-click app templates',
|
||||
'77 one-click app templates',
|
||||
'Automatic SSL certificates',
|
||||
'Technitium DNS automation',
|
||||
'Reverse proxy + Caddyfile builder',
|
||||
@@ -361,7 +361,7 @@ export default function FeaturesPage() {
|
||||
'Security Center',
|
||||
'One-click backup & restore',
|
||||
'AI Intent Router + MCP Server',
|
||||
'REST API (260+ endpoints)',
|
||||
'REST API (350+ endpoints)',
|
||||
'Plugin / Extension system',
|
||||
'i18n (31 languages, RTL)',
|
||||
'Prometheus metrics export',
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ import "./globals.css";
|
||||
export const metadata: Metadata = {
|
||||
title: "DashCaddy - Self-Hosting Made Beautiful",
|
||||
description:
|
||||
"Deploy 93 Docker apps with one click. AI-powered self-hosting with automatic SSL, DNS, reverse proxy, security center, and fleet management.",
|
||||
"Deploy 77 Docker apps with one click. AI-powered self-hosting with automatic SSL, DNS, reverse proxy, security center, and fleet management.",
|
||||
keywords: [
|
||||
"self-hosting",
|
||||
"docker",
|
||||
@@ -19,7 +19,7 @@ export const metadata: Metadata = {
|
||||
openGraph: {
|
||||
title: "DashCaddy - Self-Hosting Made Beautiful",
|
||||
description:
|
||||
"Deploy 93 Docker apps with one click. AI-powered self-hosting with automatic SSL, DNS, and reverse proxy.",
|
||||
"Deploy 77 Docker apps with one click. AI-powered self-hosting with automatic SSL, DNS, and reverse proxy.",
|
||||
url: "https://dashcaddy.net",
|
||||
siteName: "DashCaddy",
|
||||
type: "website",
|
||||
@@ -28,7 +28,7 @@ export const metadata: Metadata = {
|
||||
card: "summary_large_image",
|
||||
title: "DashCaddy - Self-Hosting Made Beautiful",
|
||||
description:
|
||||
"Deploy 93 Docker apps with one click. AI-powered self-hosting.",
|
||||
"Deploy 77 Docker apps with one click. AI-powered self-hosting.",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+16
-4
@@ -5,6 +5,9 @@ import Navbar from '@/components/Navbar';
|
||||
import Footer from '@/components/Footer';
|
||||
import FeatureCard from '@/components/FeatureCard';
|
||||
import AppShowcase from '@/components/AppShowcase';
|
||||
import InstallCommand from '@/components/InstallCommand';
|
||||
import CompetitorComparison from '@/components/CompetitorComparison';
|
||||
import FaqSection from '@/components/FaqSection';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -44,7 +47,7 @@ export default function Home() {
|
||||
<p className="text-xl text-surface-300 max-w-lg leading-relaxed">
|
||||
Just say “deploy Plex” — DashCaddy’s AI Intent Router handles the rest.
|
||||
Deploy Docker apps with one click, automatic SSL, DNS, and reverse proxy.
|
||||
93 templates, fleet management, and an MCP control plane for AI assistants.
|
||||
77 templates, fleet management, and an MCP control plane for AI assistants.
|
||||
Everything you need for self-hosted perfection.
|
||||
</p>
|
||||
</div>
|
||||
@@ -179,6 +182,9 @@ export default function Home() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Install Command */}
|
||||
<InstallCommand />
|
||||
|
||||
{/* Key Features Grid */}
|
||||
<section id="features" className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
@@ -206,8 +212,8 @@ export default function Home() {
|
||||
/>
|
||||
<FeatureCard
|
||||
icon="🚀"
|
||||
title="93 App Templates"
|
||||
description="Choose from 93 pre-configured Docker app templates. Deploy your favorite applications instantly without manual configuration."
|
||||
title="77 App Templates"
|
||||
description="Choose from 77 pre-configured Docker app templates. Deploy your favorite applications instantly without manual configuration."
|
||||
/>
|
||||
<FeatureCard
|
||||
icon="🛡️"
|
||||
@@ -332,6 +338,9 @@ export default function Home() {
|
||||
{/* App Showcase */}
|
||||
<AppShowcase />
|
||||
|
||||
{/* Competitor Comparison */}
|
||||
<CompetitorComparison />
|
||||
|
||||
{/* Why DashCaddy Section */}
|
||||
<section id="why-dashcaddy" className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
@@ -396,7 +405,7 @@ export default function Home() {
|
||||
<span className="text-surface-500">Write your own compose files</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-center text-sm">
|
||||
<span className="text-green-400 font-semibold">✓ 93 pre-configured</span>
|
||||
<span className="text-green-400 font-semibold">✓ 77 pre-configured</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="hover:bg-surface-800/30 transition-colors">
|
||||
@@ -595,6 +604,9 @@ export default function Home() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<FaqSection />
|
||||
|
||||
{/* Final CTA Section */}
|
||||
<section id="get-started" className="relative py-20 sm:py-24 lg:py-32 bg-gradient-to-b from-surface-900 to-surface-950 overflow-hidden">
|
||||
{/* Background effects */}
|
||||
|
||||
@@ -9,7 +9,7 @@ interface App {
|
||||
}
|
||||
|
||||
// All apps below have real deployable templates in DashCaddy.
|
||||
// Verified against src/docker/app-templates.js (93 templates, Aug 2026).
|
||||
// Verified against src/docker/app-templates.js (77 templates, Aug 2026).
|
||||
const apps: App[] = [
|
||||
// Media
|
||||
{ name: 'Plex', icon: '🎬', category: 'Media' },
|
||||
@@ -21,7 +21,6 @@ const apps: App[] = [
|
||||
{ name: 'Calibre-Web', icon: '📖', category: 'Media' },
|
||||
{ name: 'Komga', icon: '📚', category: 'Media' },
|
||||
{ name: 'Kavita', icon: '📖', category: 'Media' },
|
||||
{ name: 'Subsonic', icon: '🎵', category: 'Media' },
|
||||
|
||||
// Media Management
|
||||
{ name: 'Sonarr', icon: '📺', category: 'Media Mgmt' },
|
||||
@@ -50,10 +49,6 @@ const apps: App[] = [
|
||||
{ name: 'Paperless-ngx', icon: '📚', category: 'Productivity' },
|
||||
{ name: 'Trilium Notes', icon: '🗒️', category: 'Productivity' },
|
||||
{ name: 'Excalidraw', icon: '🎨', category: 'Productivity' },
|
||||
{ name: 'HedgeDoc', icon: '📝', category: 'Productivity' },
|
||||
{ name: 'OpenProject', icon: '📋', category: 'Productivity' },
|
||||
{ name: 'Plane', icon: '✈️', category: 'Productivity' },
|
||||
{ name: 'Vikunja', icon: '✅', category: 'Productivity' },
|
||||
|
||||
// Photos
|
||||
{ name: 'Immich', icon: '📸', category: 'Photos' },
|
||||
@@ -67,38 +62,28 @@ const apps: App[] = [
|
||||
// Management
|
||||
{ name: 'Portainer', icon: '🐳', category: 'Management' },
|
||||
{ name: 'Watchtower', icon: '👓', category: 'Management' },
|
||||
{ name: 'Yacht', icon: '⛵', category: 'Management' },
|
||||
|
||||
// Security
|
||||
{ name: 'Vaultwarden', icon: '🔑', category: 'Security' },
|
||||
{ name: 'DashCA', icon: '🔐', category: 'Security' },
|
||||
{ name: 'Authentik', icon: '🔐', category: 'Security' },
|
||||
{ name: 'CrowdSec', icon: '🛡️', category: 'Security' },
|
||||
{ name: 'Authelia', icon: '🔐', category: 'Security' },
|
||||
{ name: 'Bitwarden', icon: '🔑', category: 'Security' },
|
||||
{ name: 'Keycloak', icon: '🔑', category: 'Security' },
|
||||
|
||||
// Development
|
||||
{ name: 'VS Code Server', icon: '💻', category: 'Development' },
|
||||
{ name: 'Gitea', icon: '🦊', category: 'Development' },
|
||||
{ name: 'Jenkins', icon: '🔧', category: 'Development' },
|
||||
{ name: 'Drone CI', icon: '🐝', category: 'Development' },
|
||||
{ name: 'Forgejo', icon: '🦊', category: 'Development' },
|
||||
{ name: 'GitLab', icon: '🦊', category: 'Development' },
|
||||
|
||||
// Monitoring
|
||||
{ name: 'Grafana', icon: '📊', category: 'Monitoring' },
|
||||
{ name: 'Uptime Kuma', icon: '📈', category: 'Monitoring' },
|
||||
{ name: 'Speedtest Tracker', icon: '⚡', category: 'Monitoring' },
|
||||
{ name: 'Dozzle', icon: '📜', category: 'Monitoring' },
|
||||
{ name: 'Kibana', icon: '📊', category: 'Monitoring' },
|
||||
{ name: 'Netdata', icon: '📈', category: 'Monitoring' },
|
||||
{ name: 'Prometheus', icon: '🔥', category: 'Monitoring' },
|
||||
|
||||
// Networking
|
||||
{ name: 'Pi-hole', icon: '🛡️', category: 'Networking' },
|
||||
{ name: 'WireGuard VPN', icon: '🔒', category: 'Networking' },
|
||||
{ name: 'OpenVPN', icon: '🔒', category: 'Networking' },
|
||||
|
||||
// DNS
|
||||
{ name: 'Technitium DNS Server', icon: '🌐', category: 'DNS' },
|
||||
@@ -125,7 +110,6 @@ const apps: App[] = [
|
||||
// Home Automation
|
||||
{ name: 'Home Assistant', icon: '🏠', category: 'Home Automation' },
|
||||
{ name: 'Node-RED', icon: '🔴', category: 'Home Automation' },
|
||||
{ name: 'OpenHAB', icon: '🏠', category: 'Home Automation' },
|
||||
|
||||
// Utilities
|
||||
{ name: 'Weather', icon: '🌤️', category: 'Utilities' },
|
||||
@@ -174,7 +158,7 @@ export default function AppShowcase() {
|
||||
{/* Header */}
|
||||
<div className="mb-12 text-center">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-surface-50 mb-4">
|
||||
93 One-Click App Templates
|
||||
77 One-Click App Templates
|
||||
</h2>
|
||||
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
|
||||
Deploy your favorite apps instantly with pre-configured templates, automatic SSL certificates, and integrated DNS management.
|
||||
@@ -232,7 +216,7 @@ export default function AppShowcase() {
|
||||
<div className="mt-16 grid grid-cols-3 gap-4 sm:gap-8">
|
||||
<div className="text-center">
|
||||
<div className="text-3xl sm:text-4xl font-bold text-brand-400 mb-2">
|
||||
93
|
||||
77
|
||||
</div>
|
||||
<p className="text-sm text-surface-400">App Templates</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
'use client';
|
||||
|
||||
interface Capability {
|
||||
feature: string;
|
||||
dashcaddy: boolean | string;
|
||||
values: Record<string, boolean | string>;
|
||||
}
|
||||
|
||||
// The "values" map uses string "—" to indicate "not applicable / not available"
|
||||
// and true / false for the supported check. Where accurate, we use short text
|
||||
// like "Partial" or "Plugin" to be honest about the gap.
|
||||
const rows: Capability[] = [
|
||||
{
|
||||
feature: 'AI Intent Router (natural language)',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': false, 'CasaOS': false, 'YunoHost': false, 'Unraid': false, 'Cosmos Cloud': false },
|
||||
},
|
||||
{
|
||||
feature: 'MCP Server for AI assistants',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': false, 'CasaOS': false, 'YunoHost': false, 'Unraid': false, 'Cosmos Cloud': false },
|
||||
},
|
||||
{
|
||||
feature: 'One-click Docker templates',
|
||||
dashcaddy: '77 apps',
|
||||
values: { 'Portainer': 'Manual', 'CasaOS': '~120 apps', 'YunoHost': '~500 apps', 'Unraid': 'Community', 'Cosmos Cloud': '~30 apps' },
|
||||
},
|
||||
{
|
||||
feature: 'Automatic SSL (Let\'s Encrypt or internal CA)',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': 'Manual', 'CasaOS': true, 'YunoHost': true, 'Unraid': 'Manual', 'Cosmos Cloud': true },
|
||||
},
|
||||
{
|
||||
feature: 'Automatic DNS record management',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': false, 'CasaOS': false, 'YunoHost': 'Partial', 'Unraid': false, 'Cosmos Cloud': 'Partial' },
|
||||
},
|
||||
{
|
||||
feature: 'Internal PKI / CA for private TLDs',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': false, 'CasaOS': false, 'YunoHost': false, 'Unraid': false, 'Cosmos Cloud': false },
|
||||
},
|
||||
{
|
||||
feature: 'WebSocket real-time updates',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': 'Partial', 'CasaOS': false, 'YunoHost': false, 'Unraid': false, 'Cosmos Cloud': 'Partial' },
|
||||
},
|
||||
{
|
||||
feature: 'Unified Security Center (multi-source events)',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': false, 'CasaOS': false, 'YunoHost': false, 'Unraid': false, 'Cosmos Cloud': false },
|
||||
},
|
||||
{
|
||||
feature: 'Multi-host fleet management',
|
||||
dashcaddy: 'Premium',
|
||||
values: { 'Portainer': 'Business', 'CasaOS': false, 'YunoHost': false, 'Unraid': true, 'Cosmos Cloud': false },
|
||||
},
|
||||
{
|
||||
feature: 'Plugin / extension system',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': 'Limited', 'CasaOS': true, 'YunoHost': true, 'Unraid': 'Community', 'Cosmos Cloud': 'Limited' },
|
||||
},
|
||||
{
|
||||
feature: 'Open-source core',
|
||||
dashcaddy: 'Proprietary',
|
||||
values: { 'Portainer': 'CE + Business', 'CasaOS': true, 'YunoHost': true, 'Unraid': 'Proprietary', 'Cosmos Cloud': true },
|
||||
},
|
||||
{
|
||||
feature: 'Built-in user accounts + RBAC',
|
||||
dashcaddy: true,
|
||||
values: { 'Portainer': 'Business', 'CasaOS': false, 'YunoHost': true, 'Unraid': 'Plugin', 'Cosmos Cloud': 'Partial' },
|
||||
},
|
||||
];
|
||||
|
||||
const competitors = ['Portainer', 'CasaOS', 'YunoHost', 'Unraid', 'Cosmos Cloud'] as const;
|
||||
|
||||
function Cell({ value, highlight }: { value: boolean | string; highlight?: boolean }) {
|
||||
if (value === true) {
|
||||
return (
|
||||
<span className={`inline-flex items-center justify-center w-6 h-6 rounded-full ${highlight ? 'bg-brand-500/20 text-brand-400' : 'bg-green-500/20 text-green-400'}`}>
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" strokeWidth={3} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (value === false) {
|
||||
return <span className="text-surface-600">—</span>;
|
||||
}
|
||||
return <span className={`text-xs ${highlight ? 'text-brand-400 font-medium' : 'text-surface-400'}`}>{value}</span>;
|
||||
}
|
||||
|
||||
export default function CompetitorComparison() {
|
||||
return (
|
||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 text-center">
|
||||
<span className="inline-flex items-center rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
|
||||
Honest comparison
|
||||
</span>
|
||||
<h2 className="mt-6 text-3xl sm:text-4xl lg:text-5xl font-bold text-surface-50">
|
||||
How DashCaddy compares
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-surface-400 max-w-2xl mx-auto">
|
||||
DashCaddy isn't the right tool for everyone. Here's where it wins and where the alternatives are stronger.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto rounded-2xl border border-surface-700/50 bg-surface-900/50 backdrop-blur-sm">
|
||||
<table className="w-full min-w-[820px]">
|
||||
<thead>
|
||||
<tr className="border-b border-surface-700/50">
|
||||
<th className="px-4 py-4 text-left text-sm font-semibold text-surface-300 bg-surface-900/70">
|
||||
Capability
|
||||
</th>
|
||||
<th className="px-4 py-4 text-center text-sm font-semibold text-brand-400 bg-surface-900/80 border-l border-r border-brand-500/30">
|
||||
DashCaddy
|
||||
</th>
|
||||
{[...competitors].map((c) => (
|
||||
<th key={c} className="px-4 py-4 text-center text-sm font-semibold text-surface-300 bg-surface-900/50">
|
||||
{c}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-700/30">
|
||||
{rows.map((row, i) => (
|
||||
<tr key={row.feature} className={`hover:bg-surface-800/30 transition-colors ${i % 2 === 1 ? 'bg-surface-900/20' : ''}`}>
|
||||
<td className="px-4 py-4 text-sm text-surface-300">{row.feature}</td>
|
||||
<td className="px-4 py-4 text-center border-l border-r border-brand-500/20 bg-brand-500/5">
|
||||
<Cell value={row.dashcaddy} highlight />
|
||||
</td>
|
||||
{competitors.map((c) => (
|
||||
<td key={c} className="px-4 py-4 text-center">
|
||||
<Cell value={row.values[c]} />
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-800/40 p-4">
|
||||
<div className="text-2xl mb-2">🎯</div>
|
||||
<h3 className="text-sm font-semibold text-surface-50 mb-1">Where DashCaddy wins</h3>
|
||||
<p className="text-xs text-surface-400">
|
||||
AI-native control plane, MCP server, automatic DNS+SSL chain, internal PKI for private TLDs, unified security events.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-800/40 p-4">
|
||||
<div className="text-2xl mb-2">🤝</div>
|
||||
<h3 className="text-sm font-semibold text-surface-50 mb-1">Where it ties</h3>
|
||||
<p className="text-xs text-surface-400">
|
||||
One-click apps, real-time updates, plugin systems, user accounts with RBAC are table stakes. DashCaddy does all of them.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-800/40 p-4">
|
||||
<div className="text-2xl mb-2">🏆</div>
|
||||
<h3 className="text-sm font-semibold text-surface-50 mb-1">Where others win</h3>
|
||||
<p className="text-xs text-surface-400">
|
||||
YunoHost has 500+ packaged apps. Unraid has a mature community app store. If raw app count is the only thing you care about, those are better.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface Faq {
|
||||
question: string;
|
||||
answer: React.ReactNode;
|
||||
}
|
||||
|
||||
const faqs: Faq[] = [
|
||||
{
|
||||
question: 'Is DashCaddy really free?',
|
||||
answer: (
|
||||
<>
|
||||
Yes. The core platform — Docker deployment, Caddy + DNS + TLS automation, the full 77-app template catalog,
|
||||
AI Intent Router, MCP Server, Security Center, monitoring, backups, REST API, plugin system, and 31-language
|
||||
i18n — is free forever. Premium adds a focused set of advanced features (SSO, Recipes, Docker Swarm,
|
||||
Multi-Host Fleet) available as a fixed-duration license. The license runs for the duration you buy
|
||||
and then expires; you re-purchase manually to renew.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'Where does my data live?',
|
||||
answer: (
|
||||
<>
|
||||
On your own hardware. DashCaddy runs on a Linux host you control — a desktop, a NUC, a Raspberry Pi 5, or a VPS.
|
||||
No cloud dependency, no telemetry, no phoning home. Your services, your credentials, your DNS records, your SSL
|
||||
keys — all live on your network. A license validation ping is the only outbound call, and you can run unlicensed
|
||||
without it.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'What if I already have Docker containers running?',
|
||||
answer: (
|
||||
<>
|
||||
Use <strong>Service Discovery</strong>. DashCaddy scans the Docker socket, lists every running container, and
|
||||
lets you adopt any of them with one click. It adds the Caddy reverse-proxy route, DNS record, and TLS
|
||||
certificate — without touching the running container. Your existing data, volumes, and network config stay
|
||||
exactly as they are.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'Do I need a public domain?',
|
||||
answer: (
|
||||
<>
|
||||
No. You can use a custom TLD (<code>dashcaddy.home</code>, <code>dashcaddy.lan</code>) on your home network,
|
||||
or a direct IP for a quick test. Custom TLDs need DashCA’s internal CA root certificate installed on
|
||||
each device that connects — the setup wizard walks you through that. A real domain is the easiest path: it
|
||||
auto-issues trusted Let’s Encrypt certificates and you skip the CA install step.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'Can my AI assistant actually control DashCaddy?',
|
||||
answer: (
|
||||
<>
|
||||
Yes, two ways. The <strong>AI Intent Router</strong> understands plain-English commands like “deploy
|
||||
Plex” or “check SSL status.” The <strong>MCP Server</strong> exposes DashCaddy as a tool
|
||||
endpoint for Claude, GPT, and any MCP-compatible AI assistant — your assistant can list services, deploy
|
||||
templates, manage DNS, and rebuild the catalog, all through the standardized Model Context Protocol.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'What happens to my services if I uninstall DashCaddy?',
|
||||
answer: (
|
||||
<>
|
||||
By default, the uninstall stops the DashCaddy container but leaves your Docker containers and volumes
|
||||
untouched. If you want a clean removal, the uninstaller has a <code>--purge</code> flag that also deletes
|
||||
the DashCaddy config and data directories. Either way, your services keep running until you stop them.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'How does Premium compare to other paid dashboards?',
|
||||
answer: (
|
||||
<>
|
||||
Premium is a fixed-duration license. $99 covers a full year (365 days) of SSO, Recipes, Docker Swarm, and
|
||||
Fleet Management on one machine. The license expires at the end of its term and you purchase a new one
|
||||
to renew. There’s no per-user fee and no per-app fee. The free tier already covers everything a single
|
||||
host needs, so Premium only makes sense if you specifically want SSO across deployed apps,
|
||||
multi-container stacks, or to orchestrate a fleet.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'Is DashCaddy open source?',
|
||||
answer: (
|
||||
<>
|
||||
DashCaddy is proprietary software, but it’s built on a fully open-source stack — Caddy, Docker, Node.js,
|
||||
Express, Technitium DNS, Tailscale. The AI Intent Router, MCP Server, plugin system, and Security Center are
|
||||
all built on open standards. You get vendor-friendly source-available documentation, and your data never
|
||||
leaves your hardware.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export default function FaqSection() {
|
||||
const [open, setOpen] = useState<number | null>(0);
|
||||
|
||||
return (
|
||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
|
||||
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 text-center">
|
||||
<span className="inline-flex items-center rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
|
||||
FAQ
|
||||
</span>
|
||||
<h2 className="mt-6 text-3xl sm:text-4xl lg:text-5xl font-bold text-surface-50">
|
||||
Questions, <span className="text-brand-400">answered</span>
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-surface-400">
|
||||
The most common things people ask before installing DashCaddy.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{faqs.map((faq, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`rounded-xl border transition-all overflow-hidden ${
|
||||
open === idx
|
||||
? 'border-brand-500/40 bg-surface-900/70 shadow-lg shadow-brand-500/5'
|
||||
: 'border-surface-700/50 bg-surface-900/40 hover:border-surface-700'
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => setOpen(open === idx ? null : idx)}
|
||||
className="w-full px-6 py-4 flex items-center justify-between text-left gap-4"
|
||||
>
|
||||
<h3 className="text-base sm:text-lg font-semibold text-surface-50">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<svg
|
||||
className={`w-5 h-5 flex-shrink-0 text-brand-400 transition-transform ${
|
||||
open === idx ? 'rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2}
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
{open === idx && (
|
||||
<div className="border-t border-surface-700/50 bg-surface-950/40 px-6 py-5">
|
||||
<p className="text-surface-300 leading-relaxed">{faq.answer}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-10 text-center text-surface-400 text-sm">
|
||||
Have a question that’s not here?{' '}
|
||||
<Link href="/docs" className="text-brand-400 hover:text-brand-300 underline">
|
||||
Read the docs
|
||||
</Link>{' '}
|
||||
or{' '}
|
||||
<a href="mailto:support@dashcaddy.net" className="text-brand-400 hover:text-brand-300 underline">
|
||||
email support
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
interface InstallOption {
|
||||
label: string;
|
||||
description: string;
|
||||
command: string;
|
||||
}
|
||||
|
||||
const options: InstallOption[] = [
|
||||
{
|
||||
label: 'Public domain',
|
||||
description: 'You own example.com — automatic Let\'s Encrypt certs, no client setup.',
|
||||
command: 'curl -fsSL https://get.dashcaddy.net | bash -s -- --domain dashcaddy.example.com',
|
||||
},
|
||||
{
|
||||
label: 'Custom TLD',
|
||||
description: 'Home network with dashcaddy.home — clients install the DashCA root cert.',
|
||||
command: 'curl -fsSL https://get.dashcaddy.net | bash -s -- --domain dashcaddy.home',
|
||||
},
|
||||
{
|
||||
label: 'Quick test',
|
||||
description: 'Local IP install — ideal for a first run on a fresh VM.',
|
||||
command: 'curl -fsSL https://get.dashcaddy.net | bash -s -- quick',
|
||||
},
|
||||
];
|
||||
|
||||
export default function InstallCommand() {
|
||||
const [active, setActive] = useState(0);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const onCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(options[active].command);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative py-16 sm:py-20 lg:py-24 overflow-hidden">
|
||||
<div className="absolute inset-0 -z-10">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[40rem] h-[40rem] bg-brand-500/10 rounded-full blur-3xl opacity-40" />
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-10">
|
||||
<span className="inline-flex items-center gap-2 rounded-full border border-brand-500/30 bg-brand-500/10 px-4 py-1.5 text-sm font-medium text-brand-400">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-brand-400 opacity-75" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-brand-400" />
|
||||
</span>
|
||||
One-line install
|
||||
</span>
|
||||
<h2 className="mt-6 text-3xl sm:text-4xl lg:text-5xl font-bold text-surface-50">
|
||||
Live in <span className="text-brand-400">60 seconds</span>
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-surface-400 max-w-2xl mx-auto">
|
||||
Pick your mode, paste one command, and the installer handles Docker, Caddy, the wizard, and your first login.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-surface-700/50 bg-surface-900/60 backdrop-blur p-1 shadow-2xl shadow-brand-500/10">
|
||||
{/* Mode tabs */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-1 p-1">
|
||||
{options.map((opt, i) => (
|
||||
<button
|
||||
key={opt.label}
|
||||
onClick={() => setActive(i)}
|
||||
className={`rounded-xl px-4 py-3 text-left transition-all ${
|
||||
active === i
|
||||
? 'bg-surface-800/80 ring-1 ring-brand-500/40'
|
||||
: 'hover:bg-surface-800/40'
|
||||
}`}
|
||||
>
|
||||
<div className={`text-sm font-semibold ${active === i ? 'text-brand-400' : 'text-surface-200'}`}>
|
||||
{opt.label}
|
||||
</div>
|
||||
<div className="text-xs text-surface-500 mt-0.5 hidden sm:block">
|
||||
{opt.description}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Terminal-style command display */}
|
||||
<div className="relative rounded-xl mt-2 overflow-hidden border border-surface-700/50 bg-surface-950">
|
||||
<div className="flex items-center justify-between border-b border-surface-700/50 px-4 py-2 bg-surface-900/70">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500/60" />
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500/60" />
|
||||
<div className="w-3 h-3 rounded-full bg-green-500/60" />
|
||||
</div>
|
||||
<span className="text-xs font-mono text-surface-500">get.dashcaddy.net</span>
|
||||
<button
|
||||
onClick={onCopy}
|
||||
className="text-xs text-surface-400 hover:text-brand-400 transition-colors font-medium"
|
||||
>
|
||||
{copied ? '✓ Copied' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5 font-mono text-sm overflow-x-auto">
|
||||
<span className="text-green-400 select-none">$ </span>
|
||||
<span className="text-surface-100">{options[active].command}</span>
|
||||
<span className="ml-1 inline-block w-2 h-4 bg-brand-400 animate-pulse align-middle" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* After-install expectations */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mt-4 px-1">
|
||||
<div className="flex items-start gap-3 p-3 rounded-lg bg-surface-900/40 border border-surface-700/30">
|
||||
<span className="text-brand-400 text-lg">⚡</span>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-surface-100">~60 seconds</div>
|
||||
<div className="text-xs text-surface-500">From curl to ready dashboard</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-3 rounded-lg bg-surface-900/40 border border-surface-700/30">
|
||||
<span className="text-brand-400 text-lg">🧙</span>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-surface-100">Zero YAML</div>
|
||||
<div className="text-xs text-surface-500">Wizard configures everything</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-3 rounded-lg bg-surface-900/40 border border-surface-700/30">
|
||||
<span className="text-brand-400 text-lg">🔄</span>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-surface-100">Idempotent</div>
|
||||
<div className="text-xs text-surface-500">Re-run safely to update</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user