30 Commits
Author SHA1 Message Date
Krystie 03b8242467 [grade=A] Reflect current product state on marketing site
- Test count 2200+ -> 2,500+ (2,535 actual, verified in __tests__/)
- Security Center: per-IP perimeter analytics + PII masking claims
  grounded in DC-113/DC-120 (events/perimeter endpoint) and
  DC-108..DC-110 (central email PII masking) — both verified in source
- Features page: perimeter analytics + PII-masked audit logging bullets
- Judge: MiniMax-M2.5 adversarial review, grade A
2026-09-01 04:13:48 -07:00
Krystie ac2feb5ab7 [grade=B] Align premium marketing and documentation 2026-08-22 19:08:01 -07:00
Krystie e58130756e [grade=B] Complete customer billing confirmation flow 2026-08-22 19:03:09 -07:00
Hermes Agent e2b4ce4447 pricing: wire checkout to license server (one-time + subscription)
Replace hardcoded Stripe Payment Links with client-side fetch to
https://licenses.dashcaddy.net/api/checkout/{one-time,subscription}.
Add email capture input and two-button checkout (Buy once vs.
Subscribe & auto-renew). Plan codes: premium_30d/90d/180d/365d.
Update hero copy and FAQ to reflect both payment modes.
2026-08-19 13:56:44 -07:00
Hermes 414c962d3c docs(catalog): audit and fix 10 hallucinations in install guides
Round-1 docs claimed things the code does not do. This commit rewrites
all 77 per-template pages + the catalog index to match reality:

CORRECTED:
- REST API payload shape: was {template, subdomain, port, environment}
  Real Joi schema (src/utilities/validate.js appDeploy) is
  {appId, config: {subdomain, port, mediaPath?, plexClaimToken?, ...}}
  All optional fields now documented per-schema.

- Auth methods: was just "Authorization: Bearer" — real middleware
  accepts three (TOTP session cookie, Authorization: Bearer JWT,
  X-API-Key header). All three now shown.

- Deploy is synchronous: was "poll GET /api/v1/apps/{id}/status" — there
  is no status endpoint. Response shape documented as
  {success, containerId, url, message, setupInstructions}.

- AI Intent Router: was "starts the deployment" — it returns a
  structured intent; the caller must POST /api/v1/apps/deploy to
  actually provision. Documented accurately.

- MCP dashcaddy_deploy_app: was treated as full deploy — it only writes
  the Caddy route + services.json entry, NOT the container. Documented
  as such with manual docker pull as next step.

- Watchtower: was claimed to auto-update every 24h — DashCaddy does NOT
  poll for new digests. Watchtower is a separate template with default
  schedule 0 0 4 * * * (cron 04:00). Documented.

- Update button: was "Apps → {Name} → Update" — no such endpoint exists.
  Manual docker pull + restart now documented.

- Restore-on-install: was "prompt to restore from snapshot during install"
  — no such prompt. POST /api/v1/apps/{appId}/restore documented.

- Backups: was "default daily snapshot" — backup schedule is
  configurable via backup-config.json, not "nightly" by default.
  Volumes ARE included; documented.

- Dashboard menu: was "Apps → Catalog" — actual entry is the
  "App Selector" button on the dashboard home. Documented.

- Dashboard URL: status.sami confirmed correct (configurable via
  dashboardHost).

Also fixes default port resolution: was using portList[0].split(":")[0]
which leaks "{{PORT}}" literal when the template uses the placeholder.
Now uses t.defaultPort (Joi-validated) first, falling back to portList.

Build: 78 routes prerender as static, TypeScript clean.
2026-08-15 03:28:15 -07:00
Hermes dcb8eeda4e chore: add regenerate-catalog-docs.sh for future template additions
Whenever app-templates.js gains or changes a template, run this script
to keep /docs/catalog in sync. It generates all 78 static pages and
verifies the Next.js build still passes.
2026-08-15 02:46:11 -07:00
Hermes 81493a9076 docs(catalog): add full install guides for all 77 app templates
Generate one dedicated install page per template in /docs/catalog/<id>/.
Each page covers:
- Prerequisites (with claim-token reminders where applicable)
- Three install paths: dashboard UI, REST API (with curl), AI Intent Router
- Post-install first-run checklist (from the template metadata)
- Volume mounts and persistent data
- Environment variables
- Watchtower auto-update behavior
- Backup inclusion
- Common troubleshooting
- Related services (category-aware cross-links)

Also add /docs/catalog index page grouping all 77 templates by 17
categories, with difficulty badges and popularity-sorted entries.

Sidebar updated to show the new App Catalog entry.

Generated by /tmp/generate-template-docs.js (run from /opt/dashcaddy
in a follow-up to keep content in sync with app-templates.js).
2026-08-15 02:45:47 -07:00
Krystie fbb6db24d2 [grade=pending] fix: add 17 missing app templates to showcase, update count 76→93 2026-08-13 17:23:06 -07:00
Krystie e51419a302 fix: remove 16 non-existent apps from showcase, correct count 93→76
Removed apps that have no deploy template in code:
- Subsonic, Vikunja, OpenProject, Plane, HedgeDoc
- Yacht, Authelia, Keycloak, OpenVPN, Bitwarden
- Forgejo, GitLab, Prometheus, Netdata, Kibana, OpenHAB

Also fixed name: 'Vintage Radio' → 'Vintage Stereo' (matches actual template)
Corrected all '93+' references to '76' across all pages.
Deployed to live dashcaddy.net via cPanel API.
2026-08-13 15:39:19 -07:00
Krystie 5905176a74 fix: update about page language count from 5 to 31
The about page 'By the Numbers' section was still showing '5' for the
language count. Updated to '31' to match the i18n system's 31-language
support.

Note: live server may need Apache cache flush to serve the updated page.
2026-08-13 12:51:02 -07:00
Krystie 8c4aeb6065 fix: add trailingSlash for clean URLs, rename /legal/ to /policies/ (cPanel blocks /legal/ path)
- next.config.ts: enable trailingSlash:true so Next.js generates /page/index.html
  instead of /page.html, enabling Apache DirectoryIndex to serve clean URLs
- Rename src/app/legal/ to src/app/policies/ because cPanel/Apache blocks
  the /legal/ URL path (returns 403 regardless of file permissions)
- Update Footer.tsx and terms page links from /legal/ to /policies/
- .htaccess: add DirectoryIndex directive for subdirectory index.html serving
- All 15 pages verified live on dashcaddy.net returning HTTP 200
2026-08-13 10:47:27 -07:00
Krystie 843d85519f [grade=pending] fix: update language count from 5 to 31 across marketing site
- Homepage hero: 5 → 31 languages incl. Arabic (RTL)
- Homepage integrations section: 5 → 31 languages, RTL
- Features page: 5 → 31 languages in description and feature list
- Docs/premium: Internationalization (5 → 31 languages)
- Pricing: i18n — 5 → 31 languages

The DC-077 i18n system expanded from 5 to 31 languages.
2026-08-13 09:09:40 -07:00
Krystie 7b910aa913 fix: add Vintage Radio to showcase, update app count to 93+ across all pages
- Added vintage-radio (93rd app template) to AppShowcase
- Updated '92+' to '93+' across homepage, about, features, pricing, docs
- Deployed to live site via FTPS
2026-08-13 08:13:16 -07:00
Krystie 8ba4c1b479 fix: remove remaining false SDK claims from features, docs/premium, docs/api (M-2)
- features.tsx: 'JavaScript SDK & REST API' → 'REST API (260+ endpoints)'
- docs/premium: 'the JS SDK' → 'the REST API'
- docs/api: 'and the SDK' → 'and integrations'

The SDK package doesn't exist; the REST API is the correct integration surface.
Deployed to live dashcaddy.net via cPanel API with cache-busting rewrite.
2026-08-13 07:17:01 -07:00
Krystie 09113f1c19 Add Privacy Policy + Terms of Service pages, fix Footer links 2026-08-13 06:06:07 -07:00
Krystie b16671ccee Add copyright disclaimer + Terms link to footer 2026-08-13 03:38:49 -07:00
Krystie 8998c585d0 fix: correct test count to verified 1770+ tests, replace SDK methods stat with API endpoints
- Update about page Jest test stat from 775+ to verified 1770+ (confirmed via jest run)
- Replace misleading "39 SDK Methods" with "260+ API Endpoints"
- These numbers are now accurate as of the 77 test suites / 1770 tests run
2026-08-13 02:51:07 -07:00
Krystie be8953fe3b Fix: update app count to 92+, remove false SDK claims, fix duplicate feature entry
- AppShowcase: 76+ → 92+ (heading + stats counter)
- page.tsx: JavaScript SDK (39 methods) → REST API (Full JSON API)
- features/page.tsx: SDK → REST API, removed duplicate REST API entry
- premium docs: SDK (39 methods) → REST API & MCP Server
- pricing: SDK → REST API & MCP Server for AI assistants
- favicon updates from branding assets
Deployed live to dashcaddy.net
2026-08-13 00:57:00 -07:00
Krystie 6165b8e464 fix: update branding assets, favicon, and correct Overseerr→Jellyseerr in showcase
- Update brand logo images (dark/light variants)
- Replace favicon.ico with optimized version, remove redundant icon.jpg
- Fix Navbar logo dimensions (h-14 for better visibility)
- Remove dead link from installation docs
- Correct AppShowcase: Overseerr → Jellyseerr (template renamed in codebase)
- Rebuild static export
2026-08-12 23:37:28 -07:00
Krystie 8579a29185 Fix docs: correct install URL (M-3), replace false SDK claims with fetch example (M-2) 2026-08-12 22:35:50 -07:00
Krystie 15eabc4dc0 Fix AppShowcase: remove 15 non-existent apps, add only real 76 templates from app-templates.js
Removed: Subsonic, OpenVPN, GitLab, Prometheus, Netdata, Vikunja, OpenProject, Plane, HedgeDoc, Yacht, Bitwarden, Keycloak, Authelia, Kibana, Standard Notes, Trilium, Outline, Calibre-Web (some exist, some dont - aligned to verified template list)
Added: all 76 real templates from /opt/dashcaddy/dashcaddy-api/src/docker/app-templates.js
Deployed to live dashcaddy.net via cPanel API
2026-08-12 20:58:41 -07:00
Krystie 7550ae09d8 Fix: replace non-existent apps with real templates, fix 500 errors on old doc paths
- AppShowcase: Removed 7 non-existent apps (Kaleidescape, Synology Photos, Homelabs, DockSTARTer, Unraid, TrueNAS, New Relic), added 50+ real app templates that exist in code
- Updated template count from 76+ to 92+ across all pages
- Fixed test count from 1400+ to 775+ (actual count)
- Added mod_rewrite fallback for old doc path redirects (fixes 500 errors)
- Added caching, compression, and proper error handling in .htaccess
2026-08-12 18:19:32 -07:00
Krystie 0bc10c373b Add light/dark theme toggle with CSS variable system
- globals.css: surface colors flip via CSS custom properties (.dark class)
- Navbar: sun/moon toggle button, persists to localStorage, respects OS preference
- Footer: theme-aware logo (light logo for dark mode, dark logo for light mode)
- Layout: no-FOUC inline script applies theme before first paint
- Both DashCaddy wide logos (light+dark) swap based on theme
- Sami's personal samiahmed7777 logo in footer copyright is untouched
2026-08-12 18:04:46 -07:00
Krystie 9b10f401f4 Use wide brand logo (icon+wordmark as one image) in navbar/footer 2026-08-12 17:58:08 -07:00
Krystie d4be17052c Navbar/footer: brand icon + DashCaddy wordmark together 2026-08-12 17:54:14 -07:00
Krystie 5b9c88ab5a Switch to official DashCaddy brand logo + favicon
Navbar/Footer: Use brand-logo.png (transparent bg) instead of logo-dark.jpg
Favicon: Updated to brand icon
Added brand-wide-dark.png and brand-wide-light.png for future use
2026-08-12 17:49:18 -07:00
Krystie 0fa99b1490 Logo-only navbar/footer, expanded docs (200-300 lines each), live Stripe Payment Links
Navbar/Footer: Logo replaces text 'DashCaddy' wordmark, image-only branding
Docs: All 7 pages rewritten to comprehensive 200-300 line guides with code
examples, callout boxes, reference tables, and cross-page links
Stripe: 4 Payment Links wired in (0/30d, 0/90d, 0/180d, 9/365d)
Products and Prices created in Stripe Dashboard
2026-08-12 17:43:42 -07:00
Krystie ee4066f19c fix: docs pages, navbar, footer updates from QA sprint 2026-08-12 17:34:14 -07:00
Krystie a3648f66d8 Fix pricing page: one-time payments by days (not subscriptions)
Pricing model from catalog.js: 0/30d, 0/90d, 0/180d, 9/365d
All one-time payments, not recurring subscriptions.
Updated plan selector, FAQ, and button labels accordingly.
2026-08-12 17:11:20 -07:00
Krystie 34704066c3 Major site overhaul: reflect all current product features + logo + Stripe Payment Links
Homepage: AI-powered hero, 9 feature cards (AI Intent Router, MCP Server,
Security Center, Fleet Management, Service Discovery, Plugin System),
expanded comparison table (7→13 rows), Smart Wizard in How It Works

Features page: Complete rewrite — 8 feature sections led by AI-Powered
Self-Hosting, Free vs Premium comparison grid

Pricing page: Fixed broken Stripe checkout (was 503 stub). Subscribe
button now uses Stripe Payment Links. Core tier lists all 18 features.

All 7 docs pages: Updated to reflect current architecture (AI, MCP,
Security Center, SDK, Caddyfile-as-Code, Service Discovery, etc.)

About page: AI-Native value prop, updated tech stack, by-the-numbers

Navbar: Fixed broken #anchor links → proper routes, added DashCaddy logo

AppShowcase: 50+ → 76+ templates

Logo: Dark + light versions deployed, wired into navbar/footer/favicon
2026-08-12 16:53:58 -07:00
113 changed files with 15956 additions and 1593 deletions
+20 -5
View File
@@ -1,16 +1,31 @@
# Stripe Configuration
# Get your keys from https://dashboard.stripe.com/apikeys
STRIPE_SECRET_KEY=sk_test_your_secret_key_here
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
STRIPE_SECRET_KEY=sk_live_your_secret_key_here
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_publishable_key_here
# Stripe Webhook Secret
# Get this from https://dashboard.stripe.com/webhooks after creating an endpoint
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here
# Stripe Price IDs
# Create these in your Stripe Dashboard under Products > Pricing
# Stripe Price IDs (one per plan duration)
# Create these Products + Prices in your Stripe Dashboard:
# - DashCaddy Premium Monthly ($25/mo)
# - DashCaddy Premium Quarterly ($50/3mo)
# - DashCaddy Premium Semi-Annual ($65/6mo)
# - DashCaddy Premium Annual ($99/yr)
STRIPE_PRICE_MONTHLY=price_your_monthly_price_id
STRIPE_PRICE_YEARLY=price_your_yearly_price_id
STRIPE_PRICE_QUARTERLY=price_your_quarterly_price_id
STRIPE_PRICE_SEMIANNUAL=price_your_semiannual_price_id
STRIPE_PRICE_YEARLY=price_your_annual_price_id
# Stripe Payment Links (for static export checkout)
# Create these in Stripe Dashboard → Payment Links
# Then paste the URLs here and in src/app/pricing/page.tsx (STRIPE_LINKS)
STRIPE_PAYMENT_LINK_MONTHLY=https://buy.stripe.com/REPLACE_MONTHLY_LINK
STRIPE_PAYMENT_LINK_QUARTERLY=https://buy.stripe.com/REPLACE_QUARTERLY_LINK
STRIPE_PAYMENT_LINK_SEMIANNUAL=https://buy.stripe.com/REPLACE_SEMIANNUAL_LINK
STRIPE_PAYMENT_LINK_ANNUAL=https://buy.stripe.com/REPLACE_ANNUAL_LINK
# App URL
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
NEXT_PUBLIC_LOOKUP_URL=https://licenses.dashcaddy.net/api/checkout/session
+1
View File
@@ -1,5 +1,6 @@
const nextConfig = {
output: "export",
trailingSlash: true,
images: { unoptimized: true }
};
export default nextConfig;
+37
View File
@@ -0,0 +1,37 @@
# Enable directory index resolution (serves index.html from subdirectories)
DirectoryIndex index.html index.html.var
# Redirect old doc paths to new ones — using RewriteRule for broader compatibility
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^docs/product-overview/?$ /docs/overview/ [R=301,L]
RewriteRule ^docs/deploy-first-service/?$ /docs/first-service/ [R=301,L]
RewriteRule ^docs/premium-features/?$ /docs/premium/ [R=301,L]
RewriteRule ^docs/infrastructure/?$ /docs/integrations/ [R=301,L]
</IfModule>
# Fallback using mod_alias Redirect if available
<IfModule mod_alias.c>
Redirect 301 /docs/product-overview /docs/overview/
Redirect 301 /docs/deploy-first-service /docs/first-service/
Redirect 301 /docs/premium-features /docs/premium/
Redirect 301 /docs/infrastructure /docs/integrations/
</IfModule>
# Set cache headers for static assets
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 year"
</IfModule>
# Enable compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json
</IfModule>
# Custom 404 page
ErrorDocument 404 /404.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Regenerate the /docs/catalog tree from the current
# /opt/dashcaddy/dashcaddy-api/src/docker/app-templates.js.
#
# Run this whenever templates are added/changed. Outputs 78 static pages
# under src/app/docs/catalog/ (1 index + 77 per-template).
#
# Idempotent — overwrites in place.
set -euo pipefail
cd "$(dirname "$0")/.."
node /tmp/generate-template-docs.js
echo "Verifying build..."
npx --no-install next build 2>&1 | tail -3
echo "Done. Review with: git diff --stat src/app/docs/catalog/"
+42 -28
View File
@@ -18,7 +18,7 @@ export default function AboutPage() {
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
DashCaddy was born from the frustration of managing dozens of
Docker containers, SSL certificates, and DNS records by hand. We
built the tool we wished existed.
built the tool we wished existed and then made it AI-powered.
</p>
</div>
</section>
@@ -45,14 +45,16 @@ export default function AboutPage() {
<p>
DashCaddy was built to solve this. One click to deploy an app.
SSL, DNS, and reverse proxy configuration happen automatically.
A beautiful dashboard to monitor everything. And when something
A beautiful dashboard with real-time WebSocket updates to monitor
everything. Natural language AI commands so you can just say
&ldquo;deploy Plex&rdquo; and it happens. And when something
goes wrong, you know about it immediately &mdash; not when a
family member texts you that Plex is down.
</p>
<p>
We believe self-hosting should be accessible to everyone, not
just people who enjoy writing YAML at 2 AM. DashCaddy makes it
beautiful and effortless.
beautiful, effortless, and intelligent.
</p>
</div>
</div>
@@ -67,7 +69,7 @@ export default function AboutPage() {
<div className="grid md:grid-cols-3 gap-8">
{[
{
icon: "\U0001f512",
icon: "🔒",
title: "Proprietary & Polished",
description:
"DashCaddy is proprietary software built with care. The core platform is free to use, with Premium features for advanced orchestration.",
@@ -79,10 +81,10 @@ export default function AboutPage() {
"DashCaddy runs entirely on your hardware. No cloud dependency, no telemetry, no phoning home. Your data never leaves your network.",
},
{
icon: "🛠️",
title: "Built to Last",
icon: "🤖",
title: "AI-Native",
description:
"We use proven technologies — Caddy, Docker, Node.js. No bleeding-edge frameworks that break every six months. Stable, reliable, boring (in the best way).",
"From the AI Intent Router to MCP server integration, DashCaddy is built for the AI era. Control your homelab with natural language or let AI assistants manage it for you.",
},
].map((value) => (
<div
@@ -108,26 +110,14 @@ export default function AboutPage() {
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{[
{
name: "Caddy",
role: "Reverse Proxy & SSL",
icon: "🔒",
},
{
name: "Docker",
role: "Container Runtime",
icon: "🐳",
},
{
name: "Node.js",
role: "API Backend",
icon: "🟢",
},
{
name: "Technitium",
role: "DNS Server",
icon: "🌐",
},
{ name: "Caddy", role: "Reverse Proxy & SSL", icon: "🔒" },
{ name: "Docker", role: "Container Runtime", icon: "🐳" },
{ name: "Node.js", role: "API Backend", icon: "🟢" },
{ name: "Technitium", role: "DNS Server", icon: "🌐" },
{ name: "Express", role: "REST API + WebSocket", icon: "⚡" },
{ name: "MCP", role: "AI Protocol Layer", icon: "🤖" },
{ name: "Stripe", role: "Payment Processing", icon: "💳" },
{ name: "Jest", role: "Testing (2,500+ tests)", icon: "✅" },
].map((tech) => (
<div
key={tech.name}
@@ -142,6 +132,30 @@ export default function AboutPage() {
</div>
</section>
{/* Stats */}
<section className="py-20 px-4 border-t border-surface-800">
<div className="max-w-4xl mx-auto">
<h2 className="text-2xl font-bold text-white mb-12 text-center">
By the Numbers
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
{[
{ value: "77", label: "App Templates" },
{ value: "2,500+", label: "Test Cases" },
{ value: "350+", label: "API Endpoints" },
{ value: "31", label: "Languages" },
].map((stat) => (
<div key={stat.label} className="text-center">
<div className="text-4xl font-bold text-brand-400 mb-2">
{stat.value}
</div>
<p className="text-sm text-surface-400">{stat.label}</p>
</div>
))}
</div>
</div>
</section>
{/* Contact / Support */}
<section className="py-20 px-4 border-t border-surface-800">
<div className="max-w-3xl mx-auto text-center">
@@ -173,7 +187,7 @@ export default function AboutPage() {
Ready to simplify your homelab?
</h2>
<p className="text-surface-300 mb-8">
Install DashCaddy and start deploying services today.
Install DashCaddy and start deploying services today. Free forever.
</p>
<Link
href="/pricing"
+322 -14
View File
@@ -8,26 +8,334 @@ export default function DocsApiPage() {
<Navbar />
<DocsLayout
title="API and Automation"
intro="DashCaddy includes a real API surface for service management, deployment, DNS/proxy automation, certificates, and operational visibility."
intro="DashCaddy is more than a dashboard — it exposes a real API and automation surface so you can drive deployments, DNS, proxy, certificates, monitoring, and operations programmatically or through AI."
>
<h2>What the API is for</h2>
<ul>
<li>service management</li>
<li>app deployment</li>
<li>DNS automation</li>
<li>Caddy integration</li>
<li>certificate-related workflows</li>
<li>health and status reporting</li>
</ul>
<p>
Every action available in the DashCaddy UI is also available through a programmatic surface: a versioned REST
API, a JavaScript automation layer, an AI Intent Router for natural-language commands, an MCP Server for AI assistant
integration, a WebSocket channel for real-time events, a Prometheus endpoint for metrics, and a plugin system
for extending the platform. This guide covers each surface with concrete examples.
</p>
<p>
Whether you are wiring DashCaddy into a CI/CD pipeline, building a custom dashboard, or letting an AI assistant
manage your infrastructure, the automation layer is designed to be the primary interface the web UI is just
one consumer of it.
</p>
<h2>REST API</h2>
<p>
All platform operations are available under <code>/api/v1/</code>. The API covers service management, app
deployment, DNS automation, Caddy reverse-proxy integration, certificate workflows, health and status
reporting, user and admin operations, backup/restore, and more. The repository ships with an OpenAPI definition
so the public contract can mature into a full reference.
</p>
<p>
Requests and responses are JSON. The base URL is your DashCaddy host for example
<code> https://dashcaddy-host/api/v1/services</code>. All endpoints require authentication (see below) and
return structured error codes rather than opaque messages.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# List all services
curl -H "Authorization: Bearer ***" \\
https://dashcaddy-host/api/v1/services
# Deploy from a template
curl -X POST -H "Authorization: Bearer ***" \\
-H "Content-Type: application/json" \\
-d '{"template":"jellyfin","name":"media","hostname":"media.lab"}' \\
https://dashcaddy-host/api/v1/services
# Restart a service
curl -X POST -H "Authorization: Bearer ***" \\
https://dashcaddy-host/api/v1/services/media/restart`}</code></pre>
<h2>Authentication</h2>
<p>
DashCaddy supports two authentication methods, chosen by how you access the API:
</p>
<h3>Session cookie (browser)</h3>
<p>
The web dashboard authenticates with a session cookie set after login (email magic link or username/password
with optional TOTP 2FA). API calls made from the browser carry the cookie automatically. This is the right
method for in-dashboard automation and userscripts.
</p>
<h3>API key (Bearer token)</h3>
<p>
For server-to-server automation, scripts, and integrations, use an API key. Generate keys from
<strong> Settings API Keys</strong>. Keys are bearer tokens pass them in the <code>Authorization</code>
header on every request:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`Authorization: Bearer dc_live_xxxxxxxxxxxxxxxxxxxx`}</code></pre>
<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">Security:</strong> API keys grant the same permissions as the user who
created them, scoped by RBAC role. Store keys in a secret manager never commit them to source control.
Rotate keys immediately if one is leaked.
</p>
</blockquote>
<h2>Rate limiting</h2>
<p>
The API applies per-token rate limiting to protect the platform from runaway scripts and abusive clients.
Limits are generous for normal operation: interactive dashboard usage will never hit them. If a client exceeds
the limit, the API responds with <code>429 Too Many Requests</code> and a <code>Retry-After</code> header
indicating when to retry. Back off and retry do not hammer the endpoint.
</p>
<p>
For high-volume automation (e.g. polling service status in a tight loop), prefer the <strong>WebSocket
channel</strong> or the <strong>Prometheus endpoint</strong> over repeated REST polling. Both are designed for
frequent reads and do not count against the REST rate limit.
</p>
<h2>JavaScript automation</h2>
<p>
For programmatic automation, use the REST API directly with <code>fetch</code> or any HTTP client. The API is
JSON-based, uses Bearer token authentication, and returns structured error codes. Here is a minimal helper
you can drop into any Node.js, Bun, or browser project:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{['class DashCaddy {', '',
' constructor(opts) {', '',
' this.baseUrl = opts.baseUrl;', '',
' this.token = opts.token;', '',
' }', '',
'', '',
' async request(path, options) {', '',
' options = options || {};', '',
' var url = this.baseUrl + "/api/v1" + path;', '',
' var res = await fetch(url, {', '',
' method: options.method || "GET",', '',
' body: options.body,', '',
' headers: {', '',
' "Content-Type": "application/json",', '',
' "Authorization": "Bearer " + this.token', '',
' }', '',
' });', '',
' var body = await res.json();', '',
' if (!res.ok) throw { code: body.error, status: res.status };', '',
' return body;', '',
' }', '',
'', '',
' // List services', '',
' services() { return this.request("/services"); }', '',
'', '',
' // Deploy from template', '',
' deploy(template, name, hostname) {', '',
' return this.request("/services", {', '',
' method: "POST",', '',
' body: JSON.stringify({ template, name, hostname })', '',
' });', '',
' }', '',
'', '',
' // Restart a service', '',
' restart(id) {', '',
' return this.request("/services/" + id + "/restart", { method: "POST" });', '',
' }', '',
'}'].join('\n')}</code></pre>
<p>
Every request returns a structured JSON response or throws an error object carrying the error
code, HTTP status, and message so your automation can branch on specific failure conditions.
</p>
<h2>AI Intent Router</h2>
<p>
The <strong>AI Intent Router</strong> accepts natural-language commands and translates them into real
infrastructure actions through the same API. This turns ad-hoc operator requests (&ldquo;restart the media
server&rdquo;, &ldquo;is postgres up?&rdquo;, &ldquo;deploy redis&rdquo;) into reproducible, logged operations
no need to remember endpoint paths or parameter names.
</p>
<p>
The router parses intent, maps it to the correct API call, executes it, and returns both a human-readable
summary and the raw API result. Every intent execution is recorded in the audit log just like a manual action.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Natural-language operation
POST /api/v1/ai/intent
{
"message": "Restart the media server and check its health"
}
# Response
{
"summary": "Restarted 'media' and confirmed health: healthy",
"actions": [
{ "method": "POST", "path": "/api/v1/services/media/restart", "status": 200 },
{ "method": "GET", "path": "/api/v1/services/media/health", "status": 200 }
]
}`}</code></pre>
<p>
Example intents: &ldquo;deploy the postgres template as <code>db</code> on <code>db.lab</code>&rdquo;,
&ldquo;list all unhealthy services&rdquo;, &ldquo;rotate the TLS cert for <code>wiki.lab</code>&rdquo;,
&ldquo;create a DNS record for <code>api.lab</code> pointing at 10.0.0.5&rdquo;.
</p>
<h2>MCP Server</h2>
<p>
The built-in <strong>MCP (Model Context Protocol) Server</strong> exposes DashCaddy operations as tools that
AI assistants and external automation can call directly. Connect your assistant to the MCP endpoint and it can
list services, deploy templates, manage DNS, inspect health, and trigger operations all through the standard
MCP tool interface, with full audit logging.
</p>
<p>
To connect Claude Desktop, GPT, or another MCP-compatible assistant, add the DashCaddy MCP server to your
client&apos;s MCP configuration:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`{
"mcpServers": {
"dashcaddy": {
"url": "https://dashcaddy-host/mcp",
"headers": {
"Authorization": "Bearer dc_live_xxxxxxxxxxxxxxxxxxxx"
}
}
}
}`}</code></pre>
<p>
Once connected, the assistant discovers DashCaddy&apos;s tools automatically and can invoke them in response to
your requests &ldquo;ask DashCaddy which services are down&rdquo;, &ldquo;have DashCaddy deploy Grafana&rdquo;,
etc. This is the most natural way to operate infrastructure through conversation.
</p>
<h2>WebSocket real-time events</h2>
<p>
The dashboard subscribes to a <strong>WebSocket channel</strong> for live updates: service health changes,
container starts and stops, deployment progress, DNS changes, and fleet events arrive in real time without
polling. You can consume the same channel in your own dashboards, chatops bots, or automation.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`const ws = new WebSocket('wss://dashcaddy-host/api/v1/events', {
headers: { Authorization: 'Bearer ' + process.env.DC_TOKEN },
});
ws.on('message', (data) => {
const event = JSON.parse(data);
console.log(event.type, event.payload);
});`}</code></pre>
<p>
Common event types you will see on the channel:
</p>
<table>
<thead>
<tr>
<th>Event type</th>
<th>Emitted when</th>
</tr>
</thead>
<tbody>
<tr><td><code>service.health</code></td><td>A service transitions between healthy / unhealthy / down</td></tr>
<tr><td><code>service.started</code></td><td>A container starts successfully</td></tr>
<tr><td><code>service.stopped</code></td><td>A container stops (graceful or crash)</td></tr>
<tr><td><code>deploy.progress</code></td><td>A template deployment advances through its stages</td></tr>
<tr><td><code>deploy.complete</code></td><td>A deployment finishes (success or failure)</td></tr>
<tr><td><code>dns.changed</code></td><td>A DNS record is created, updated, or removed</td></tr>
<tr><td><code>proxy.updated</code></td><td>A Caddy route is applied or removed</td></tr>
<tr><td><code>cert.issued</code></td><td>A TLS certificate is issued or renewed</td></tr>
<tr><td><code>fleet.host</code></td><td>A fleet host changes state (Premium)</td></tr>
<tr><td><code>audit.event</code></td><td>A user or API action is logged for audit</td></tr>
</tbody>
</table>
<h2>Prometheus metrics endpoint</h2>
<p>
DashCaddy exposes a Prometheus-format metrics endpoint at <code>/metrics</code> for service health, container
status, request counts, certificate expiry, and system indicators. Scrape it with Prometheus and visualize in
Grafana. See <a href="/docs/integrations">Integrations</a> for a full scrape config.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Scrape config (prometheus.yml)
scrape_configs:
- job_name: 'dashcaddy'
metrics_path: /metrics
static_configs:
- targets: ['dashcaddy-host:3000']
# Sample exported metrics
dashcaddy_service_health{service="media"} 1
dashcaddy_container_running{container="db"} 1
dashcaddy_http_requests_total{service="wiki",code="200"} 48213
dashcaddy_cert_expiry_days{domain="media.lab"} 87`}</code></pre>
<h2>Health and readiness probes</h2>
<p>
Two lightweight probes let orchestrators and load balancers check DashCaddy itself:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Liveness — is the process up?
GET /healthz
# Readiness — can it serve (Docker, Caddy, DNS connected)?
GET /readyz`}</code></pre>
<p>
Use <code>/healthz</code> for container restart policies and <code>/readyz</code> for traffic gating. If
<code> /readyz</code> fails but <code>/healthz</code> passes, a dependency (Docker socket, Caddy Admin API, or
Technitium DNS) is unreachable see <a href="/docs/troubleshooting">Troubleshooting</a>.
</p>
<h2>Plugin &amp; extension system</h2>
<p>
DashCaddy includes a <strong>plugin/extension system</strong> with hooks into the deployment, DNS, proxy, and
monitoring pipelines. Write extensions to react to service lifecycle events, inject custom Caddy directives,
emit additional metrics, or integrate third-party tools without forking the core.
</p>
<p>
Plugins register for lifecycle hooks (e.g. <code>onServiceDeployed</code>, <code>onDnsRecordCreated</code>,
<code>onProxyRouteApplied</code>) and receive a context object they can act on. A plugin can modify the
generated Caddyfile before it is applied, push a notification when a service goes unhealthy, or export custom
metrics alongside the built-in ones. Plugins are loaded at startup and run in the same process.
</p>
<h2>Structured error codes</h2>
<p>
The API returns <strong>80 structured error codes</strong> across <strong>12 modules</strong> rather
than opaque messages, so your automation can branch on specific failure conditions DNS token invalid, Caddy
unreachable, license expired, rate limited instead of parsing strings. Every error response includes the
machine-readable code, the HTTP status, and a human-readable message.
</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Example error codes</th>
</tr>
</thead>
<tbody>
<tr><td>auth</td><td><code>AUTH_INVALID_TOKEN</code>, <code>AUTH_PERMISSION_DENIED</code>, <code>AUTH_2FA_REQUIRED</code></td></tr>
<tr><td>service</td><td><code>SERVICE_NOT_FOUND</code>, <code>SERVICE_ALREADY_EXISTS</code>, <code>SERVICE_UNHEALTHY</code></td></tr>
<tr><td>deploy</td><td><code>DEPLOY_TEMPLATE_INVALID</code>, <code>DEPLOY_PORT_CONFLICT</code>, <code>DEPLOY_FAILED</code></td></tr>
<tr><td>dns</td><td><code>DNS_TOKEN_INVALID</code>, <code>DNS_ZONE_NOT_FOUND</code>, <code>DNS_RECORD_EXISTS</code></td></tr>
<tr><td>proxy</td><td><code>PROXY_CADDY_UNREACHABLE</code>, <code>PROXY_CONFIG_INVALID</code>, <code>PROXY_UPSTREAM_TIMEOUT</code></td></tr>
<tr><td>cert</td><td><code>CERT_ISSUANCE_FAILED</code>, <code>CERT_EXPIRED</code>, <code>CERT_NOT_TRUSTED</code></td></tr>
<tr><td>license</td><td><code>LICENSE_EXPIRED</code>, <code>LICENSE_INVALID</code>, <code>LICENSE_MACHINE_LIMIT</code></td></tr>
<tr><td>user</td><td><code>USER_NOT_FOUND</code>, <code>USER_ALREADY_EXISTS</code>, <code>USER_INVITE_EXPIRED</code></td></tr>
<tr><td>backup</td><td><code>BACKUP_FAILED</code>, <code>BACKUP_CORRUPT</code>, <code>RESTORE_CONFLICT</code></td></tr>
<tr><td>recipe</td><td><code>RECIPE_INVALID</code>, <code>RECIPE_COMPONENT_FAILED</code> (Premium)</td></tr>
<tr><td>swarm</td><td><code>SWARM_NOT_INITIALIZED</code>, <code>SWARM_NODE_UNREACHABLE</code> (Premium)</td></tr>
<tr><td>fleet</td><td><code>FLEET_HOST_OFFLINE</code>, <code>FLEET_DEPLOY_PLAN_FAILED</code> (Premium)</td></tr>
</tbody>
</table>
<p>
Handle errors by code in your automation:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`try {
await dc.services.deploy({ template: 'postgres', name: 'db', hostname: 'db.lab' });
} catch (err) {
if (err.code === 'DEPLOY_PORT_CONFLICT') {
// pick a different port and retry
} else if (err.code === 'LICENSE_EXPIRED') {
// alert ops to renew
} else {
throw err; // unknown — surface to the operator
}
}`}</code></pre>
<h2>Why automation matters</h2>
<p>
DashCaddy is more than a dashboard because it can execute the infrastructure chain around a service, not just show service state after the fact.
DashCaddy can execute the full infrastructure chain around a service, not just report its state after the fact.
Between the REST API, the AI Intent Router, MCP, WebSockets, Prometheus, and the plugin system, you
have every surface you need to make DashCaddy a first-class citizen of your automation stack. Start with a
simple <code>curl</code> call, and add AI and event-driven flows as your needs grow.
</p>
<h2>Reference direction</h2>
<p>
The repo already includes an OpenAPI file, which means the public API docs can mature into a proper reference section as the external contract is stabilized.
For the infrastructure that backs all of this, see <a href="/docs/integrations">Integrations</a>. When things go
wrong, the <a href="/docs/troubleshooting">Troubleshooting</a> guide walks each layer with commands and fixes.
</p>
</DocsLayout>
<Footer />
+128
View File
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Actual Budget — DashCaddy Docs',
description: 'Install and configure Actual Budget via DashCaddy. Privacy-focused budgeting app with envelope budgeting',
};
export default function actualBudgetDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Actual Budget"
intro="Privacy-focused budgeting app with envelope budgeting"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">actualbudget/actual-server:latest</code></span>
</div>
<h2>What is Actual Budget?</h2>
<p>Privacy-focused budgeting app with envelope budgeting</p>
<p>Actual Budget ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Actual Budget, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Actual Budget</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>budget</code>), host port (default: <code>5006</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "actual-budget",
"config": &#123;
"subdomain": "budget",
"port": 5006
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Actual Budget on my home host and expose it at budget.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull actualbudget/actual-server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Create your first budget in the web interface</li>
<li>Import transactions from your bank (OFX, QFX, CSV)</li>
<li>Set up envelope categories for spending control</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/actual-budget/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull actualbudget/actual-server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Actual Budget:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>actual-budget</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Adminer — DashCaddy Docs',
description: 'Install and configure Adminer via DashCaddy. Lightweight database management in single PHP file',
};
export default function adminerDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Adminer"
intro="Lightweight database management in single PHP file"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Database</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">adminer:latest</code></span>
</div>
<h2>What is Adminer?</h2>
<p>Lightweight database management in single PHP file</p>
<p>Adminer ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Adminer, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Adminer</strong> from the Database category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>adminer</code>), host port (default: <code>8087</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "adminer",
"config": &#123;
"subdomain": "adminer",
"port": 8087
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Adminer on my home host and expose it at adminer.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull adminer:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Connect to your database servers</li>
<li>Supports MySQL, PostgreSQL, SQLite, etc.</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/adminer:/var/www/html</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>ADMINER_DEFAULT_SERVER</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull adminer:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Adminer:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>adminer</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+134
View File
@@ -0,0 +1,134 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Airsonic Advanced — DashCaddy Docs',
description: 'Install and configure Airsonic Advanced via DashCaddy. Free web-based media streamer',
};
export default function airsonicDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Airsonic Advanced"
intro="Free web-based media streamer"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/airsonic-advanced:latest</code></span>
</div>
<h2>What is Airsonic Advanced?</h2>
<p>Free web-based media streamer</p>
<p>Airsonic Advanced ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Airsonic Advanced, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Airsonic Advanced</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>airsonic</code>), host port (default: <code>4040</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "airsonic",
"config": &#123;
"subdomain": "airsonic",
"port": 4040
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Airsonic Advanced on my home host and expose it at airsonic.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/airsonic-advanced:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Default login: admin/admin</li>
<li>Configure media folders</li>
<li>Set up transcoding</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/airsonic/config:/config</code></li>
<li><code>/music:/music</code></li>
<li><code>/podcasts:/podcasts</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/airsonic-advanced:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Airsonic Advanced:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>airsonic</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
@@ -0,0 +1,138 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Audiobookshelf — DashCaddy Docs',
description: 'Install and configure Audiobookshelf via DashCaddy. Self-hosted audiobook and podcast server',
};
export default function audiobookshelfDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Audiobookshelf"
intro="Self-hosted audiobook and podcast server"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/advplyr/audiobookshelf:latest</code></span>
</div>
<h2>What is Audiobookshelf?</h2>
<p>Self-hosted audiobook and podcast server</p>
<p>Audiobookshelf ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Audiobookshelf, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>A host path containing your media. Default suggestion: <code>/media/audiobooks</code>. The deploy form / API payload <code>config.mediaPath</code> must be readable by the container UID (usually <code>1000</code>).</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Audiobookshelf</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>audiobooks</code>), host port (default: <code>13378</code>), and the media library path.</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "audiobookshelf",
"config": &#123;
"subdomain": "audiobooks",
"port": 13378,
"mediaPath": "/media/audiobooks"
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Audiobookshelf on my home host and expose it at audiobooks.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/advplyr/audiobookshelf:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Create your account on first access</li>
<li>Add your audiobook library folders</li>
<li>Download the mobile app for offline listening</li>
</ol>
<h2>Media library path notes</h2>
<p>The media mount path you pass as <code>mediaPath</code> in the deploy payload is mounted as <code>/audiobooks</code> inside the container. Bind a host directory containing your media library (movies, TV shows, music, etc.).</p>
<ul>
<li><strong>UID/GID:</strong> Audiobookshelf runs as a non-root user. If you see permission errors in the dashboard Logs tab, run <code>chown -R 1000:1000 /media/audiobooks</code> on the host.</li>
<li><strong>Multi-library:</strong> bind the parent folder and let Audiobookshelf discover subfolders.</li>
</ul>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/audiobookshelf/config:/config</code></li>
<li><code>/opt/audiobookshelf/metadata:/metadata</code></li>
<li><code>MEDIA_PATH:/audiobooks</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/advplyr/audiobookshelf:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Audiobookshelf:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>Library shows empty:</strong> confirm <code>mediaPath</code> is readable by the container UID and that the directory contains the file extensions Audiobookshelf indexes.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>audiobookshelf</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Authentik — DashCaddy Docs',
description: 'Install and configure Authentik via DashCaddy. Identity provider and single sign-on platform',
};
export default function authentikDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Authentik"
intro="Identity provider and single sign-on platform"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Security</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/goauthentik/server:latest</code></span>
</div>
<h2>What is Authentik?</h2>
<p>Identity provider and single sign-on platform</p>
<p>Authentik ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Authentik, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Authentik</strong> from the Security category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>auth</code>), host port (default: <code>9010</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/-/health/live/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "authentik",
"config": &#123;
"subdomain": "auth",
"port": 9010
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Authentik on my home host and expose it at auth.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/goauthentik/server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Requires a PostgreSQL database and Redis instance</li>
<li>Consider deploying via the Dev Environment recipe for full stack</li>
<li>Set up flows for authentication, enrollment, and recovery</li>
<li>Configure OAuth2/OIDC providers for SSO with other apps</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/authentik/media:/media</code></li>
<li><code>/opt/authentik/templates:/templates</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>AUTHENTIK_SECRET_KEY</code></li>
<li><code>AUTHENTIK_ERROR_REPORTING__ENABLED</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/goauthentik/server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Authentik:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/-/health/live/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>authentik</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+134
View File
@@ -0,0 +1,134 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Bazarr — DashCaddy Docs',
description: 'Install and configure Bazarr via DashCaddy. Automatic subtitle downloader for Sonarr and Radarr',
};
export default function bazarrDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Bazarr"
intro="Automatic subtitle downloader for Sonarr and Radarr"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/bazarr:latest</code></span>
</div>
<h2>What is Bazarr?</h2>
<p>Automatic subtitle downloader for Sonarr and Radarr</p>
<p>Bazarr ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Bazarr, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Bazarr</strong> from the Media Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>bazarr</code>), host port (default: <code>6767</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "bazarr",
"config": &#123;
"subdomain": "bazarr",
"port": 6767
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Bazarr on my home host and expose it at bazarr.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/bazarr:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Connect to Sonarr and Radarr</li>
<li>Configure subtitle providers</li>
<li>Set language preferences</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/bazarr/config:/config</code></li>
<li><code>/movies:/movies</code></li>
<li><code>/tv:/tv</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/bazarr:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Bazarr:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>bazarr</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+135
View File
@@ -0,0 +1,135 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install BIND9 DNS Server — DashCaddy Docs',
description: 'Install and configure BIND9 DNS Server via DashCaddy. Industry-standard DNS server - powerful and flexible',
};
export default function bind9DocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install BIND9 DNS Server"
intro="Industry-standard DNS server - powerful and flexible"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: DNS</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ubuntu/bind9:latest</code></span>
</div>
<h2>What is BIND9 DNS Server?</h2>
<p>Industry-standard DNS server - powerful and flexible</p>
<p>BIND9 DNS Server ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using BIND9 DNS Server, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>BIND9 DNS Server</strong> from the DNS category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>dns2</code>), host port (default: <code>953</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>tcp://localhost:53</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "bind9",
"config": &#123;
"subdomain": "dns2",
"port": 953
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy BIND9 DNS Server on my home host and expose it at dns2.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ubuntu/bind9:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure zone files in /opt/bind9/config/</li>
<li>Create named.conf.local for your .sami zone</li>
<li>Add zone file: /opt/bind9/records/db.sami</li>
<li>Restart container to apply changes</li>
<li>Test with: dig @localhost sami</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/bind9/config:/etc/bind</code></li>
<li><code>/opt/bind9/cache:/var/cache/bind</code></li>
<li><code>/opt/bind9/records:/var/lib/bind</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>BIND9_USER</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ubuntu/bind9:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with BIND9 DNS Server:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>tcp://localhost:53</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>bind9</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+136
View File
@@ -0,0 +1,136 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install BookStack — DashCaddy Docs',
description: 'Install and configure BookStack via DashCaddy. Simple wiki and documentation platform',
};
export default function bookstackDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install BookStack"
intro="Simple wiki and documentation platform"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/bookstack:latest</code></span>
</div>
<h2>What is BookStack?</h2>
<p>Simple wiki and documentation platform</p>
<p>BookStack ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using BookStack, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>BookStack</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>wiki</code>), host port (default: <code>8091</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "bookstack",
"config": &#123;
"subdomain": "wiki",
"port": 8091
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy BookStack on my home host and expose it at wiki.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/bookstack:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Requires MariaDB/MySQL database</li>
<li>Default login: admin@admin.com / password</li>
<li>Change default credentials</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/bookstack/config:/config</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>APP_URL</code></li>
<li><code>DB_HOST</code></li>
<li><code>DB_DATABASE</code></li>
<li><code>DB_USERNAME</code></li>
<li><code>DB_PASSWORD</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/bookstack:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with BookStack:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>bookstack</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+141
View File
@@ -0,0 +1,141 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Calibre-Web — DashCaddy Docs',
description: 'Install and configure Calibre-Web via DashCaddy. Web-based ebook manager and reader',
};
export default function calibreWebDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Calibre-Web"
intro="Web-based ebook manager and reader"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">lscr.io/linuxserver/calibre-web:latest</code></span>
</div>
<h2>What is Calibre-Web?</h2>
<p>Web-based ebook manager and reader</p>
<p>Calibre-Web ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Calibre-Web, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>A host path containing your media. Default suggestion: <code>/media/books</code>. The deploy form / API payload <code>config.mediaPath</code> must be readable by the container UID (usually <code>1000</code>).</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Calibre-Web</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>books</code>), host port (default: <code>8083</code>), and the media library path.</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "calibre-web",
"config": &#123;
"subdomain": "books",
"port": 8083,
"mediaPath": "/media/books"
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Calibre-Web on my home host and expose it at books.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull lscr.io/linuxserver/calibre-web:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Default login: admin / admin123</li>
<li>Point to your Calibre database location on first setup</li>
<li>Supports EPUB, PDF, MOBI, and more formats</li>
</ol>
<h2>Media library path notes</h2>
<p>The media mount path you pass as <code>mediaPath</code> in the deploy payload is mounted as <code>/books</code> inside the container. Bind a host directory containing your media library (movies, TV shows, music, etc.).</p>
<ul>
<li><strong>UID/GID:</strong> Calibre-Web runs as a non-root user. If you see permission errors in the dashboard Logs tab, run <code>chown -R 1000:1000 /media/books</code> on the host.</li>
<li><strong>Multi-library:</strong> bind the parent folder and let Calibre-Web discover subfolders.</li>
</ul>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/calibre-web/config:/config</code></li>
<li><code>MEDIA_PATH:/books</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull lscr.io/linuxserver/calibre-web:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Calibre-Web:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>Library shows empty:</strong> confirm <code>mediaPath</code> is readable by the container UID and that the directory contains the file extensions Calibre-Web indexes.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>calibre-web</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Change Detection — DashCaddy Docs',
description: 'Install and configure Change Detection via DashCaddy. Monitor websites for changes',
};
export default function changedetectionDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Change Detection"
intro="Monitor websites for changes"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Utilities</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/dgtlmoon/changedetection.io:latest</code></span>
</div>
<h2>What is Change Detection?</h2>
<p>Monitor websites for changes</p>
<p>Change Detection ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Change Detection, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Change Detection</strong> from the Utilities category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>watch</code>), host port (default: <code>5001</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "changedetection",
"config": &#123;
"subdomain": "watch",
"port": 5001
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Change Detection on my home host and expose it at watch.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/dgtlmoon/changedetection.io:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Add URLs to monitor</li>
<li>Configure check frequency</li>
<li>Set up notifications</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/changedetection/data:/datastore</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/dgtlmoon/changedetection.io:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Change Detection:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>changedetection</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install CoreDNS — DashCaddy Docs',
description: 'Install and configure CoreDNS via DashCaddy. Cloud-native DNS server - lightweight and flexible',
};
export default function corednsDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install CoreDNS"
intro="Cloud-native DNS server - lightweight and flexible"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: DNS</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">coredns/coredns:latest</code></span>
</div>
<h2>What is CoreDNS?</h2>
<p>Cloud-native DNS server - lightweight and flexible</p>
<p>CoreDNS ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using CoreDNS, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>CoreDNS</strong> from the DNS category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>dns4</code>), host port (default: <code>53</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>tcp://localhost:53</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "coredns",
"config": &#123;
"subdomain": "dns4",
"port": 53
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy CoreDNS on my home host and expose it at dns4.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull coredns/coredns:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Create Corefile in /opt/coredns/config/</li>
<li>Define .sami zone with file plugin</li>
<li>Create zone file with your records</li>
<li>Restart container to load config</li>
<li>Test with: dig @localhost test.sami</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/coredns/config:/etc/coredns</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull coredns/coredns:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with CoreDNS:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>tcp://localhost:53</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>coredns</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install CrowdSec — DashCaddy Docs',
description: 'Install and configure CrowdSec via DashCaddy. Collaborative intrusion prevention system',
};
export default function crowdsecDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install CrowdSec"
intro="Collaborative intrusion prevention system"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Security</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">crowdsecurity/crowdsec:latest</code></span>
</div>
<h2>What is CrowdSec?</h2>
<p>Collaborative intrusion prevention system</p>
<p>CrowdSec ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using CrowdSec, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>CrowdSec</strong> from the Security category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>crowdsec</code>), host port (default: <code>8091</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/health</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "crowdsec",
"config": &#123;
"subdomain": "crowdsec",
"port": 8091
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy CrowdSec on my home host and expose it at crowdsec.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull crowdsecurity/crowdsec:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Register at app.crowdsec.net for community threat intelligence</li>
<li>Install bouncers on your reverse proxy for active blocking</li>
<li>CrowdSec analyzes logs and shares threat data with the community</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/crowdsec/config:/etc/crowdsec</code></li>
<li><code>/opt/crowdsec/data:/var/lib/crowdsec/data</code></li>
<li><code>/var/log:/var/log:ro</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull crowdsecurity/crowdsec:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with CrowdSec:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/health</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>crowdsec</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install DashCA — DashCaddy Docs',
description: 'Install and configure DashCA via DashCaddy. One-click root CA certificate installer for your network',
};
export default function dashcaDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install DashCA"
intro="One-click root CA certificate installer for your network"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Security</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">N/A</code></span>
</div>
<h2>What is DashCA?</h2>
<p>One-click root CA certificate installer for your network</p>
<p>DashCA ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using DashCA, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>DashCA</strong> from the Security category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>ca</code>), host port (default: <code>32400</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/healthz</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "dashca",
"config": &#123;
"subdomain": "ca",
"port": 32400
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy DashCA on my home host and expose it at ca.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull N/A</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>New devices: visit http://ca.sami (HTTP, no certificate needed)</li>
<li>Click the 'Install Certificate' button for your platform</li>
<li>Follow platform-specific instructions</li>
<li>Verify all *.sami domains now show secure connections</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull N/A</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with DashCA:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/healthz</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>dashca</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+127
View File
@@ -0,0 +1,127 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Digital Clock — DashCaddy Docs',
description: 'Install and configure Digital Clock via DashCaddy. Live digital clock with time, date, and day of week',
};
export default function digitalClockDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Digital Clock"
intro="Live digital clock with time, date, and day of week"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Utilities</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">N/A</code></span>
</div>
<h2>What is Digital Clock?</h2>
<p>Live digital clock with time, date, and day of week</p>
<p>Digital Clock ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Digital Clock, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Digital Clock</strong> from the Utilities category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>digital-clock</code>), host port (default: <code>32400</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/healthz</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "digital-clock",
"config": &#123;
"subdomain": "digital-clock",
"port": 32400
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Digital Clock on my home host and expose it at digital-clock.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull N/A</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Clock appears in the top bar to the right of the weather widget</li>
<li>No configuration needed runs automatically</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull N/A</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Digital Clock:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/healthz</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>digital-clock</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+128
View File
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Dozzle — DashCaddy Docs',
description: 'Install and configure Dozzle via DashCaddy. Real-time Docker container log viewer',
};
export default function dozzleDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Dozzle"
intro="Real-time Docker container log viewer"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Monitoring</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">amir20/dozzle:latest</code></span>
</div>
<h2>What is Dozzle?</h2>
<p>Real-time Docker container log viewer</p>
<p>Dozzle ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Dozzle, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Dozzle</strong> from the Monitoring category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>logs</code>), host port (default: <code>8088</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "dozzle",
"config": &#123;
"subdomain": "logs",
"port": 8088
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Dozzle on my home host and expose it at logs.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull amir20/dozzle:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>View real-time logs from all running containers</li>
<li>Filter and search across container logs</li>
<li>No configuration needed - auto-discovers containers</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/var/run/docker.sock:/var/run/docker.sock:ro</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull amir20/dozzle:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Dozzle:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>dozzle</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Drone CI — DashCaddy Docs',
description: 'Install and configure Drone CI via DashCaddy. Container-native continuous delivery platform',
};
export default function droneDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Drone CI"
intro="Container-native continuous delivery platform"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Development</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">drone/drone:latest</code></span>
</div>
<h2>What is Drone CI?</h2>
<p>Container-native continuous delivery platform</p>
<p>Drone CI ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Drone CI, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Drone CI</strong> from the Development category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>drone</code>), host port (default: <code>8090</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "drone",
"config": &#123;
"subdomain": "drone",
"port": 8090
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Drone CI on my home host and expose it at drone.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull drone/drone:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure Git provider integration</li>
<li>Set up shared secret</li>
<li>Deploy Drone runners</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/drone/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>DRONE_GITEA_SERVER</code></li>
<li><code>DRONE_RPC_SECRET</code></li>
<li><code>DRONE_SERVER_HOST</code></li>
<li><code>DRONE_SERVER_PROTO</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull drone/drone:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Drone CI:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>drone</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+142
View File
@@ -0,0 +1,142 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Emby — DashCaddy Docs',
description: 'Install and configure Emby via DashCaddy. Personal media server with apps for all devices',
};
export default function embyDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Emby"
intro="Personal media server with apps for all devices"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">emby/embyserver:latest</code></span>
</div>
<h2>What is Emby?</h2>
<p>Personal media server with apps for all devices</p>
<p>Emby ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Emby, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>A host path containing your media. Default suggestion: <code>/media</code>. The deploy form / API payload <code>config.mediaPath</code> must be readable by the container UID (usually <code>1000</code>).</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Emby</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>emby</code>), host port (default: <code>8096</code>), and the media library path.</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/emby/web/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "emby",
"config": &#123;
"subdomain": "emby",
"port": 8096,
"mediaPath": "/media"
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Emby on my home host and expose it at emby.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull emby/embyserver:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Complete the initial setup wizard at the web interface</li>
<li>Add your media libraries (Movies, TV Shows, Music)</li>
<li>Configure user accounts and permissions</li>
<li>Install Emby apps on your devices for remote access</li>
</ol>
<h2>Media library path notes</h2>
<p>The media mount path you pass as <code>mediaPath</code> in the deploy payload is mounted as <code>/media</code> inside the container. Bind a host directory containing your media library (movies, TV shows, music, etc.).</p>
<ul>
<li><strong>UID/GID:</strong> Emby runs as a non-root user. If you see permission errors in the dashboard Logs tab, run <code>chown -R 1000:1000 /media</code> on the host.</li>
<li><strong>Multi-library:</strong> bind the parent folder and let Emby discover subfolders.</li>
</ul>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/emby/config:/config</code></li>
<li><code>/opt/emby/cache:/cache</code></li>
<li><code>MEDIA_PATH:/media</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>UID</code></li>
<li><code>GID</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull emby/embyserver:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Emby:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>Library shows empty:</strong> confirm <code>mediaPath</code> is readable by the container UID and that the directory contains the file extensions Emby indexes.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/emby/web/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>emby</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+128
View File
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Excalidraw — DashCaddy Docs',
description: 'Install and configure Excalidraw via DashCaddy. Collaborative virtual whiteboard for sketching and diagrams',
};
export default function excalidrawDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Excalidraw"
intro="Collaborative virtual whiteboard for sketching and diagrams"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">excalidraw/excalidraw:latest</code></span>
</div>
<h2>What is Excalidraw?</h2>
<p>Collaborative virtual whiteboard for sketching and diagrams</p>
<p>Excalidraw ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Excalidraw, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Excalidraw</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>draw</code>), host port (default: <code>8086</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "excalidraw",
"config": &#123;
"subdomain": "draw",
"port": 8086
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Excalidraw on my home host and expose it at draw.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull excalidraw/excalidraw:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Start drawing immediately - no account needed</li>
<li>Share drawings via link for real-time collaboration</li>
<li>Export as PNG, SVG, or Excalidraw file</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/excalidraw/data:/var/lib/excalidraw</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull excalidraw/excalidraw:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Excalidraw:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>excalidraw</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install FileBrowser — DashCaddy Docs',
description: 'Install and configure FileBrowser via DashCaddy. Web-based file manager with sharing capabilities',
};
export default function filebrowserDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install FileBrowser"
intro="Web-based file manager with sharing capabilities"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Files</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">filebrowser/filebrowser:latest</code></span>
</div>
<h2>What is FileBrowser?</h2>
<p>Web-based file manager with sharing capabilities</p>
<p>FileBrowser ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using FileBrowser, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>FileBrowser</strong> from the Files category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>files</code>), host port (default: <code>8085</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "filebrowser",
"config": &#123;
"subdomain": "files",
"port": 8085
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy FileBrowser on my home host and expose it at files.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull filebrowser/filebrowser:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Default login: admin/admin</li>
<li>Change default password immediately</li>
<li>Configure user permissions and shares</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/filebrowser/data:/srv</code></li>
<li><code>/opt/filebrowser/database:/database</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull filebrowser/filebrowser:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with FileBrowser:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>filebrowser</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Gitea — DashCaddy Docs',
description: 'Install and configure Gitea via DashCaddy. Lightweight self-hosted Git service',
};
export default function giteaDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Gitea"
intro="Lightweight self-hosted Git service"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Development</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">gitea/gitea:latest</code></span>
</div>
<h2>What is Gitea?</h2>
<p>Lightweight self-hosted Git service</p>
<p>Gitea ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Gitea, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Gitea</strong> from the Development category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>gitea</code>), host port (default: <code>3005</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "gitea",
"config": &#123;
"subdomain": "gitea",
"port": 3005
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Gitea on my home host and expose it at gitea.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull gitea/gitea:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Complete initial setup wizard</li>
<li>Create admin account</li>
<li>Configure SSH access</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/gitea/data:/data</code></li>
<li><code>/etc/timezone:/etc/timezone:ro</code></li>
<li><code>/etc/localtime:/etc/localtime:ro</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>USER_UID</code></li>
<li><code>USER_GID</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull gitea/gitea:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Gitea:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>gitea</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Grafana — DashCaddy Docs',
description: 'Install and configure Grafana via DashCaddy. Analytics and interactive visualization platform',
};
export default function grafanaDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Grafana"
intro="Analytics and interactive visualization platform"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Monitoring</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">grafana/grafana:latest</code></span>
</div>
<h2>What is Grafana?</h2>
<p>Analytics and interactive visualization platform</p>
<p>Grafana ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Grafana, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Grafana</strong> from the Monitoring category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>grafana</code>), host port (default: <code>3000</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/health</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "grafana",
"config": &#123;
"subdomain": "grafana",
"port": 3000
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Grafana on my home host and expose it at grafana.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull grafana/grafana:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Open the deployed URL (returned in the response as <code>url</code>, or visible in the dashboard).</li>
<li>Complete the upstream Grafana setup wizard (admin account, library paths, EULA).</li>
<li>Restore from a backup if one exists: <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with the backup ID from <code>GET /api/v1/backups/history</code>.</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/grafana/data:/var/lib/grafana</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>GF_SECURITY_ADMIN_PASSWORD</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull grafana/grafana:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Grafana:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/health</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>grafana</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Homarr — DashCaddy Docs',
description: 'Install and configure Homarr via DashCaddy. Sleek dashboard for all your services',
};
export default function homarrDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Homarr"
intro="Sleek dashboard for all your services"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Utilities</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/ajnart/homarr:latest</code></span>
</div>
<h2>What is Homarr?</h2>
<p>Sleek dashboard for all your services</p>
<p>Homarr ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Homarr, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Homarr</strong> from the Utilities category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>homarr</code>), host port (default: <code>7575</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "homarr",
"config": &#123;
"subdomain": "homarr",
"port": 7575
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Homarr on my home host and expose it at homarr.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/ajnart/homarr:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Add your services via UI</li>
<li>Configure integrations</li>
<li>Customize layout and appearance</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/homarr/configs:/app/data/configs</code></li>
<li><code>/opt/homarr/icons:/app/public/icons</code></li>
<li><code>/var/run/docker.sock:/var/run/docker.sock:ro</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/ajnart/homarr:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Homarr:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>homarr</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+131
View File
@@ -0,0 +1,131 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Home Assistant — DashCaddy Docs',
description: 'Install and configure Home Assistant via DashCaddy. Open source home automation platform',
};
export default function homeassistantDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Home Assistant"
intro="Open source home automation platform"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Home Automation</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">homeassistant/home-assistant:stable</code></span>
</div>
<h2>What is Home Assistant?</h2>
<p>Open source home automation platform</p>
<p>Home Assistant ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Home Assistant, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Home Assistant</strong> from the Home Automation category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>home</code>), host port (default: <code>8123</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "homeassistant",
"config": &#123;
"subdomain": "home",
"port": 8123
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Home Assistant on my home host and expose it at home.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull homeassistant/home-assistant:stable</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Complete onboarding wizard</li>
<li>Add integrations for your smart devices</li>
<li>Create automations and dashboards</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/homeassistant/config:/config</code></li>
<li><code>/etc/localtime:/etc/localtime:ro</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull homeassistant/home-assistant:stable</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Home Assistant:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>homeassistant</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Homepage — DashCaddy Docs',
description: 'Install and configure Homepage via DashCaddy. Highly customizable application dashboard',
};
export default function homepageDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Homepage"
intro="Highly customizable application dashboard"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Utilities</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/gethomepage/homepage:latest</code></span>
</div>
<h2>What is Homepage?</h2>
<p>Highly customizable application dashboard</p>
<p>Homepage ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Homepage, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Homepage</strong> from the Utilities category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>dashboard</code>), host port (default: <code>3008</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "homepage",
"config": &#123;
"subdomain": "dashboard",
"port": 3008
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Homepage on my home host and expose it at dashboard.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/gethomepage/homepage:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Edit config files to add services</li>
<li>Configure widgets</li>
<li>Customize appearance</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/homepage/config:/app/config</code></li>
<li><code>/var/run/docker.sock:/var/run/docker.sock:ro</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/gethomepage/homepage:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Homepage:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>homepage</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+135
View File
@@ -0,0 +1,135 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Immich — DashCaddy Docs',
description: 'Install and configure Immich via DashCaddy. Self-hosted Google Photos alternative',
};
export default function immichDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Immich"
intro="Self-hosted Google Photos alternative"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Photos</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/immich-app/immich-server:latest</code></span>
</div>
<h2>What is Immich?</h2>
<p>Self-hosted Google Photos alternative</p>
<p>Immich ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Immich, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Immich</strong> from the Photos category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>photos</code>), host port (default: <code>2283</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/server-info/ping</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "immich",
"config": &#123;
"subdomain": "photos",
"port": 2283
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Immich on my home host and expose it at photos.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/immich-app/immich-server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Requires PostgreSQL and Redis</li>
<li>Install mobile apps for backup</li>
<li>Configure machine learning for face detection</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/immich/upload:/usr/src/app/upload</code></li>
<li><code>/opt/immich/library:/usr/src/app/library</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>DB_HOSTNAME</code></li>
<li><code>DB_USERNAME</code></li>
<li><code>DB_PASSWORD</code></li>
<li><code>DB_DATABASE_NAME</code></li>
<li><code>REDIS_HOSTNAME</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/immich-app/immich-server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Immich:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/server-info/ping</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>immich</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+128
View File
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install IT Tools — DashCaddy Docs',
description: 'Install and configure IT Tools via DashCaddy. Collection of handy developer and IT tools in one place',
};
export default function itToolsDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install IT Tools"
intro="Collection of handy developer and IT tools in one place"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Utilities</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">corentinth/it-tools:latest</code></span>
</div>
<h2>What is IT Tools?</h2>
<p>Collection of handy developer and IT tools in one place</p>
<p>IT Tools ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using IT Tools, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>IT Tools</strong> from the Utilities category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>tools</code>), host port (default: <code>8087</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "it-tools",
"config": &#123;
"subdomain": "tools",
"port": 8087
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy IT Tools on my home host and expose it at tools.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull corentinth/it-tools:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Access the web interface for instant tools access</li>
<li>Includes: hash generators, UUID, JWT decoder, base64, regex tester, and 70+ more</li>
<li>No configuration needed</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/it-tools/config:/config</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull corentinth/it-tools:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with IT Tools:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>it-tools</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install JDownloader 2 — DashCaddy Docs',
description: 'Install and configure JDownloader 2 via DashCaddy. Download manager for file hosting sites',
};
export default function jdownloaderDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install JDownloader 2"
intro="Download manager for file hosting sites"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Downloads</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">jlesage/jdownloader-2:latest</code></span>
</div>
<h2>What is JDownloader 2?</h2>
<p>Download manager for file hosting sites</p>
<p>JDownloader 2 ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using JDownloader 2, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>JDownloader 2</strong> from the Downloads category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>jdownloader</code>), host port (default: <code>5800</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "jdownloader",
"config": &#123;
"subdomain": "jdownloader",
"port": 5800
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy JDownloader 2 on my home host and expose it at jdownloader.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull jlesage/jdownloader-2:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Access web interface to configure</li>
<li>Link to MyJDownloader account</li>
<li>Configure download paths</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/jdownloader/config:/config</code></li>
<li><code>/downloads:/output</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull jlesage/jdownloader-2:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with JDownloader 2:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>jdownloader</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+140
View File
@@ -0,0 +1,140 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Jellyfin — DashCaddy Docs',
description: 'Install and configure Jellyfin via DashCaddy. Free software media system - alternative to Plex',
};
export default function jellyfinDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Jellyfin"
intro="Free software media system - alternative to Plex"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">jellyfin/jellyfin:latest</code></span>
</div>
<h2>What is Jellyfin?</h2>
<p>Free software media system - alternative to Plex</p>
<p>Jellyfin ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Jellyfin, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>A host path containing your media. Default suggestion: <code>/media</code>. The deploy form / API payload <code>config.mediaPath</code> must be readable by the container UID (usually <code>1000</code>).</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Jellyfin</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>jellyfin</code>), host port (default: <code>8096</code>), and the media library path.</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/health</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "jellyfin",
"config": &#123;
"subdomain": "jellyfin",
"port": 8096,
"mediaPath": "/media"
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Jellyfin on my home host and expose it at jellyfin.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull jellyfin/jellyfin:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Complete the initial setup wizard</li>
<li>Add your media libraries</li>
<li>Configure user accounts and permissions</li>
</ol>
<h2>Media library path notes</h2>
<p>The media mount path you pass as <code>mediaPath</code> in the deploy payload is mounted as <code>/media</code> inside the container. Bind a host directory containing your media library (movies, TV shows, music, etc.).</p>
<ul>
<li><strong>UID/GID:</strong> Jellyfin runs as a non-root user. If you see permission errors in the dashboard Logs tab, run <code>chown -R 1000:1000 /media</code> on the host.</li>
<li><strong>Multi-library:</strong> bind the parent folder and let Jellyfin discover subfolders.</li>
</ul>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/jellyfin/config:/config</code></li>
<li><code>/opt/jellyfin/cache:/cache</code></li>
<li><code>MEDIA_PATH:/media</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>JELLYFIN_PublishedServerUrl</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull jellyfin/jellyfin:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Jellyfin:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>Library shows empty:</strong> confirm <code>mediaPath</code> is readable by the container UID and that the directory contains the file extensions Jellyfin indexes.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/health</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>jellyfin</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+128
View File
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Jenkins — DashCaddy Docs',
description: 'Install and configure Jenkins via DashCaddy. Automation server for CI/CD pipelines',
};
export default function jenkinsDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Jenkins"
intro="Automation server for CI/CD pipelines"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Development</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">jenkins/jenkins:lts</code></span>
</div>
<h2>What is Jenkins?</h2>
<p>Automation server for CI/CD pipelines</p>
<p>Jenkins ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Jenkins, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Jenkins</strong> from the Development category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>jenkins</code>), host port (default: <code>8089</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/login</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "jenkins",
"config": &#123;
"subdomain": "jenkins",
"port": 8089
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Jenkins on my home host and expose it at jenkins.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull jenkins/jenkins:lts</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Get initial admin password from logs</li>
<li>Install suggested plugins</li>
<li>Create admin user</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/jenkins/data:/var/jenkins_home</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull jenkins/jenkins:lts</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Jenkins:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/login</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>jenkins</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+137
View File
@@ -0,0 +1,137 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Kavita — DashCaddy Docs',
description: 'Install and configure Kavita via DashCaddy. Digital reading platform for manga, comics, and books',
};
export default function kavitaDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Kavita"
intro="Digital reading platform for manga, comics, and books"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">jvmilazz0/kavita:latest</code></span>
</div>
<h2>What is Kavita?</h2>
<p>Digital reading platform for manga, comics, and books</p>
<p>Kavita ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Kavita, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>A host path containing your media. Default suggestion: <code>/media/reading</code>. The deploy form / API payload <code>config.mediaPath</code> must be readable by the container UID (usually <code>1000</code>).</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Kavita</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>kavita</code>), host port (default: <code>5004</code>), and the media library path.</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "kavita",
"config": &#123;
"subdomain": "kavita",
"port": 5004,
"mediaPath": "/media/reading"
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Kavita on my home host and expose it at kavita.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull jvmilazz0/kavita:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Create admin account on first access</li>
<li>Add library folders for manga, comics, or books</li>
<li>Supports EPUB, PDF, CBZ, CBR formats</li>
</ol>
<h2>Media library path notes</h2>
<p>The media mount path you pass as <code>mediaPath</code> in the deploy payload is mounted as <code>/data</code> inside the container. Bind a host directory containing your media library (movies, TV shows, music, etc.).</p>
<ul>
<li><strong>UID/GID:</strong> Kavita runs as a non-root user. If you see permission errors in the dashboard Logs tab, run <code>chown -R 1000:1000 /media/reading</code> on the host.</li>
<li><strong>Multi-library:</strong> bind the parent folder and let Kavita discover subfolders.</li>
</ul>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/kavita/config:/kavita/config</code></li>
<li><code>MEDIA_PATH:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull jvmilazz0/kavita:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Kavita:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>Library shows empty:</strong> confirm <code>mediaPath</code> is readable by the container UID and that the directory contains the file extensions Kavita indexes.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>kavita</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+139
View File
@@ -0,0 +1,139 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Komga — DashCaddy Docs',
description: 'Install and configure Komga via DashCaddy. Comic and manga media server with web reader',
};
export default function komgaDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Komga"
intro="Comic and manga media server with web reader"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">gotson/komga:latest</code></span>
</div>
<h2>What is Komga?</h2>
<p>Comic and manga media server with web reader</p>
<p>Komga ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Komga, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>A host path containing your media. Default suggestion: <code>/media/comics</code>. The deploy form / API payload <code>config.mediaPath</code> must be readable by the container UID (usually <code>1000</code>).</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Komga</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>komga</code>), host port (default: <code>25600</code>), and the media library path.</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "komga",
"config": &#123;
"subdomain": "komga",
"port": 25600,
"mediaPath": "/media/comics"
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Komga on my home host and expose it at komga.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull gotson/komga:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Create admin account on first access</li>
<li>Add your comic libraries (CBZ, CBR, PDF supported)</li>
<li>Use OPDS for third-party reader apps</li>
</ol>
<h2>Media library path notes</h2>
<p>The media mount path you pass as <code>mediaPath</code> in the deploy payload is mounted as <code>/data</code> inside the container. Bind a host directory containing your media library (movies, TV shows, music, etc.).</p>
<ul>
<li><strong>UID/GID:</strong> Komga runs as a non-root user. If you see permission errors in the dashboard Logs tab, run <code>chown -R 1000:1000 /media/comics</code> on the host.</li>
<li><strong>Multi-library:</strong> bind the parent folder and let Komga discover subfolders.</li>
</ul>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/komga/config:/config</code></li>
<li><code>MEDIA_PATH:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull gotson/komga:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Komga:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>Library shows empty:</strong> confirm <code>mediaPath</code> is readable by the container UID and that the directory contains the file extensions Komga indexes.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>komga</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+134
View File
@@ -0,0 +1,134 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Lidarr — DashCaddy Docs',
description: 'Install and configure Lidarr via DashCaddy. Music collection manager for Usenet and BitTorrent',
};
export default function lidarrDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Lidarr"
intro="Music collection manager for Usenet and BitTorrent"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/lidarr:latest</code></span>
</div>
<h2>What is Lidarr?</h2>
<p>Music collection manager for Usenet and BitTorrent</p>
<p>Lidarr ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Lidarr, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Lidarr</strong> from the Media Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>lidarr</code>), host port (default: <code>8686</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/v1/system/status</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "lidarr",
"config": &#123;
"subdomain": "lidarr",
"port": 8686
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Lidarr on my home host and expose it at lidarr.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/lidarr:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure download clients</li>
<li>Add indexers</li>
<li>Set up root folders for music</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/lidarr/config:/config</code></li>
<li><code>/downloads:/downloads</code></li>
<li><code>/music:/music</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/lidarr:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Lidarr:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/v1/system/status</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>lidarr</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+137
View File
@@ -0,0 +1,137 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Docker Mailserver — DashCaddy Docs',
description: 'Install and configure Docker Mailserver via DashCaddy. Full-featured email server with SMTP, IMAP, spam filtering',
};
export default function mailserverDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Docker Mailserver"
intro="Full-featured email server with SMTP, IMAP, spam filtering"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Communication</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">mailserver/docker-mailserver:latest</code></span>
</div>
<h2>What is Docker Mailserver?</h2>
<p>Full-featured email server with SMTP, IMAP, spam filtering</p>
<p>Docker Mailserver ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Docker Mailserver, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Docker Mailserver</strong> from the Communication category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>mail</code>), host port (default: <code>25</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "mailserver",
"config": &#123;
"subdomain": "mail",
"port": 25
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Docker Mailserver on my home host and expose it at mail.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull mailserver/docker-mailserver:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure DNS records (MX, SPF, DKIM, DMARC)</li>
<li>Create email accounts using setup.sh</li>
<li>Set up SSL certificates for secure connections</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/mailserver/data:/var/mail</code></li>
<li><code>/opt/mailserver/state:/var/mail-state</code></li>
<li><code>/opt/mailserver/logs:/var/log/mail</code></li>
<li><code>/opt/mailserver/config:/tmp/docker-mailserver</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>ENABLE_SPAMASSASSIN</code></li>
<li><code>ENABLE_CLAMAV</code></li>
<li><code>ENABLE_FAIL2BAN</code></li>
<li><code>ONE_DIR</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull mailserver/docker-mailserver:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Docker Mailserver:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>mailserver</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+131
View File
@@ -0,0 +1,131 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Matrix Synapse — DashCaddy Docs',
description: 'Install and configure Matrix Synapse via DashCaddy. Decentralized, secure messaging and collaboration',
};
export default function matrixDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Matrix Synapse"
intro="Decentralized, secure messaging and collaboration"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Communication</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">matrixdotorg/synapse:latest</code></span>
</div>
<h2>What is Matrix Synapse?</h2>
<p>Decentralized, secure messaging and collaboration</p>
<p>Matrix Synapse ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Matrix Synapse, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Matrix Synapse</strong> from the Communication category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>matrix</code>), host port (default: <code>8008</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/_matrix/client/versions</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "matrix",
"config": &#123;
"subdomain": "matrix",
"port": 8008
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Matrix Synapse on my home host and expose it at matrix.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull matrixdotorg/synapse:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Generate initial config with --generate</li>
<li>Configure homeserver.yaml</li>
<li>Set up federation if needed</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/matrix/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>SYNAPSE_SERVER_NAME</code></li>
<li><code>SYNAPSE_REPORT_STATS</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull matrixdotorg/synapse:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Matrix Synapse:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/_matrix/client/versions</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>matrix</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Mealie — DashCaddy Docs',
description: 'Install and configure Mealie via DashCaddy. Recipe manager and meal planner with grocery lists',
};
export default function mealieDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Mealie"
intro="Recipe manager and meal planner with grocery lists"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/mealie-recipes/mealie:latest</code></span>
</div>
<h2>What is Mealie?</h2>
<p>Recipe manager and meal planner with grocery lists</p>
<p>Mealie ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Mealie, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Mealie</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>mealie</code>), host port (default: <code>9925</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "mealie",
"config": &#123;
"subdomain": "mealie",
"port": 9925
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Mealie on my home host and expose it at mealie.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/mealie-recipes/mealie:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Default login: changeme@example.com / MyPassword</li>
<li>Import recipes from URLs or add them manually</li>
<li>Create meal plans and generate shopping lists</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/mealie/data:/app/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>ALLOW_SIGNUP</code></li>
<li><code>MAX_WORKERS</code></li>
<li><code>WEB_CONCURRENCY</code></li>
<li><code>BASE_URL</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/mealie-recipes/mealie:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Mealie:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>mealie</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+136
View File
@@ -0,0 +1,136 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Minecraft Server — DashCaddy Docs',
description: 'Install and configure Minecraft Server via DashCaddy. Minecraft Java Edition dedicated server',
};
export default function minecraftDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Minecraft Server"
intro="Minecraft Java Edition dedicated server"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Gaming</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">itzg/minecraft-server:latest</code></span>
</div>
<h2>What is Minecraft Server?</h2>
<p>Minecraft Java Edition dedicated server</p>
<p>Minecraft Server ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Minecraft Server, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Minecraft Server</strong> from the Gaming category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>mc</code>), host port (default: <code>25565</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>tcp://localhost:25565</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "minecraft",
"config": &#123;
"subdomain": "mc",
"port": 25565
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Minecraft Server on my home host and expose it at mc.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull itzg/minecraft-server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Server accepts the Minecraft EULA automatically</li>
<li>Connect with your Minecraft client to the server IP:port</li>
<li>Configure server.properties in the data volume for customization</li>
<li>Supports Vanilla, Paper, Forge, Fabric via TYPE environment variable</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/minecraft/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>EULA</code></li>
<li><code>TYPE</code></li>
<li><code>VERSION</code></li>
<li><code>MEMORY</code></li>
<li><code>MAX_PLAYERS</code></li>
<li><code>MOTD</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull itzg/minecraft-server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Minecraft Server:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>tcp://localhost:25565</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>minecraft</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+131
View File
@@ -0,0 +1,131 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install MongoDB — DashCaddy Docs',
description: 'Install and configure MongoDB via DashCaddy. Document-oriented NoSQL database',
};
export default function mongodbDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install MongoDB"
intro="Document-oriented NoSQL database"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Database</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">mongo:latest</code></span>
</div>
<h2>What is MongoDB?</h2>
<p>Document-oriented NoSQL database</p>
<p>MongoDB ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using MongoDB, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>MongoDB</strong> from the Database category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>mongo</code>), host port (default: <code>27017</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "mongodb",
"config": &#123;
"subdomain": "mongo",
"port": 27017
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy MongoDB on my home host and expose it at mongo.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull mongo:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Change default admin password</li>
<li>Create application databases and users</li>
<li>Configure replica set if needed</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/mongodb/data:/data/db</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>MONGO_INITDB_ROOT_USERNAME</code></li>
<li><code>MONGO_INITDB_ROOT_PASSWORD</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull mongo:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with MongoDB:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>mongodb</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+132
View File
@@ -0,0 +1,132 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Navidrome — DashCaddy Docs',
description: 'Install and configure Navidrome via DashCaddy. Modern music server and streamer',
};
export default function navidromeDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Navidrome"
intro="Modern music server and streamer"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">deluan/navidrome:latest</code></span>
</div>
<h2>What is Navidrome?</h2>
<p>Modern music server and streamer</p>
<p>Navidrome ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Navidrome, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Navidrome</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>music</code>), host port (default: <code>4533</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "navidrome",
"config": &#123;
"subdomain": "music",
"port": 4533
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Navidrome on my home host and expose it at music.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull deluan/navidrome:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Point to your music library</li>
<li>Create user accounts</li>
<li>Install Subsonic-compatible apps</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/navidrome/data:/data</code></li>
<li><code>/music:/music:ro</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>ND_SCANSCHEDULE</code></li>
<li><code>ND_LOGLEVEL</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull deluan/navidrome:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Navidrome:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>navidrome</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Nextcloud — DashCaddy Docs',
description: 'Install and configure Nextcloud via DashCaddy. Self-hosted productivity platform and file sync',
};
export default function nextcloudDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Nextcloud"
intro="Self-hosted productivity platform and file sync"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">nextcloud:latest</code></span>
</div>
<h2>What is Nextcloud?</h2>
<p>Self-hosted productivity platform and file sync</p>
<p>Nextcloud ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Nextcloud, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Nextcloud</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>cloud</code>), host port (default: <code>8080</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/status.php</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "nextcloud",
"config": &#123;
"subdomain": "cloud",
"port": 8080
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Nextcloud on my home host and expose it at cloud.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull nextcloud:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Change the default admin password</li>
<li>Configure trusted domains</li>
<li>Install recommended apps</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/nextcloud/html:/var/www/html</code></li>
<li><code>/opt/nextcloud/data:/var/www/html/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>NEXTCLOUD_ADMIN_USER</code></li>
<li><code>NEXTCLOUD_ADMIN_PASSWORD</code></li>
<li><code>NEXTCLOUD_TRUSTED_DOMAINS</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull nextcloud:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Nextcloud:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/status.php</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>nextcloud</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Node-RED — DashCaddy Docs',
description: 'Install and configure Node-RED via DashCaddy. Flow-based programming for IoT and automation',
};
export default function noderedDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Node-RED"
intro="Flow-based programming for IoT and automation"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Home Automation</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">nodered/node-red:latest</code></span>
</div>
<h2>What is Node-RED?</h2>
<p>Flow-based programming for IoT and automation</p>
<p>Node-RED ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Node-RED, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Node-RED</strong> from the Home Automation category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>nodered</code>), host port (default: <code>1880</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "nodered",
"config": &#123;
"subdomain": "nodered",
"port": 1880
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Node-RED on my home host and expose it at nodered.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull nodered/node-red:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Install additional nodes from palette</li>
<li>Create flows for automation</li>
<li>Connect to Home Assistant or MQTT</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/nodered/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull nodered/node-red:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Node-RED:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>nodered</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install NZBGet — DashCaddy Docs',
description: 'Install and configure NZBGet via DashCaddy. Efficient Usenet downloader',
};
export default function nzbgetDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install NZBGet"
intro="Efficient Usenet downloader"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Downloads</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/nzbget:latest</code></span>
</div>
<h2>What is NZBGet?</h2>
<p>Efficient Usenet downloader</p>
<p>NZBGet ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using NZBGet, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>NZBGet</strong> from the Downloads category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>nzbget</code>), host port (default: <code>6789</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "nzbget",
"config": &#123;
"subdomain": "nzbget",
"port": 6789
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy NZBGet on my home host and expose it at nzbget.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/nzbget:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Default login: nzbget/tegbzn6789</li>
<li>Configure news servers</li>
<li>Set up categories and paths</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/nzbget/config:/config</code></li>
<li><code>/downloads:/downloads</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/nzbget:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with NZBGet:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>nzbget</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+132
View File
@@ -0,0 +1,132 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Outline — DashCaddy Docs',
description: 'Install and configure Outline via DashCaddy. Modern team knowledge base and wiki',
};
export default function outlineDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Outline"
intro="Modern team knowledge base and wiki"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">outlinewiki/outline:latest</code></span>
</div>
<h2>What is Outline?</h2>
<p>Modern team knowledge base and wiki</p>
<p>Outline ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Outline, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Outline</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>outline</code>), host port (default: <code>3006</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "outline",
"config": &#123;
"subdomain": "outline",
"port": 3006
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Outline on my home host and expose it at outline.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull outlinewiki/outline:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Requires PostgreSQL and Redis</li>
<li>Configure OAuth provider</li>
<li>Set up S3-compatible storage</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/outline/data:/var/lib/outline/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>URL</code></li>
<li><code>SECRET_KEY</code></li>
<li><code>DATABASE_URL</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull outlinewiki/outline:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Outline:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>outline</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+638
View File
@@ -0,0 +1,638 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'App Catalog — DashCaddy Docs',
description: 'Browse all 77 one-click installable apps supported by DashCaddy, organized by category with install instructions for each.',
};
export default function DocsCatalogPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="App Catalog"
intro="DashCaddy ships with 77 pre-configured application templates. Every template can be deployed from the dashboard, called via the REST API, or invoked through the MCP server. This page is the index — click any app for its dedicated install guide with prerequisites, the exact API payload shape, and post-install verification steps."
>
<div className="mb-8 rounded-xl border border-brand-500/30 bg-brand-500/5 p-5">
<p className="text-sm text-surface-200">
<strong className="text-brand-400">77 apps</strong> across <strong>17 categories</strong>. The canonical source is <code>dashcaddy-api/src/docker/app-templates.js</code>; regenerate these pages with <code>scripts/regenerate-catalog-docs.sh</code> after editing.
</p>
</div>
<h2>Media</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/plex" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Plex</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Stream your personal media collection anywhere</p>
</a>
<a href="/docs/catalog/jellyfin" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Jellyfin</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Free software media system - alternative to Plex</p>
</a>
<a href="/docs/catalog/emby" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Emby</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Personal media server with apps for all devices</p>
</a>
<a href="/docs/catalog/audiobookshelf" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Audiobookshelf</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Self-hosted audiobook and podcast server</p>
</a>
<a href="/docs/catalog/navidrome" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Navidrome</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Modern music server and streamer</p>
</a>
<a href="/docs/catalog/calibre-web" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Calibre-Web</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Web-based ebook manager and reader</p>
</a>
<a href="/docs/catalog/kavita" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Kavita</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Digital reading platform for manga, comics, and books</p>
</a>
<a href="/docs/catalog/vintage-radio" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Vintage Stereo</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Glass-front console stereo that tunes curated real internet stations (SomaFM, KEXP, Radio Paradise, and more) through a beautiful analog UI</p>
</a>
<a href="/docs/catalog/komga" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Komga</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Comic and manga media server with web reader</p>
</a>
<a href="/docs/catalog/airsonic" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Airsonic Advanced</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Free web-based media streamer</p>
</a>
</div>
<h2>Media Management</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/seerr" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Seerr</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Media request and discovery manager for Plex, Jellyfin, and Emby</p>
</a>
<a href="/docs/catalog/sonarr" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Sonarr</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Smart PVR for newsgroup and bittorrent users</p>
</a>
<a href="/docs/catalog/radarr" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Radarr</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Movie collection manager for Usenet and BitTorrent</p>
</a>
<a href="/docs/catalog/tautulli" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Tautulli</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Plex media server monitoring and statistics</p>
</a>
<a href="/docs/catalog/prowlarr" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Prowlarr</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Indexer manager/proxy for *arr applications</p>
</a>
<a href="/docs/catalog/bazarr" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Bazarr</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Automatic subtitle downloader for Sonarr and Radarr</p>
</a>
<a href="/docs/catalog/lidarr" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Lidarr</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Music collection manager for Usenet and BitTorrent</p>
</a>
<a href="/docs/catalog/readarr" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Readarr</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Book and audiobook collection manager</p>
</a>
</div>
<h2>Downloads</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/qbittorrent" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">qBittorrent</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Lightweight BitTorrent client with web UI</p>
</a>
<a href="/docs/catalog/transmission" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Transmission</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Lightweight BitTorrent client</p>
</a>
<a href="/docs/catalog/sabnzbd" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">SABnzbd</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Binary newsreader for Usenet downloads</p>
</a>
<a href="/docs/catalog/jdownloader" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">JDownloader 2</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Download manager for file hosting sites</p>
</a>
<a href="/docs/catalog/nzbget" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">NZBGet</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Efficient Usenet downloader</p>
</a>
</div>
<h2>Productivity</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/nextcloud" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Nextcloud</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Self-hosted productivity platform and file sync</p>
</a>
<a href="/docs/catalog/paperless-ngx" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Paperless-ngx</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Document management system - scan, organize, and search documents</p>
</a>
<a href="/docs/catalog/bookstack" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">BookStack</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Simple wiki and documentation platform</p>
</a>
<a href="/docs/catalog/actual-budget" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Actual Budget</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Privacy-focused budgeting app with envelope budgeting</p>
</a>
<a href="/docs/catalog/mealie" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Mealie</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Recipe manager and meal planner with grocery lists</p>
</a>
<a href="/docs/catalog/outline" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Outline</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Modern team knowledge base and wiki</p>
</a>
<a href="/docs/catalog/trilium" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Trilium Notes</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Hierarchical knowledge base and note-taking app</p>
</a>
<a href="/docs/catalog/excalidraw" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Excalidraw</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Collaborative virtual whiteboard for sketching and diagrams</p>
</a>
<a href="/docs/catalog/standardnotes" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Standard Notes</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">End-to-end encrypted notes app</p>
</a>
</div>
<h2>Development</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/gitea" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Gitea</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Lightweight self-hosted Git service</p>
</a>
<a href="/docs/catalog/vscode-server" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">VS Code Server</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Visual Studio Code in your browser</p>
</a>
<a href="/docs/catalog/jenkins" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Jenkins</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Automation server for CI/CD pipelines</p>
</a>
<a href="/docs/catalog/drone" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Drone CI</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Container-native continuous delivery platform</p>
</a>
</div>
<h2>Management</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/portainer" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Portainer</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Docker container management UI</p>
</a>
<a href="/docs/catalog/watchtower" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Watchtower</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Automatic Docker container image updates</p>
</a>
</div>
<h2>Monitoring</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/uptime-kuma" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Uptime Kuma</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Self-hosted monitoring tool like Uptime Robot</p>
</a>
<a href="/docs/catalog/grafana" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Grafana</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Analytics and interactive visualization platform</p>
</a>
<a href="/docs/catalog/dozzle" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Dozzle</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Real-time Docker container log viewer</p>
</a>
<a href="/docs/catalog/speedtest" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Speedtest Tracker</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Internet speed monitoring over time</p>
</a>
</div>
<h2>Networking</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/pihole" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Pi-hole</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Network-wide ad blocker and DNS sinkhole</p>
</a>
<a href="/docs/catalog/wireguard" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">WireGuard VPN</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Fast, modern, secure VPN tunnel</p>
</a>
</div>
<h2>DNS</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/technitium" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Technitium DNS Server</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Modern DNS server with web UI for managing private zones</p>
</a>
<a href="/docs/catalog/bind9" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">BIND9 DNS Server</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Industry-standard DNS server - powerful and flexible</p>
</a>
<a href="/docs/catalog/powerdns" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">PowerDNS</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">High-performance DNS server with SQL backend</p>
</a>
<a href="/docs/catalog/coredns" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">CoreDNS</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Cloud-native DNS server - lightweight and flexible</p>
</a>
</div>
<h2>Files</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/filebrowser" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">FileBrowser</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Web-based file manager with sharing capabilities</p>
</a>
<a href="/docs/catalog/syncthing" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Syncthing</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Continuous file synchronization between devices</p>
</a>
<a href="/docs/catalog/sami-files" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Sami Files</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Multi-server SSH file manager browse, edit, upload, and exec across all your machines from one browser tab</p>
</a>
</div>
<h2>Communication</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/rocketchat" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Rocket.Chat</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Team collaboration platform like Slack</p>
</a>
<a href="/docs/catalog/matrix" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Matrix Synapse</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Decentralized, secure messaging and collaboration</p>
</a>
<a href="/docs/catalog/roundcube" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Roundcube</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Modern webmail client with rich features</p>
</a>
<a href="/docs/catalog/mailserver" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Docker Mailserver</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Full-featured email server with SMTP, IMAP, spam filtering</p>
</a>
</div>
<h2>Home Automation</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/homeassistant" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Home Assistant</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Open source home automation platform</p>
</a>
<a href="/docs/catalog/nodered" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Node-RED</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Flow-based programming for IoT and automation</p>
</a>
</div>
<h2>Database</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/postgres" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">PostgreSQL</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Advanced open-source relational database</p>
</a>
<a href="/docs/catalog/redis" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Redis</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">In-memory data structure store and cache</p>
</a>
<a href="/docs/catalog/mongodb" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">MongoDB</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Document-oriented NoSQL database</p>
</a>
<a href="/docs/catalog/adminer" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Adminer</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Lightweight database management in single PHP file</p>
</a>
</div>
<h2>Security</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/dashca" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">DashCA</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">One-click root CA certificate installer for your network</p>
</a>
<a href="/docs/catalog/vaultwarden" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Vaultwarden</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Lightweight Bitwarden-compatible password manager</p>
</a>
<a href="/docs/catalog/authentik" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Authentik</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Advanced</span>
</div>
<p className="mt-1 text-sm text-surface-300">Identity provider and single sign-on platform</p>
</a>
<a href="/docs/catalog/crowdsec" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">CrowdSec</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Collaborative intrusion prevention system</p>
</a>
</div>
<h2>Photos</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/immich" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Immich</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">Self-hosted Google Photos alternative</p>
</a>
<a href="/docs/catalog/photoprism" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">PhotoPrism</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Intermediate</span>
</div>
<p className="mt-1 text-sm text-surface-300">AI-powered photo management</p>
</a>
</div>
<h2>Utilities</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/homepage" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Homepage</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Highly customizable application dashboard</p>
</a>
<a href="/docs/catalog/stirling-pdf" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Stirling PDF</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Self-hosted PDF manipulation tool - merge, split, convert, and more</p>
</a>
<a href="/docs/catalog/weather" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Weather</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Live weather widget with temperature, conditions, and wind</p>
</a>
<a href="/docs/catalog/homarr" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Homarr</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Sleek dashboard for all your services</p>
</a>
<a href="/docs/catalog/digital-clock" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Digital Clock</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Live digital clock with time, date, and day of week</p>
</a>
<a href="/docs/catalog/it-tools" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">IT Tools</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Collection of handy developer and IT tools in one place</p>
</a>
<a href="/docs/catalog/changedetection" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Change Detection</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Monitor websites for changes</p>
</a>
<a href="/docs/catalog/whoami" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Whoami</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Simple HTTP request debugging service</p>
</a>
</div>
<h2>Gaming</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a href="/docs/catalog/minecraft" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Minecraft Server</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Minecraft Java Edition dedicated server</p>
</a>
<a href="/docs/catalog/valheim" className="block rounded-xl border border-surface-700/50 bg-surface-900/40 p-4 transition-colors hover:border-brand-500/50 hover:bg-surface-800/50">
<div className="flex items-start justify-between gap-2">
<h3 className="text-base font-semibold text-surface-50">Valheim Server</h3>
<span className="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Easy</span>
</div>
<p className="mt-1 text-sm text-surface-300">Valheim dedicated server for multiplayer Viking adventures</p>
</a>
</div>
<hr className="my-10 border-surface-700" />
<h2>Adding your own template</h2>
<p>Add an entry to <code>APP_TEMPLATES</code> in <code>dashcaddy-api/src/docker/app-templates.js</code> with the required fields (name, description, category, docker.image, ports, volumes), then re-run <code>scripts/regenerate-catalog-docs.sh</code>. The template will appear in the dashboard App Selector automatically.</p>
</DocsLayout>
<Footer />
</div>
);
}
+137
View File
@@ -0,0 +1,137 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Paperless-ngx — DashCaddy Docs',
description: 'Install and configure Paperless-ngx via DashCaddy. Document management system - scan, organize, and search documents',
};
export default function paperlessNgxDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Paperless-ngx"
intro="Document management system - scan, organize, and search documents"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/paperless-ngx/paperless-ngx:latest</code></span>
</div>
<h2>What is Paperless-ngx?</h2>
<p>Document management system - scan, organize, and search documents</p>
<p>Paperless-ngx ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Paperless-ngx, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Paperless-ngx</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>paperless</code>), host port (default: <code>8095</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "paperless-ngx",
"config": &#123;
"subdomain": "paperless",
"port": 8095
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Paperless-ngx on my home host and expose it at paperless.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/paperless-ngx/paperless-ngx:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Create admin account via: docker exec -it &lt;container&gt; python3 manage.py createsuperuser</li>
<li>Drop documents into the consume folder for automatic import</li>
<li>Configure tags and correspondents for organization</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/paperless/data:/usr/src/paperless/data</code></li>
<li><code>/opt/paperless/media:/usr/src/paperless/media</code></li>
<li><code>/opt/paperless/consume:/usr/src/paperless/consume</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PAPERLESS_URL</code></li>
<li><code>USERMAP_UID</code></li>
<li><code>USERMAP_GID</code></li>
<li><code>PAPERLESS_TIME_ZONE</code></li>
<li><code>PAPERLESS_OCR_LANGUAGE</code></li>
<li><code>PAPERLESS_SECRET_KEY</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/paperless-ngx/paperless-ngx:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Paperless-ngx:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>paperless-ngx</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install PhotoPrism — DashCaddy Docs',
description: 'Install and configure PhotoPrism via DashCaddy. AI-powered photo management',
};
export default function photoprismDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install PhotoPrism"
intro="AI-powered photo management"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Photos</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">photoprism/photoprism:latest</code></span>
</div>
<h2>What is PhotoPrism?</h2>
<p>AI-powered photo management</p>
<p>PhotoPrism ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using PhotoPrism, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>PhotoPrism</strong> from the Photos category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>gallery</code>), host port (default: <code>2342</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/v1/status</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "photoprism",
"config": &#123;
"subdomain": "gallery",
"port": 2342
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy PhotoPrism on my home host and expose it at gallery.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull photoprism/photoprism:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Change admin password</li>
<li>Import your photos</li>
<li>Run indexing for AI features</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/photoprism/storage:/photoprism/storage</code></li>
<li><code>/opt/photoprism/originals:/photoprism/originals</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PHOTOPRISM_ADMIN_PASSWORD</code></li>
<li><code>PHOTOPRISM_SITE_URL</code></li>
<li><code>PHOTOPRISM_DATABASE_DRIVER</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull photoprism/photoprism:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with PhotoPrism:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/v1/status</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>photoprism</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+132
View File
@@ -0,0 +1,132 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Pi-hole — DashCaddy Docs',
description: 'Install and configure Pi-hole via DashCaddy. Network-wide ad blocker and DNS sinkhole',
};
export default function piholeDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Pi-hole"
intro="Network-wide ad blocker and DNS sinkhole"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Networking</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">pihole/pihole:latest</code></span>
</div>
<h2>What is Pi-hole?</h2>
<p>Network-wide ad blocker and DNS sinkhole</p>
<p>Pi-hole ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Pi-hole, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Pi-hole</strong> from the Networking category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>pihole</code>), host port (default: <code>80</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/admin/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "pihole",
"config": &#123;
"subdomain": "pihole",
"port": 80
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Pi-hole on my home host and expose it at pihole.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull pihole/pihole:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Open the deployed URL (returned in the response as <code>url</code>, or visible in the dashboard).</li>
<li>Complete the upstream Pi-hole setup wizard (admin account, library paths, EULA).</li>
<li>Restore from a backup if one exists: <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with the backup ID from <code>GET /api/v1/backups/history</code>.</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/pihole/etc:/etc/pihole</code></li>
<li><code>/opt/pihole/dnsmasq:/etc/dnsmasq.d</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>WEBPASSWORD</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull pihole/pihole:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Pi-hole:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/admin/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>pihole</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+154
View File
@@ -0,0 +1,154 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Plex — DashCaddy Docs',
description: 'Install and configure Plex via DashCaddy. Stream your personal media collection anywhere',
};
export default function plexDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Plex"
intro="Stream your personal media collection anywhere"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">plexinc/pms-docker:latest</code></span>
</div>
<h2>What is Plex?</h2>
<p>Stream your personal media collection anywhere</p>
<p>Plex ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Plex, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>A host path containing your media. Default suggestion: <code>/media</code>. The deploy form / API payload <code>config.mediaPath</code> must be readable by the container UID (usually <code>1000</code>).</li>
<li>A <strong>Plex Claim Token</strong> get one from <a href="https://plex.tv/claim" className="text-brand-400 underline">https://plex.tv/claim</a> right before you click Deploy.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Plex</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>plex</code>), host port (default: <code>32400</code>), and the media library path, and the claim token.</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/web/index.html</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "plex",
"config": &#123;
"subdomain": "plex",
"port": 32400,
"mediaPath": "/media",
"plexClaimToken": "&lt;get fresh token from https://plex.tv/claim&gt;"
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Plex on my home host and expose it at plex.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull plexinc/pms-docker:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Get your claim token from https://plex.tv/claim</li>
<li>Add your media libraries in the web interface</li>
<li>Configure remote access settings</li>
</ol>
<h2>Media library path notes</h2>
<p>The media mount path you pass as <code>mediaPath</code> in the deploy payload is mounted as <code>/data</code> inside the container. Bind a host directory containing your media library (movies, TV shows, music, etc.).</p>
<ul>
<li><strong>UID/GID:</strong> Plex runs as a non-root user. If you see permission errors in the dashboard Logs tab, run <code>chown -R 1000:1000 /media</code> on the host.</li>
<li><strong>Multi-library:</strong> bind the parent folder and let Plex discover subfolders.</li>
</ul>
<h2>Plex Claim Token</h2>
<p>Get from https://plex.tv/claim - expires in 4 minutes!</p>
<p>Pass it as <code>plexClaimToken</code> inside the <code>config</code> object of the deploy payload (NOT as an environment variable).</p>
<blockquote className="border-l-4 border-yellow-500/50 bg-yellow-500/5 p-4 rounded-r-lg">
<p className="text-yellow-200"><strong>Heads up:</strong> Plex Claim Token expires within minutes. Get a fresh one from
<a href="https://plex.tv/claim" className="underline"> https://plex.tv/claim</a>
right before you click <em>Deploy</em>.</p>
</blockquote>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/plex/config:/config</code></li>
<li><code>/opt/plex/transcode:/transcode</code></li>
<li><code>MEDIA_PATH:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PLEX_CLAIM</code></li>
<li><code>ADVERTISE_IP</code></li>
<li><code>PLEX_UID</code></li>
<li><code>PLEX_GID</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull plexinc/pms-docker:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Plex:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>Library shows empty:</strong> confirm <code>mediaPath</code> is readable by the container UID and that the directory contains the file extensions Plex indexes.</li>
<li><strong>Account linking fails:</strong> your claim token probably expired. Get a new one and redeploy.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/web/index.html</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>plex</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Portainer — DashCaddy Docs',
description: 'Install and configure Portainer via DashCaddy. Docker container management UI',
};
export default function portainerDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Portainer"
intro="Docker container management UI"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">portainer/portainer-ce:latest</code></span>
</div>
<h2>What is Portainer?</h2>
<p>Docker container management UI</p>
<p>Portainer ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Portainer, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Portainer</strong> from the Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>portainer</code>), host port (default: <code>9000</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/status</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "portainer",
"config": &#123;
"subdomain": "portainer",
"port": 9000
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Portainer on my home host and expose it at portainer.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull portainer/portainer-ce:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Open the deployed URL (returned in the response as <code>url</code>, or visible in the dashboard).</li>
<li>Complete the upstream Portainer setup wizard (admin account, library paths, EULA).</li>
<li>Restore from a backup if one exists: <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with the backup ID from <code>GET /api/v1/backups/history</code>.</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/var/run/docker.sock:/var/run/docker.sock</code></li>
<li><code>/opt/portainer/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull portainer/portainer-ce:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Portainer:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/status</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>portainer</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+132
View File
@@ -0,0 +1,132 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install PostgreSQL — DashCaddy Docs',
description: 'Install and configure PostgreSQL via DashCaddy. Advanced open-source relational database',
};
export default function postgresDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install PostgreSQL"
intro="Advanced open-source relational database"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Database</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">postgres:16-alpine</code></span>
</div>
<h2>What is PostgreSQL?</h2>
<p>Advanced open-source relational database</p>
<p>PostgreSQL ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using PostgreSQL, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>PostgreSQL</strong> from the Database category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>postgres</code>), host port (default: <code>5432</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "postgres",
"config": &#123;
"subdomain": "postgres",
"port": 5432
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy PostgreSQL on my home host and expose it at postgres.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull postgres:16-alpine</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Change default password immediately</li>
<li>Create databases and users as needed</li>
<li>Configure pg_hba.conf for remote access</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/postgres/data:/var/lib/postgresql/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>POSTGRES_USER</code></li>
<li><code>POSTGRES_PASSWORD</code></li>
<li><code>POSTGRES_DB</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull postgres:16-alpine</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with PostgreSQL:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>postgres</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+137
View File
@@ -0,0 +1,137 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install PowerDNS — DashCaddy Docs',
description: 'Install and configure PowerDNS via DashCaddy. High-performance DNS server with SQL backend',
};
export default function powerdnsDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install PowerDNS"
intro="High-performance DNS server with SQL backend"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: DNS</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">pschiffe/pdns-mysql:latest</code></span>
</div>
<h2>What is PowerDNS?</h2>
<p>High-performance DNS server with SQL backend</p>
<p>PowerDNS ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using PowerDNS, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>PowerDNS</strong> from the DNS category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>dns3</code>), host port (default: <code>8081</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/v1/servers</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "powerdns",
"config": &#123;
"subdomain": "dns3",
"port": 8081
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy PowerDNS on my home host and expose it at dns3.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull pschiffe/pdns-mysql:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Access API at https://dns3.sami:8081</li>
<li>Use API key for authentication</li>
<li>Create zone via API or PowerDNS Admin</li>
<li>Add records for your .sami domain</li>
<li>Configure devices to use DNS server</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/powerdns/data:/var/lib/mysql</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PDNS_api</code></li>
<li><code>PDNS_api_key</code></li>
<li><code>PDNS_webserver</code></li>
<li><code>PDNS_webserver_address</code></li>
<li><code>PDNS_webserver_allow_from</code></li>
<li><code>MYSQL_ROOT_PASSWORD</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull pschiffe/pdns-mysql:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with PowerDNS:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/v1/servers</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>powerdns</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+132
View File
@@ -0,0 +1,132 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Prowlarr — DashCaddy Docs',
description: 'Install and configure Prowlarr via DashCaddy. Indexer manager/proxy for *arr applications',
};
export default function prowlarrDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Prowlarr"
intro="Indexer manager/proxy for *arr applications"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/prowlarr:latest</code></span>
</div>
<h2>What is Prowlarr?</h2>
<p>Indexer manager/proxy for *arr applications</p>
<p>Prowlarr ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Prowlarr, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Prowlarr</strong> from the Media Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>prowlarr</code>), host port (default: <code>9696</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/v1/system/status</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "prowlarr",
"config": &#123;
"subdomain": "prowlarr",
"port": 9696
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Prowlarr on my home host and expose it at prowlarr.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/prowlarr:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Open the deployed URL (returned in the response as <code>url</code>, or visible in the dashboard).</li>
<li>Complete the upstream Prowlarr setup wizard (admin account, library paths, EULA).</li>
<li>Restore from a backup if one exists: <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with the backup ID from <code>GET /api/v1/backups/history</code>.</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/prowlarr/config:/config</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/prowlarr:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Prowlarr:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/v1/system/status</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>prowlarr</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+134
View File
@@ -0,0 +1,134 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install qBittorrent — DashCaddy Docs',
description: 'Install and configure qBittorrent via DashCaddy. Lightweight BitTorrent client with web UI',
};
export default function qbittorrentDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install qBittorrent"
intro="Lightweight BitTorrent client with web UI"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Downloads</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/qbittorrent:latest</code></span>
</div>
<h2>What is qBittorrent?</h2>
<p>Lightweight BitTorrent client with web UI</p>
<p>qBittorrent ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using qBittorrent, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>qBittorrent</strong> from the Downloads category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>torrent</code>), host port (default: <code>8080</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "qbittorrent",
"config": &#123;
"subdomain": "torrent",
"port": 8080
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy qBittorrent on my home host and expose it at torrent.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/qbittorrent:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Default login: admin/adminadmin</li>
<li>Change default password immediately</li>
<li>Configure download paths</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/qbittorrent/config:/config</code></li>
<li><code>/downloads:/downloads</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
<li><code>WEBUI_PORT</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/qbittorrent:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with qBittorrent:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>qbittorrent</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+134
View File
@@ -0,0 +1,134 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Radarr — DashCaddy Docs',
description: 'Install and configure Radarr via DashCaddy. Movie collection manager for Usenet and BitTorrent',
};
export default function radarrDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Radarr"
intro="Movie collection manager for Usenet and BitTorrent"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/radarr:latest</code></span>
</div>
<h2>What is Radarr?</h2>
<p>Movie collection manager for Usenet and BitTorrent</p>
<p>Radarr ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Radarr, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Radarr</strong> from the Media Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>radarr</code>), host port (default: <code>7878</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/v3/system/status</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "radarr",
"config": &#123;
"subdomain": "radarr",
"port": 7878
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Radarr on my home host and expose it at radarr.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/radarr:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Open the deployed URL (returned in the response as <code>url</code>, or visible in the dashboard).</li>
<li>Complete the upstream Radarr setup wizard (admin account, library paths, EULA).</li>
<li>Restore from a backup if one exists: <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with the backup ID from <code>GET /api/v1/backups/history</code>.</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/radarr/config:/config</code></li>
<li><code>/downloads:/downloads</code></li>
<li><code>/movies:/movies</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/radarr:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Radarr:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/v3/system/status</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>radarr</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+134
View File
@@ -0,0 +1,134 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Readarr — DashCaddy Docs',
description: 'Install and configure Readarr via DashCaddy. Book and audiobook collection manager',
};
export default function readarrDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Readarr"
intro="Book and audiobook collection manager"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/readarr:develop</code></span>
</div>
<h2>What is Readarr?</h2>
<p>Book and audiobook collection manager</p>
<p>Readarr ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Readarr, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Readarr</strong> from the Media Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>readarr</code>), host port (default: <code>8787</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/v1/system/status</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "readarr",
"config": &#123;
"subdomain": "readarr",
"port": 8787
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Readarr on my home host and expose it at readarr.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/readarr:develop</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure download clients</li>
<li>Add indexers for books</li>
<li>Set up root folders</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/readarr/config:/config</code></li>
<li><code>/downloads:/downloads</code></li>
<li><code>/books:/books</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/readarr:develop</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Readarr:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/v1/system/status</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>readarr</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+128
View File
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Redis — DashCaddy Docs',
description: 'Install and configure Redis via DashCaddy. In-memory data structure store and cache',
};
export default function redisDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Redis"
intro="In-memory data structure store and cache"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Database</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">redis:alpine</code></span>
</div>
<h2>What is Redis?</h2>
<p>In-memory data structure store and cache</p>
<p>Redis ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Redis, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Redis</strong> from the Database category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>redis</code>), host port (default: <code>6379</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "redis",
"config": &#123;
"subdomain": "redis",
"port": 6379
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Redis on my home host and expose it at redis.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull redis:alpine</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure redis.conf for persistence</li>
<li>Set up authentication if needed</li>
<li>Configure maxmemory policy</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/redis/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull redis:alpine</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Redis:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>redis</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+131
View File
@@ -0,0 +1,131 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Rocket.Chat — DashCaddy Docs',
description: 'Install and configure Rocket.Chat via DashCaddy. Team collaboration platform like Slack',
};
export default function rocketchatDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Rocket.Chat"
intro="Team collaboration platform like Slack"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Communication</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">rocket.chat:latest</code></span>
</div>
<h2>What is Rocket.Chat?</h2>
<p>Team collaboration platform like Slack</p>
<p>Rocket.Chat ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Rocket.Chat, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Rocket.Chat</strong> from the Communication category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>chat</code>), host port (default: <code>3004</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/info</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "rocketchat",
"config": &#123;
"subdomain": "chat",
"port": 3004
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Rocket.Chat on my home host and expose it at chat.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull rocket.chat:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Requires MongoDB - deploy mongo container first</li>
<li>Complete admin setup wizard</li>
<li>Configure OAuth and integrations</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/rocketchat/uploads:/app/uploads</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>ROOT_URL</code></li>
<li><code>MONGO_URL</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull rocket.chat:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Rocket.Chat:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/info</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>rocketchat</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+132
View File
@@ -0,0 +1,132 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Roundcube — DashCaddy Docs',
description: 'Install and configure Roundcube via DashCaddy. Modern webmail client with rich features',
};
export default function roundcubeDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Roundcube"
intro="Modern webmail client with rich features"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Communication</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">roundcube/roundcubemail:latest</code></span>
</div>
<h2>What is Roundcube?</h2>
<p>Modern webmail client with rich features</p>
<p>Roundcube ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Roundcube, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Roundcube</strong> from the Communication category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>webmail</code>), host port (default: <code>8086</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "roundcube",
"config": &#123;
"subdomain": "webmail",
"port": 8086
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Roundcube on my home host and expose it at webmail.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull roundcube/roundcubemail:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure IMAP/SMTP server settings</li>
<li>Set up database connection</li>
<li>Customize appearance and plugins</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/roundcube/config:/var/roundcube/config</code></li>
<li><code>/opt/roundcube/db:/var/roundcube/db</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>ROUNDCUBEMAIL_DEFAULT_HOST</code></li>
<li><code>ROUNDCUBEMAIL_SMTP_SERVER</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull roundcube/roundcubemail:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Roundcube:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>roundcube</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install SABnzbd — DashCaddy Docs',
description: 'Install and configure SABnzbd via DashCaddy. Binary newsreader for Usenet downloads',
};
export default function sabnzbdDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install SABnzbd"
intro="Binary newsreader for Usenet downloads"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Downloads</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/sabnzbd:latest</code></span>
</div>
<h2>What is SABnzbd?</h2>
<p>Binary newsreader for Usenet downloads</p>
<p>SABnzbd ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using SABnzbd, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>SABnzbd</strong> from the Downloads category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>sabnzbd</code>), host port (default: <code>8092</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "sabnzbd",
"config": &#123;
"subdomain": "sabnzbd",
"port": 8092
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy SABnzbd on my home host and expose it at sabnzbd.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/sabnzbd:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure Usenet server credentials</li>
<li>Set up download categories</li>
<li>Configure post-processing scripts</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/sabnzbd/config:/config</code></li>
<li><code>/downloads:/downloads</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/sabnzbd:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with SABnzbd:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>sabnzbd</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Sami Files — DashCaddy Docs',
description: 'Install and configure Sami Files via DashCaddy. Multi-server SSH file manager — browse, edit, upload, and exec across all your machines from one browser tab',
};
export default function samiFilesDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Sami Files"
intro="Multi-server SSH file manager — browse, edit, upload, and exec across all your machines from one browser tab"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Files</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">N/A</code></span>
</div>
<h2>What is Sami Files?</h2>
<p>Multi-server SSH file manager browse, edit, upload, and exec across all your machines from one browser tab</p>
<p>Sami Files ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Sami Files, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Sami Files</strong> from the Files category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>files</code>), host port (default: <code>8765</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>http://127.0.0.1:8765/api/health</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "sami-files",
"config": &#123;
"subdomain": "files",
"port": 8765
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Sami Files on my home host and expose it at files.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull N/A</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Clone the repo: git clone http://100.81.59.99:3030/sami7777/sami-files.git /opt/sami-files</li>
<li>Create venv and install deps: /usr/local/lib/hermes-agent/venv/bin/pip install fastapi uvicorn asyncssh pyyaml python-multipart</li>
<li>Copy deploy/sami-files.service to /etc/systemd/system/ and `systemctl daemon-reload`</li>
<li>Enable + start: systemctl enable --now sami-files.service</li>
<li>Edit /opt/sami-files/config/servers.yaml to add your SSH targets</li>
<li>Add the Caddy snippet (above) to your Caddyfile and reload Caddy</li>
<li>Mount the log dir into DashCaddy: add `-v /opt/sami-files/logs:/opt/sami-files/logs:ro` to start.sh, then recreate the container</li>
<li>Browse to https://files.sami — log in via DashCaddy SSO</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull N/A</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Sami Files:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>http://127.0.0.1:8765/api/health</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>sami-files</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Seerr — DashCaddy Docs',
description: 'Install and configure Seerr via DashCaddy. Media request and discovery manager for Plex, Jellyfin, and Emby',
};
export default function seerrDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Seerr"
intro="Media request and discovery manager for Plex, Jellyfin, and Emby"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/seerr-team/seerr:latest</code></span>
</div>
<h2>What is Seerr?</h2>
<p>Media request and discovery manager for Plex, Jellyfin, and Emby</p>
<p>Seerr ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Seerr, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Seerr</strong> from the Media Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>requests</code>), host port (default: <code>5055</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/v1/status</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "seerr",
"config": &#123;
"subdomain": "requests",
"port": 5055
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Seerr on my home host and expose it at requests.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/seerr-team/seerr:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Connect to Plex, Jellyfin, or Emby server</li>
<li>Link Sonarr and Radarr</li>
<li>Configure user permissions</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/seerr/config:/app/config</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/seerr-team/seerr:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Seerr:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/v1/status</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>seerr</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+134
View File
@@ -0,0 +1,134 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Sonarr — DashCaddy Docs',
description: 'Install and configure Sonarr via DashCaddy. Smart PVR for newsgroup and bittorrent users',
};
export default function sonarrDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Sonarr"
intro="Smart PVR for newsgroup and bittorrent users"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/sonarr:latest</code></span>
</div>
<h2>What is Sonarr?</h2>
<p>Smart PVR for newsgroup and bittorrent users</p>
<p>Sonarr ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Sonarr, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Sonarr</strong> from the Media Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>sonarr</code>), host port (default: <code>8989</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/api/v3/system/status</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> — there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "sonarr",
"config": &#123;
"subdomain": "sonarr",
"port": 8989
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> — host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Sonarr on my home host and expose it at sonarr.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/sonarr:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure download clients (qBittorrent, etc.)</li>
<li>Add indexers for content discovery</li>
<li>Set up root folders for TV shows</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/sonarr/config:/config</code></li>
<li><code>/downloads:/downloads</code></li>
<li><code>/tv:/tv</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/sonarr:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Sonarr:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/api/v3/system/status</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>sonarr</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+132
View File
@@ -0,0 +1,132 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Speedtest Tracker — DashCaddy Docs',
description: 'Install and configure Speedtest Tracker via DashCaddy. Internet speed monitoring over time',
};
export default function speedtestDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Speedtest Tracker"
intro="Internet speed monitoring over time"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Monitoring</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">ghcr.io/alexjustesen/speedtest-tracker:latest</code></span>
</div>
<h2>What is Speedtest Tracker?</h2>
<p>Internet speed monitoring over time</p>
<p>Speedtest Tracker ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Speedtest Tracker, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Speedtest Tracker</strong> from the Monitoring category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>speedtest</code>), host port (default: <code>8093</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "speedtest",
"config": &#123;
"subdomain": "speedtest",
"port": 8093
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Speedtest Tracker on my home host and expose it at speedtest.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull ghcr.io/alexjustesen/speedtest-tracker:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure test schedule</li>
<li>View historical data</li>
<li>Set up notifications for slow speeds</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/speedtest/config:/config</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>DB_CONNECTION</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull ghcr.io/alexjustesen/speedtest-tracker:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Speedtest Tracker:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>speedtest</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Standard Notes — DashCaddy Docs',
description: 'Install and configure Standard Notes via DashCaddy. End-to-end encrypted notes app',
};
export default function standardnotesDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Standard Notes"
intro="End-to-end encrypted notes app"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#68a4ff22', color: '#68a4ff' }}>Difficulty: Intermediate</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">standardnotes/server:latest</code></span>
</div>
<h2>What is Standard Notes?</h2>
<p>End-to-end encrypted notes app</p>
<p>Standard Notes ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Standard Notes, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Standard Notes</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>notes</code>), host port (default: <code>3007</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "standardnotes",
"config": &#123;
"subdomain": "notes",
"port": 3007
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Standard Notes on my home host and expose it at notes.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull standardnotes/server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure environment variables</li>
<li>Set up database connection</li>
<li>Install Standard Notes apps</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/standardnotes/data:/var/lib/server</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>RAILS_ENV</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull standardnotes/server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Standard Notes:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>standardnotes</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+131
View File
@@ -0,0 +1,131 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Stirling PDF — DashCaddy Docs',
description: 'Install and configure Stirling PDF via DashCaddy. Self-hosted PDF manipulation tool - merge, split, convert, and more',
};
export default function stirlingPdfDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Stirling PDF"
intro="Self-hosted PDF manipulation tool - merge, split, convert, and more"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Utilities</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">frooodle/s-pdf:latest</code></span>
</div>
<h2>What is Stirling PDF?</h2>
<p>Self-hosted PDF manipulation tool - merge, split, convert, and more</p>
<p>Stirling PDF ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Stirling PDF, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Stirling PDF</strong> from the Utilities category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>pdf</code>), host port (default: <code>8084</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "stirling-pdf",
"config": &#123;
"subdomain": "pdf",
"port": 8084
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Stirling PDF on my home host and expose it at pdf.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull frooodle/s-pdf:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Access the web interface to start manipulating PDFs</li>
<li>Supports merge, split, rotate, convert, compress, and more</li>
<li>Optional OCR support via Tesseract</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/stirling-pdf/data:/usr/share/tessdata</code></li>
<li><code>/opt/stirling-pdf/config:/configs</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>DOCKER_ENABLE_SECURITY</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull frooodle/s-pdf:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Stirling PDF:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>stirling-pdf</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Syncthing — DashCaddy Docs',
description: 'Install and configure Syncthing via DashCaddy. Continuous file synchronization between devices',
};
export default function syncthingDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Syncthing"
intro="Continuous file synchronization between devices"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Files</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/syncthing:latest</code></span>
</div>
<h2>What is Syncthing?</h2>
<p>Continuous file synchronization between devices</p>
<p>Syncthing ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Syncthing, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Syncthing</strong> from the Files category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>sync</code>), host port (default: <code>8384</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "syncthing",
"config": &#123;
"subdomain": "sync",
"port": 8384
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Syncthing on my home host and expose it at sync.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/syncthing:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Add devices using their Device IDs</li>
<li>Configure shared folders</li>
<li>Set up folder synchronization</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/syncthing/config:/config</code></li>
<li><code>/opt/syncthing/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/syncthing:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Syncthing:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>syncthing</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+132
View File
@@ -0,0 +1,132 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Tautulli — DashCaddy Docs',
description: 'Install and configure Tautulli via DashCaddy. Plex media server monitoring and statistics',
};
export default function tautulliDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Tautulli"
intro="Plex media server monitoring and statistics"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/tautulli:latest</code></span>
</div>
<h2>What is Tautulli?</h2>
<p>Plex media server monitoring and statistics</p>
<p>Tautulli ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Tautulli, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Tautulli</strong> from the Media Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>tautulli</code>), host port (default: <code>8181</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "tautulli",
"config": &#123;
"subdomain": "tautulli",
"port": 8181
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Tautulli on my home host and expose it at tautulli.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/tautulli:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Connect to Plex server</li>
<li>Configure notifications</li>
<li>Set up newsletters</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/tautulli/config:/config</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/tautulli:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Tautulli:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>tautulli</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Technitium DNS Server — DashCaddy Docs',
description: 'Install and configure Technitium DNS Server via DashCaddy. Modern DNS server with web UI for managing private zones',
};
export default function technitiumDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Technitium DNS Server"
intro="Modern DNS server with web UI for managing private zones"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: DNS</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">technitium/dns-server:latest</code></span>
</div>
<h2>What is Technitium DNS Server?</h2>
<p>Modern DNS server with web UI for managing private zones</p>
<p>Technitium DNS Server ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Technitium DNS Server, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Technitium DNS Server</strong> from the DNS category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>dns1</code>), host port (default: <code>5380</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "technitium",
"config": &#123;
"subdomain": "dns1",
"port": 5380
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Technitium DNS Server on my home host and expose it at dns1.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull technitium/dns-server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Access web interface at https://dns1.sami</li>
<li>Login with admin credentials</li>
<li>Create a primary zone for 'sami' domain</li>
<li>Add A records for your services (e.g., plex.sami -&gt; 192.168.1.100)</li>
<li>Configure your devices to use this DNS server</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/technitium/config:/etc/dns</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>DNS_SERVER_DOMAIN</code></li>
<li><code>DNS_SERVER_ADMIN_PASSWORD</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull technitium/dns-server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Technitium DNS Server:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>technitium</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Transmission — DashCaddy Docs',
description: 'Install and configure Transmission via DashCaddy. Lightweight BitTorrent client',
};
export default function transmissionDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Transmission"
intro="Lightweight BitTorrent client"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Downloads</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/transmission:latest</code></span>
</div>
<h2>What is Transmission?</h2>
<p>Lightweight BitTorrent client</p>
<p>Transmission ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Transmission, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Transmission</strong> from the Downloads category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>transmission</code>), host port (default: <code>9092</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/transmission/web/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "transmission",
"config": &#123;
"subdomain": "transmission",
"port": 9092
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Transmission on my home host and expose it at transmission.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/transmission:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure download paths</li>
<li>Set bandwidth limits</li>
<li>Configure blocklists if needed</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/transmission/config:/config</code></li>
<li><code>/downloads:/downloads</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/transmission:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Transmission:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/transmission/web/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>transmission</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+128
View File
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Trilium Notes — DashCaddy Docs',
description: 'Install and configure Trilium Notes via DashCaddy. Hierarchical knowledge base and note-taking app',
};
export default function triliumDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Trilium Notes"
intro="Hierarchical knowledge base and note-taking app"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Productivity</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">zadam/trilium:latest</code></span>
</div>
<h2>What is Trilium Notes?</h2>
<p>Hierarchical knowledge base and note-taking app</p>
<p>Trilium Notes ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Trilium Notes, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Trilium Notes</strong> from the Productivity category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>notes</code>), host port (default: <code>8085</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "trilium",
"config": &#123;
"subdomain": "notes",
"port": 8085
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Trilium Notes on my home host and expose it at notes.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull zadam/trilium:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Set your password on first access</li>
<li>Organize notes in a tree hierarchy</li>
<li>Supports rich text, code blocks, math equations, and diagrams</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/trilium/data:/home/node/trilium-data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull zadam/trilium:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Trilium Notes:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>trilium</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+128
View File
@@ -0,0 +1,128 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Uptime Kuma — DashCaddy Docs',
description: 'Install and configure Uptime Kuma via DashCaddy. Self-hosted monitoring tool like Uptime Robot',
};
export default function uptimeKumaDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Uptime Kuma"
intro="Self-hosted monitoring tool like Uptime Robot"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Monitoring</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">louislam/uptime-kuma:latest</code></span>
</div>
<h2>What is Uptime Kuma?</h2>
<p>Self-hosted monitoring tool like Uptime Robot</p>
<p>Uptime Kuma ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Uptime Kuma, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Uptime Kuma</strong> from the Monitoring category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>uptime</code>), host port (default: <code>3002</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "uptime-kuma",
"config": &#123;
"subdomain": "uptime",
"port": 3002
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Uptime Kuma on my home host and expose it at uptime.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull louislam/uptime-kuma:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Open the deployed URL (returned in the response as <code>url</code>, or visible in the dashboard).</li>
<li>Complete the upstream Uptime Kuma setup wizard (admin account, library paths, EULA).</li>
<li>Restore from a backup if one exists: <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with the backup ID from <code>GET /api/v1/backups/history</code>.</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/uptime-kuma:/app/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull louislam/uptime-kuma:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Uptime Kuma:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>uptime-kuma</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+135
View File
@@ -0,0 +1,135 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Valheim Server — DashCaddy Docs',
description: 'Install and configure Valheim Server via DashCaddy. Valheim dedicated server for multiplayer Viking adventures',
};
export default function valheimDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Valheim Server"
intro="Valheim dedicated server for multiplayer Viking adventures"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Gaming</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">lloesche/valheim-server:latest</code></span>
</div>
<h2>What is Valheim Server?</h2>
<p>Valheim dedicated server for multiplayer Viking adventures</p>
<p>Valheim Server ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Valheim Server, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Valheim Server</strong> from the Gaming category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>valheim</code>), host port (default: <code>2456</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>tcp://localhost:2456</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "valheim",
"config": &#123;
"subdomain": "valheim",
"port": 2456
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Valheim Server on my home host and expose it at valheim.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull lloesche/valheim-server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Connect via Steam: Add Server &gt; IP:2456</li>
<li>Default server password is auto-generated (check environment variables)</li>
<li>World data is persisted in the data volume</li>
<li>Requires at least 4GB RAM for smooth operation</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/valheim/config:/config</code></li>
<li><code>/opt/valheim/data:/opt/valheim</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>SERVER_NAME</code></li>
<li><code>WORLD_NAME</code></li>
<li><code>SERVER_PASS</code></li>
<li><code>SERVER_PUBLIC</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull lloesche/valheim-server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Valheim Server:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>tcp://localhost:2456</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>valheim</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+131
View File
@@ -0,0 +1,131 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Vaultwarden — DashCaddy Docs',
description: 'Install and configure Vaultwarden via DashCaddy. Lightweight Bitwarden-compatible password manager',
};
export default function vaultwardenDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Vaultwarden"
intro="Lightweight Bitwarden-compatible password manager"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Security</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">vaultwarden/server:latest</code></span>
</div>
<h2>What is Vaultwarden?</h2>
<p>Lightweight Bitwarden-compatible password manager</p>
<p>Vaultwarden ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Vaultwarden, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Vaultwarden</strong> from the Security category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>vault</code>), host port (default: <code>8088</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "vaultwarden",
"config": &#123;
"subdomain": "vault",
"port": 8088
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Vaultwarden on my home host and expose it at vault.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull vaultwarden/server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Change admin token immediately</li>
<li>Create your account</li>
<li>Install browser extensions and mobile apps</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/vaultwarden/data:/data</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>DOMAIN</code></li>
<li><code>ADMIN_TOKEN</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull vaultwarden/server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Vaultwarden:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>vaultwarden</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Vintage Stereo — DashCaddy Docs',
description: 'Install and configure Vintage Stereo via DashCaddy. Glass-front console stereo that tunes curated real internet stations (SomaFM, KEXP, Radio Paradise, and more) through a beautiful analog UI',
};
export default function vintageRadioDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Vintage Stereo"
intro="Glass-front console stereo that tunes curated real internet stations (SomaFM, KEXP, Radio Paradise, and more) through a beautiful analog UI"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Media</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">nginx:alpine</code></span>
</div>
<h2>What is Vintage Stereo?</h2>
<p>Glass-front console stereo that tunes curated real internet stations (SomaFM, KEXP, Radio Paradise, and more) through a beautiful analog UI</p>
<p>Vintage Stereo ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Vintage Stereo, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Vintage Stereo</strong> from the Media category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>radio</code>), host port (default: <code>8090</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "vintage-radio",
"config": &#123;
"subdomain": "radio",
"port": 8090
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Vintage Stereo on my home host and expose it at radio.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull nginx:alpine</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Run `bash /usr/local/bin/vintage-radio-install.sh` once before starting the container copies the bundled web assets (index.html, radio.css, radio.js, stations.json) from the DashCaddy repo (dashcaddy-api/static-sites/vintage-radio/web) into /opt/vintage-radio/web</li>
<li>Open radio.sami (or your configured subdomain)</li>
<li>Press the PWR knob, drag the dial or click a station card</li>
<li>Cycle the MODE knob to filter by genre (ALL / AMBIENT / ROCK / MIXED)</li>
<li>To add stations, edit /opt/vintage-radio/web/stations.json on the host and restart the container</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/vintage-radio/web:/usr/share/nginx/html:ro</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull nginx:alpine</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Vintage Stereo:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>vintage-radio</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+131
View File
@@ -0,0 +1,131 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install VS Code Server — DashCaddy Docs',
description: 'Install and configure VS Code Server via DashCaddy. Visual Studio Code in your browser',
};
export default function vscodeServerDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install VS Code Server"
intro="Visual Studio Code in your browser"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Development</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">codercom/code-server:latest</code></span>
</div>
<h2>What is VS Code Server?</h2>
<p>Visual Studio Code in your browser</p>
<p>VS Code Server ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using VS Code Server, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>VS Code Server</strong> from the Development category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>code</code>), host port (default: <code>8443</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/healthz</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "vscode-server",
"config": &#123;
"subdomain": "code",
"port": 8443
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy VS Code Server on my home host and expose it at code.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull codercom/code-server:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Open the deployed URL (returned in the response as <code>url</code>, or visible in the dashboard).</li>
<li>Complete the upstream VS Code Server setup wizard (admin account, library paths, EULA).</li>
<li>Restore from a backup if one exists: <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with the backup ID from <code>GET /api/v1/backups/history</code>.</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/vscode/config:/home/coder/.config</code></li>
<li><code>/opt/vscode/projects:/home/coder/projects</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PASSWORD</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull codercom/code-server:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with VS Code Server:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/healthz</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>vscode-server</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+133
View File
@@ -0,0 +1,133 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Watchtower — DashCaddy Docs',
description: 'Install and configure Watchtower via DashCaddy. Automatic Docker container image updates',
};
export default function watchtowerDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Watchtower"
intro="Automatic Docker container image updates"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Management</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">containrrr/watchtower:latest</code></span>
</div>
<h2>What is Watchtower?</h2>
<p>Automatic Docker container image updates</p>
<p>Watchtower ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Watchtower, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Watchtower</strong> from the Management category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>watchtower</code>), host port (default: <code>8089</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/v1/update</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "watchtower",
"config": &#123;
"subdomain": "watchtower",
"port": 8089
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Watchtower on my home host and expose it at watchtower.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull containrrr/watchtower:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Watchtower checks for image updates daily at 4 AM by default</li>
<li>Customize schedule via WATCHTOWER_SCHEDULE (cron format)</li>
<li>Add labels to exclude specific containers from updates</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/var/run/docker.sock:/var/run/docker.sock</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>WATCHTOWER_CLEANUP</code></li>
<li><code>WATCHTOWER_SCHEDULE</code></li>
<li><code>WATCHTOWER_HTTP_API_METRICS</code></li>
<li><code>WATCHTOWER_HTTP_API_TOKEN</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull containrrr/watchtower:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Watchtower:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/v1/update</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>watchtower</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+127
View File
@@ -0,0 +1,127 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Weather — DashCaddy Docs',
description: 'Install and configure Weather via DashCaddy. Live weather widget with temperature, conditions, and wind',
};
export default function weatherDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Weather"
intro="Live weather widget with temperature, conditions, and wind"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Utilities</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">N/A</code></span>
</div>
<h2>What is Weather?</h2>
<p>Live weather widget with temperature, conditions, and wind</p>
<p>Weather ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Weather, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Weather</strong> from the Utilities category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>weather</code>), host port (default: <code>32400</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/healthz</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "weather",
"config": &#123;
"subdomain": "weather",
"port": 32400
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Weather on my home host and expose it at weather.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull N/A</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Click the gear icon on the widget to set your ZIP code</li>
<li>Weather appears in the top bar next to the logo</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull N/A</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Weather:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/healthz</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>weather</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+127
View File
@@ -0,0 +1,127 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install Whoami — DashCaddy Docs',
description: 'Install and configure Whoami via DashCaddy. Simple HTTP request debugging service',
};
export default function whoamiDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install Whoami"
intro="Simple HTTP request debugging service"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Utilities</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#7cf2c022', color: '#7cf2c0' }}>Difficulty: Easy</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">traefik/whoami:latest</code></span>
</div>
<h2>What is Whoami?</h2>
<p>Simple HTTP request debugging service</p>
<p>Whoami ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using Whoami, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>Whoami</strong> from the Utilities category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>whoami</code>), host port (default: <code>8094</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "whoami",
"config": &#123;
"subdomain": "whoami",
"port": 8094
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy Whoami on my home host and expose it at whoami.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull traefik/whoami:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Useful for testing reverse proxy setup</li>
<li>Shows request headers and info</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/whoami/config:/config</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<p>None. The container runs with its upstream defaults.</p><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull traefik/whoami:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with Whoami:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>whoami</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+135
View File
@@ -0,0 +1,135 @@
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import DocsLayout from '@/components/docs/DocsLayout';
export const metadata = {
title: 'Install WireGuard VPN — DashCaddy Docs',
description: 'Install and configure WireGuard VPN via DashCaddy. Fast, modern, secure VPN tunnel',
};
export default function wireguardDocsPage() {
return (
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
<Navbar />
<DocsLayout
title="Install WireGuard VPN"
intro="Fast, modern, secure VPN tunnel"
>
<div className="mb-6 flex flex-wrap gap-3 text-xs font-semibold">
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Category: Networking</span>
<span className="rounded-full px-3 py-1" style={{ backgroundColor: '#f5a62322', color: '#f5a623' }}>Difficulty: Advanced</span>
<span className="rounded-full bg-surface-800 px-3 py-1 text-surface-300">Docker image: <code className="text-brand-400">linuxserver/wireguard:latest</code></span>
</div>
<h2>What is WireGuard VPN?</h2>
<p>Fast, modern, secure VPN tunnel</p>
<p>WireGuard VPN ships as a self-contained Docker image that DashCaddy provisions with one click. DashCaddy handles the container lifecycle, DNS record, Caddy reverse-proxy entry, and HTTPS certificate so you can focus on using WireGuard VPN, not installing it.</p>
<h2>Prerequisites</h2>
<ul>
<li>A running DashCaddy host with the dashboard accessible (default URL: <code>https://status.sami</code>; configurable via the <code>dashboardHost</code> setting in <code>config.json</code>).</li>
<li>You must be signed in to the dashboard with an admin session, or have an API key with admin scope (<code>POST /api/v1/auth/keys</code> to create one).</li>
<li>No special host paths required.</li>
<li>For HTTPS with a real certificate, Technitium DNS must be configured and the host must have port 80/443 reachable from the internet. Otherwise DashCaddy will fall back to direct-IP access or a self-signed certificate.</li>
</ul>
<h2>Install via the DashCaddy dashboard</h2>
<ol>
<li>Sign in at <code>https://status.sami</code> (or your host's dashboard URL).</li>
<li>Click the <strong>📱 App Selector</strong> button on the dashboard home page.</li>
<li>Pick <strong>WireGuard VPN</strong> from the Networking category.</li>
<li>Fill in the deployment form: subdomain (default suggestion: <code>vpn</code>), host port (default: <code>51820</code>).</li>
<li>Click <strong>Deploy</strong>. DashCaddy will pull the image, create persistent volumes, write a Caddy route, create the DNS record, and wait up to 30 seconds for the container's health check (<code>/</code>) to pass.</li>
<li>When the dashboard shows the service as <strong>Running</strong>, the URL (built from your subdomain and configured TLD) is clickable. The deploy API returns a synchronous response with <code>&#123;success, containerId, url, message, setupInstructions&#125;</code> there is no separate status-poll endpoint; the dashboard updates live.</li>
</ol>
<h2>Install via the REST API</h2>
<p>Authenticate with an API key (or a JWT minted via <code>POST /api/v1/auth/jwt</code>). Send as <code>X-API-Key: dk_...</code> or <code>Authorization: Bearer &lt;jwt&gt;</code>.</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/apps/deploy \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123;
"appId": "wireguard",
"config": &#123;
"subdomain": "vpn",
"port": 51820
&#125;
&#125;'</code></pre>
<p>The full body schema is in <code>src/utilities/validate.js</code> (Joi schema <code>appDeploy</code>). All <code>config.*</code> fields except <code>subdomain</code> are optional. Notable options:</p>
<ul>
<li><code>config.port</code> host port (165535). Defaults to the template's <code>defaultPort</code>.</li>
<li><code>config.mediaPath</code> host directory to mount as the media library.</li>
<li><code>config.plexClaimToken</code> Plex claim token (when the upstream service needs one).</li>
<li><code>config.useExisting: true</code> + <code>existingContainerId</code> attach DashCaddy metadata to an already-running container instead of pulling a new image.</li>
<li><code>config.tailscaleOnly: true</code> restrict the reverse-proxy entry to your Tailscale network.</li>
<li><code>config.allowedIPs</code> array of CIDR ranges allowed past the reverse proxy.</li>
<li><code>config.createDns: false</code> skip DNS record creation (use when the subdomain already resolves).</li>
<li><code>config.resources</code> <code>&#123;memory, cpus&#125;</code> limits applied to the container.</li>
</ul>
<h2>Install via the AI Intent Router (returns a structured intent)</h2>
<p>The Intent Router returns a structured intent, not a deployed container. To deploy via AI, send natural language to:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>curl -X POST https://status.sami/api/v1/ai/intent \\
-H "X-API-Key: dk_your_api_key" \\
-H "Content-Type: application/json" \\
-d '&#123; "message": "Deploy WireGuard VPN on my home host and expose it at vpn.sami" &#125;'</code></pre>
<p>The response includes <code>intent</code>, <code>action</code>, <code>parameters</code>, and <code>followup</code> your client (or the MCP server) must then call <code>POST /api/v1/apps/deploy</code> with those parameters to actually provision the container.</p>
<h2>Install via the MCP Server</h2>
<p>For AI agents (Claude Desktop, Hermes, etc.) configure the MCP server (<code>src/mcp/mcp-server.js</code>) with:</p>
<pre className="overflow-x-auto rounded-lg bg-surface-900 p-4 text-sm"><code>DASHCADDY_URL=https://status.sami:3001 # internal API URL, may differ from dashboard URL
DASHCADDY_API_KEY=dk_your_api_key</code></pre>
<p>The server exposes <code>dashcaddy_deploy_app</code>. Note: this tool writes the Caddy route and creates the <code>services.json</code> entry, but it does NOT pull the Docker image or start the container. You must run <code>docker pull linuxserver/wireguard:latest</code> and start the container yourself for the URL to actually serve traffic. For a fully-managed deployment, call <code>POST /api/v1/apps/deploy</code> directly from your agent.</p>
<h2>Post-install: first-run checklist</h2>
<ol>
<li>Configure your external IP/domain</li>
<li>Set up port forwarding on router</li>
<li>Download client configs from /config/peer1/</li>
</ol>
<h2>Volumes and persistent data</h2>
<p>DashCaddy creates these volume mounts in the container spec:</p>
<ul>
<li><code>/opt/wireguard/config:/config</code></li>
</ul>
<p>All paths are host paths (left side) mapped into the container (right side). Restarting the container preserves data; reinstalling the template preserves it unless you explicitly pass <code>config.useExisting: false</code> AND wipe the volume. Bind mounts use the host-path conventions above (e.g. <code>/opt/plex/config</code> becomes a bind mount to the host directory of the same path).</p>
<h2>Environment variables</h2>
<ul>
<li><code>PUID</code></li>
<li><code>PGID</code></li>
<li><code>TZ</code></li>
<li><code>SERVERURL</code></li>
<li><code>SERVERPORT</code></li>
<li><code>PEERS</code></li>
</ul><p>These are baked into the template at deploy time and shipped to the container. The deploy handler does NOT allow overriding them via the API payload to change them you must edit the template definition in <code>dashcaddy-api/src/docker/app-templates.js</code>.</p>
<h2>Updating the image</h2>
<p>There is no built-in auto-update DashCaddy does NOT poll for new image digests. To pull a new version:</p>
<ol>
<li>SSH into the DashCaddy host and run <code>docker pull linuxserver/wireguard:latest</code>.</li>
<li>Restart the container: <code>docker restart &lt;containerId&gt;</code> (find the ID via <code>GET /api/v1/services</code> or the dashboard).</li>
<li>Or deploy Watchtower from this catalog (separate template that polls Docker Hub and updates other containers automatically, default schedule <code>0 0 4 * * *</code> = 04:00 daily).</li>
</ol>
<h2>Backups</h2>
<p>The default backup policy includes the entire <code>/app/data/</code> directory (services, config, credentials, stats) AND all Docker volumes. Backups are scheduled via <code>backup-config.json</code> the default schedule is configurable, not "nightly" out of the box. To restore on a fresh host, redeploy the same template and then call <code>POST /api/v1/apps/&#123;appId&#125;/restore</code> with a backup ID from <code>GET /api/v1/backups/history</code>.</p>
<h2>Troubleshooting</h2>
<p>Common issues with WireGuard VPN:</p>
<ul>
<li><strong>Container won't start:</strong> check the dashboard's Logs panel. Most startup failures are permission errors on the host-path volume.</li>
<li><strong>URL not reachable after deploy:</strong> the Caddy route was written but DNS hasn't propagated, or port 80/443 is firewalled. Check <code>GET /api/v1/dns/records</code> and <code>systemctl status caddy</code> on the host.</li>
<li><strong>Health check timeout (deploy returns 30s after start):</strong> the container is starting but <code>/</code> is not returning 200. Inspect <code>docker logs &lt;containerId&gt;</code> directly.</li>
</ul>
<p>For layer-by-layer diagnostics, see the <a href="/docs/troubleshooting" className="text-brand-400 underline">Troubleshooting guide</a>.</p>
<hr className="my-8 border-surface-700" />
<p className="text-sm text-surface-400">
Template ID: <code>wireguard</code>. Source: <code>dashcaddy-api/src/docker/app-templates.js</code>.
</p>
</DocsLayout>
<Footer />
</div>
);
}
+353 -16
View File
@@ -8,35 +8,372 @@ export default function DocsFirstServicePage() {
<Navbar />
<DocsLayout
title="Deploy Your First Service"
intro="This is where DashCaddy becomes real: you take a service from container or target port to DNS-backed, reverse-proxied, HTTPS-enabled application visible in one dashboard."
intro="This is where DashCaddy becomes real: take an app from a template, an existing container, or a raw target port to a DNS-backed, reverse-proxied, HTTPS-enabled service visible in one dashboard — in a few clicks."
>
<h2>Prerequisites</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">Before you begin:</strong> Complete the <a href="/docs/installation" className="text-brand-400 hover:text-brand-300 underline">Installation Guide</a> first. You need a running DashCaddy instance with the dashboard accessible, the API responding on <code>/healthz</code>, and Caddy's Admin API reachable. Technitium DNS is recommended but optional services will still deploy without it using direct IP access.
</p>
</blockquote>
<h2>What DashCaddy handles for you</h2>
<p>When you deploy a service, DashCaddy automates the full infrastructure chain:</p>
<ul>
<li>service records</li>
<li>app template deployment or manual service definition</li>
<li>DNS creation when configured</li>
<li>Caddy reverse proxy updates</li>
<li>service visibility and health tracking</li>
<li>Container deployment (from template) or adoption of an existing container</li>
<li>Service record creation in the DashCaddy state store</li>
<li>DNS record creation through Technitium DNS (when configured)</li>
<li>Caddy reverse-proxy route configuration via the Admin API</li>
<li>Automatic TLS certificate issuance through Caddy's internal CA / DashCA</li>
<li>Real-time health tracking and WebSocket status updates on the dashboard</li>
</ul>
<p>
You provide the intent (which app, which hostname), and DashCaddy coordinates every layer atomically.
If any step fails, the operation rolls back cleanly you never end up with a half-wired service.
</p>
<h2>Three ways to add a service</h2>
<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,
volumes, environment variables, and the recommended subdomain.
</p>
<h3>2. Use Service Discovery to auto-detect existing containers</h3>
<p>
Already running Docker containers? DashCaddy's <strong>Service Discovery</strong> scans the host and lists
every running container, marking any that are not yet managed.
</p>
<h3>3. Define a service manually</h3>
<p>
For custom images or apps not in the template library, define the service by hand with full control
over image, ports, volumes, and environment variables.
</p>
<h2>Step-by-step: Deploy Plex</h2>
<p>
Let's walk through deploying Plex Media Server using the template library. This is the most common
path for new users and demonstrates the full deployment chain.
</p>
<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 77 applications.
</p>
<h3>Step 2: Find Plex</h3>
<p>
Type "Plex" in the search bar, or browse the "Media" category. Click the Plex template card to
open its configuration form.
</p>
<h3>Step 3: Configure the service</h3>
<p>The form is pre-filled with sensible defaults. Review and adjust:</p>
<ul>
<li><strong>Service name:</strong> <code>plex</code> (used for internal identification)</li>
<li><strong>Hostname:</strong> <code>plex.local</code> (the subdomain DashCaddy will publish)</li>
<li><strong>Container image:</strong> <code>linuxserver/plex:latest</code></li>
<li><strong>Port:</strong> <code>32400</code> (Plex's default web interface port)</li>
<li><strong>Volumes:</strong> <code>/opt/plex/config:/config</code> and <code>/mnt/media:/media</code></li>
<li><strong>Environment variables:</strong> <code>PUID=1000</code>, <code>PGID=1000</code>, <code>VERSION=docker</code></li>
</ul>
<h2>Typical first deployment flow</h2>
<h3>Step 4: Deploy</h3>
<p>
Click <strong>Deploy</strong>. DashCaddy now executes the full deployment chain:
</p>
<ol>
<li>Open the dashboard.</li>
<li>Choose a template or define a service manually.</li>
<li>Provide the service name, hostname/subdomain, and backend target.</li>
<li>Let DashCaddy wire DNS and Caddy where configured.</li>
<li>Wait for readiness, then verify the service URL.</li>
<li>Pulls the <code>linuxserver/plex:latest</code> Docker image</li>
<li>Creates and starts the container with your configured volumes and environment</li>
<li>Creates a service record in the DashCaddy state store</li>
<li>Generates a Caddy route mapping <code>plex.local</code> <code>localhost:32400</code></li>
<li>Applies the route through the Caddy Admin API</li>
<li>Requests a TLS certificate for <code>plex.local</code> via DashCA</li>
<li>Creates an A record in Technitium DNS pointing <code>plex.local</code> to your host IP</li>
<li>Starts health checks and reports status on the dashboard</li>
</ol>
<h2>If the service is internal-only</h2>
<h3>Step 5: Verify</h3>
<p>
Verify the client trusts the DashCA root certificate, the internal domain resolves properly, and the route is reachable on the network you intend to use.
Within 30 seconds, the service card on the dashboard should show <strong>Healthy</strong> with a green
status indicator. Click the service to see its detail page, which shows:
</p>
<ul>
<li>Container logs (live-streamed via WebSocket)</li>
<li>Resource usage (CPU, memory, network I/O)</li>
<li>Caddy route configuration</li>
<li>DNS record details</li>
<li>Certificate expiration date</li>
<li>Health check history</li>
</ul>
<h2>Service Discovery: Adopt existing containers</h2>
<p>
If you already have Docker containers running that you want DashCaddy to manage, use Service Discovery
instead of redeploying from scratch.
</p>
<h2>If the service does not come up correctly</h2>
<h3>How it works</h3>
<p>
Debug in order: backend process, reverse proxy, DNS, TLS trust, and finally dashboard/API state.
Service Discovery scans the Docker socket and lists every running container on the host. Containers
that are already managed by DashCaddy are marked as "Managed." Unmanaged containers are listed with
their image name, exposed ports, and current status.
</p>
<h3>Adopt a container</h3>
<ol>
<li>Open <strong>Service Discovery</strong> from the sidebar</li>
<li>Review the list of detected containers</li>
<li>For each one you want to manage, click <strong>Adopt</strong></li>
<li>Provide a hostname/subdomain (e.g., <code>grafana.local</code>)</li>
<li>Specify which port to expose (if the container exposes multiple)</li>
<li>Click <strong>Adopt &amp; Configure</strong></li>
</ol>
<p>
DashCaddy creates a service record, generates the Caddy route, DNS record, and certificate without
restarting or modifying the running container. The container continues running with its existing
configuration; DashCaddy simply adds the proxy and DNS layers on top.
</p>
<h2>Behind the scenes: The deployment chain</h2>
<p>
Understanding what happens during a deployment helps you troubleshoot when things go wrong. Here's
the full chain, layer by layer:
</p>
<h3>1. Docker: Container creation</h3>
<p>
The orchestration layer calls the Docker API to create a container from the specified image. It attaches
the container to the <code>dashcaddy-net</code> bridge network, mounts the configured volumes, and injects
environment variables. The container starts in the background and begins listening on its configured port.
</p>
<h3>2. DashCaddy: Service record</h3>
<p>
A service record is written to the DashCaddy state store (a SQLite database in <code>./data/services.db</code>).
The record includes the service name, hostname, backend port, container ID, deployment timestamp, and
configuration metadata. This record is the source of truth for the dashboard and API.
</p>
<h3>3. Caddy: Reverse proxy route</h3>
<p>
The Caddyfile-as-Code builder generates a route configuration:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`plex.local {
reverse_proxy localhost:32400
encode gzip
header / {
Strict-Transport-Security "max-age=31536000;"
}
}`}</code></pre>
<p>
This configuration is applied atomically through the Caddy Admin API (<code>POST /load</code>). Caddy
reloads its configuration without downtime and begins routing traffic for <code>plex.local</code> to
<code>localhost:32400</code>.
</p>
<h3>4. DNS: Record creation</h3>
<p>
If Technitium DNS is configured, the orchestration layer calls the Technitium API to create an A record:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`POST /api/zones/records/create
{
"zone": "local",
"type": "A",
"name": "plex",
"ipAddress": "192.168.1.100"
}`}</code></pre>
<p>
The DNS record propagates immediately (Technitium is authoritative for the <code>local</code> zone).
Clients on your network can now resolve <code>plex.local</code> to your host's IP address.
</p>
<h3>5. TLS: Certificate issuance</h3>
<p>
Caddy's built-in ACME client detects the new hostname and requests a certificate. For internal domains
like <code>plex.local</code>, Caddy uses its internal CA (DashCA) rather than Let's Encrypt. The certificate
is issued, stored in Caddy's data directory, and served automatically for all HTTPS connections to
<code>plex.local</code>.
</p>
<p>
The certificate is valid for 90 days and renewed automatically 30 days before expiration. DashCaddy
tracks certificate expiration dates and surfaces warnings on the dashboard when renewal is approaching.
</p>
<h3>6. Health: Continuous monitoring</h3>
<p>
Once the service is deployed, DashCaddy starts a health check loop that runs every 30 seconds. The health
check performs an HTTP GET to the backend port and expects a 2xx or 3xx response. If the check fails three
times in a row, the service is marked <strong>Unhealthy</strong> on the dashboard and an event is logged.
</p>
<p>
Health status updates are pushed to the dashboard over WebSocket, so you see status changes in real-time
without refreshing the page.
</p>
<h2>Configuration reference</h2>
<p>
When deploying a service, these are the configuration fields available in the deployment form:
</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">Field</th>
<th className="py-3 pr-4 font-semibold text-surface-200">Required</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">Service Name</td>
<td className="py-3 pr-4">Yes</td>
<td className="py-3">Internal identifier (lowercase, no spaces)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Hostname</td>
<td className="py-3 pr-4">Yes</td>
<td className="py-3">Subdomain for the service (e.g., plex.local)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Container Image</td>
<td className="py-3 pr-4">Yes</td>
<td className="py-3">Docker image (e.g., linuxserver/plex:latest)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Backend Port</td>
<td className="py-3 pr-4">Yes</td>
<td className="py-3">Port the container listens on internally</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Volumes</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">Host:container path mappings for persistent storage</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Environment Variables</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">Key-value pairs injected into the container</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Network</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">Docker network to attach (default: dashcaddy-net)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4 font-mono text-xs">Restart Policy</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">Container restart behavior (default: unless-stopped)</td>
</tr>
<tr>
<td className="py-3 pr-4 font-mono text-xs">Health Check Path</td>
<td className="py-3 pr-4">No</td>
<td className="py-3">HTTP path for health checks (default: /)</td>
</tr>
</tbody>
</table>
</div>
<h2>Common scenarios</h2>
<h3>Deploy an internal-only service</h3>
<p>
For services that should stay on the local network (not exposed to the internet), use a <code>.local</code>
or <code>.internal</code> TLD. Ensure client devices trust the DashCA root certificate (download it from
the DashCA page in the dashboard). The service will be accessible at <code>https://servicename.local</code>
with a trusted HTTPS connection, but only from devices on your network that have the root cert installed.
</p>
<h3>Deploy a service with custom environment variables</h3>
<p>
When deploying manually or editing a template, you can add custom environment variables in the deployment
form. Each variable is a key-value pair that gets injected into the container at startup. Common examples:
</p>
<ul>
<li><code>PUID=1000</code> / <code>PGID=1000</code> user/group ID for file permissions (LinuxServer images)</li>
<li><code>TZ=America/New_York</code> timezone for log timestamps</li>
<li><code>DB_PASSWORD=secret</code> database credentials for apps like Nextcloud</li>
</ul>
<h3>Deploy multiple services on the same host</h3>
<p>
DashCaddy handles multiple services on the same host automatically. Each service gets a unique subdomain,
and Caddy routes traffic based on the <code>Host</code> header. You can run Plex on <code>plex.local</code>,
Nextcloud on <code>nextcloud.local</code>, and Grafana on <code>grafana.local</code> all on the same
host, all on port 443, with no port conflicts.
</p>
<h3>Adopt a service that's already running</h3>
<p>
If you have a container running outside of DashCaddy (e.g., started manually with <code>docker run</code>),
use Service Discovery to adopt it. DashCaddy will add the proxy and DNS layers without restarting the
container. The container's existing volumes, environment, and network configuration are preserved.
</p>
<h2>Verification checklist</h2>
<p>After deploying a service, verify each layer:</p>
<ol>
<li>
<strong>Container status:</strong> The service card on the dashboard shows <strong>Running</strong> with
a green status indicator
</li>
<li>
<strong>Backend port:</strong> <code>curl http://localhost:32400</code> returns a response from the
application
</li>
<li>
<strong>Caddy route:</strong> <code>curl http://localhost:2019/config/</code> shows a route for your
hostname
</li>
<li>
<strong>DNS resolution:</strong> <code>ping plex.local</code> resolves to your host's IP address
</li>
<li>
<strong>TLS certificate:</strong> <code>curl -v https://plex.local</code> shows a valid certificate
(no warnings if DashCA root is installed)
</li>
<li>
<strong>Health check:</strong> The dashboard shows <strong>Healthy</strong> and the health check
history graph shows consistent success
</li>
</ol>
<h2>Troubleshooting</h2>
<p>If the service does not come up correctly, debug in order, layer by layer:</p>
<ol>
<li><strong>Container:</strong> Check <code>docker logs &lt;container_name&gt;</code> for startup errors</li>
<li><strong>Backend port:</strong> Verify the container is listening on the expected port</li>
<li><strong>Caddy route:</strong> Confirm the route exists in the Caddy Admin API</li>
<li><strong>DNS resolution:</strong> Check that the DNS record was created in Technitium</li>
<li><strong>TLS trust:</strong> Verify the DashCA root certificate is installed on the client device</li>
<li><strong>Dashboard state:</strong> Check the service detail page for error messages or failed health checks</li>
</ol>
<p>
See the <a href="/docs/troubleshooting" className="text-brand-400 hover:text-brand-300 underline">Troubleshooting Guide</a> for
the full checklist and common error patterns.
</p>
<h2>Next steps</h2>
<ul>
<li>
<a href="/docs/overview" className="text-brand-400 hover:text-brand-300 underline">
Product Overview
</a> understand the full architecture and design philosophy
</li>
<li>
Explore the <strong>Template Library</strong> to discover other applications you can deploy
</li>
<li>
Configure <strong>Service Discovery</strong> to adopt existing containers
</li>
<li>
Set up <strong>Prometheus metrics</strong> for external monitoring integration
</li>
</ul>
</DocsLayout>
<Footer />
</div>
+262 -24
View File
@@ -8,44 +8,282 @@ export default function DocsInstallationPage() {
<Navbar />
<DocsLayout
title="Installation Guide"
intro="DashCaddy supports both guided installation and manual operator-controlled setup. This guide covers the dependencies and the two practical setup paths."
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>Prerequisites</h2>
<h2>Before you start</h2>
<p>
You only need three things to install DashCaddy:
</p>
<ul>
<li>Docker and Docker Compose</li>
<li>Caddy with Admin API access</li>
<li>Node.js 18+ for the API server</li>
<li>Technitium DNS if you want DNS automation</li>
<li><strong>A Linux host that stays on</strong> Ubuntu 22.04 LTS, Debian 12, or any recent Linux distribution. 2&nbsp;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&apos;s IP for local-only use.</li>
</ul>
<h2>Installer-based setup</h2>
<h2>Pick your hostname</h2>
<p>
DashCaddy includes a dedicated installer intended to walk users through dependency checks, file deployment,
configuration generation, and first launch.
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>
<h2>Manual setup</h2>
<h3>Option A A real domain you own (recommended)</h3>
<p>
Manual setup is best when you want direct control over paths, services, Caddy, DNS integration, and deployment layout.
If you own <code>example.com</code>, register <code>dashcaddy.example.com</code> as a DNS A record pointing at
your host&apos;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&apos;s Encrypt
certificate on first launch no certificate warnings, no client setup.
</p>
<h3>Option B A custom TLD on your home network</h3>
<p>
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>
<ul>
<li><strong>Router DNS</strong> most home routers let you add a local DNS entry. Point <code>*.dashcaddy.home</code> at your server&apos;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>
Custom TLDs require installing DashCaddy&apos;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&apos;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>
<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">No port forwarding?</strong> If your ISP puts you behind CGNAT or you
don&apos;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&apos;s origin, then run the installer
with <code>--no-caddy</code> and let the tunnel handle HTTPS termination.
</p>
</blockquote>
<h2>Run the installer</h2>
<p>
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>Clone the software repo.</li>
<li>Install the API dependencies.</li>
<li>Prepare Caddy and confirm the Admin API is reachable.</li>
<li>Prepare Technitium DNS if using automatic DNS changes.</li>
<li>Configure environment and state paths.</li>
<li>Start the DashCaddy API.</li>
<li>Serve the dashboard through Caddy.</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>Post-install checks</h2>
<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&apos;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&apos;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&nbsp;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&apos;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&apos;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>Dashboard loads in browser</li>
<li>API responds correctly</li>
<li>Caddy Admin API is reachable</li>
<li>Service management UI loads</li>
<li>DNS/certificate integration is healthy if configured</li>
<li><strong>macOS</strong> open the downloaded <code>.crt</code>, add it to the System keychain, set it to &ldquo;Always Trust&rdquo;</li>
<li><strong>Windows</strong> double-click the <code>.crt</code>, install to &ldquo;Trusted Root Certification Authorities&rdquo;</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&apos;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&apos;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&apos;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&apos;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">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">Installer says &ldquo;Docker not found&rdquo;</td>
<td className="py-3 pr-4">Docker Engine isn&apos;t installed or you&apos;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">Installer fails on &ldquo;port 80/443 already in use&rdquo;</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">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">Browser shows &ldquo;Your connection is not private&rdquo;</td>
<td className="py-3 pr-4">Custom-TLD install with no DashCA trust, OR the domain doesn&apos;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&apos;s public IP.</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Let&apos;s Encrypt certificate never issues</td>
<td className="py-3 pr-4">Domain doesn&apos;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&apos;re on CGNAT, switch to Tailscale Funnel or Cloudflare Tunnel.</td>
</tr>
<tr>
<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>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
# Remove everything including config and data (irreversible)
curl -fsSL https://get.dashcaddy.net | bash -s -- --uninstall --purge`}</code></pre>
<h2>Next steps</h2>
<p>
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 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 />
</div>
+269 -11
View File
@@ -8,31 +8,289 @@ export default function DocsIntegrationsPage() {
<Navbar />
<DocsLayout
title="Infrastructure Integrations"
intro="DashCaddy is most valuable when its supporting integrations are healthy. This guide explains the layers it expects to work with and how they fit together."
intro="DashCaddy is most valuable when its supporting integrations are healthy. This guide explains each layer it expects to work with, what it does, and how the pieces fit together into a single control plane."
>
<h2>Docker</h2>
<p>
Docker is the runtime foundation for deployment workflows, container lifecycle actions, and app-template based launches.
DashCaddy is not a monolith. It is an orchestration layer that drives several independent infrastructure
components a container runtime, a reverse proxy, a DNS server, a certificate authority, a private network,
a metrics pipeline, and an AI surface. Each integration is swappable, observable, and independently debuggable.
When you understand what each layer is responsible for, you can pinpoint failures in minutes instead of guessing.
</p>
<p>
This guide walks every integration in depth: what it does, how DashCaddy talks to it, the configuration it
expects, and a code example where relevant. Read it end-to-end once, then come back to specific sections when
something goes wrong. For a quick diagnostic flow, see the <a href="/docs/troubleshooting">Troubleshooting</a> guide.
</p>
<h2>Caddy</h2>
<h2>Docker container runtime</h2>
<p>
Caddy is the reverse proxy and HTTPS publication layer. DashCaddy relies on the Caddy Admin API to manage routes and exposure.
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>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,
DashCaddy&apos;s <code>/readyz</code> probe will fail immediately a fast signal that the runtime layer is broken.
During the <strong>Smart Defaults Wizard</strong>, DashCaddy probes the socket, reports the Docker version, and
suggests socket paths if the default is not found.
</p>
<p>
Optional <strong>Docker Swarm</strong> support (Premium) extends the same model across multiple nodes. When
Swarm mode is enabled, DashCaddy switches from single-container operations to service-level operations, managing
placement, replicas, and rolling updates across the cluster. See <a href="/docs/premium">Premium Features</a>.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Verify the Docker socket DashCaddy will use
docker version
ls -l /var/run/docker.sock
# The DashCaddy container needs the socket mounted:
docker run -d \\
-v /var/run/docker.sock:/var/run/docker.sock \\
-p 3000:3000 \\
ghcr.io/dashcaddy/dashcaddy:latest`}</code></pre>
<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">Note:</strong> Mounting the Docker socket grants full container control.
In production, run DashCaddy behind Tailscale or a firewall so the dashboard is not exposed to the public
internet.
</p>
</blockquote>
<h2>Caddy reverse proxy &amp; automatic HTTPS</h2>
<p>
Caddy is the reverse proxy and automatic HTTPS layer. Every service you publish through DashCaddy gets a Caddy
route that terminates TLS and proxies traffic to the upstream container. DashCaddy communicates with the
<strong> Caddy Admin API</strong> (default <code>localhost:2019</code>) to create, update, and remove routes,
and to trigger certificate issuance on demand.
</p>
<p>
Caddy&apos;s built-in internal CA auto-generates and renews certificates for every published service. For
public domains, Caddy can also use ACME (Let&apos;s Encrypt / ZeroSSL) automatically. The choice between
internal and public CA is made per-service at publish time, so you can mix internet-facing and lab services
on the same host without conflict.
</p>
<h2>Technitium DNS</h2>
<h3>Caddyfile-as-Code</h3>
<p>
Technitium DNS is the documented DNS automation target for record creation and removal.
Instead of hand-editing Caddyfiles, DashCaddy exposes a <strong>visual Caddyfile-as-Code builder</strong>. You
describe the desired route hostname, upstream, TLS options, headers, redirects, compression and DashCaddy
generates the valid Caddy configuration and applies it atomically through the Admin API. Configuration is
versioned and reviewable, so every change is auditable and reversible.
</p>
<p>
The generated config is rendered in the service&apos;s <strong>Caddyfile-as-Code view</strong>, so you can
inspect exactly what Caddy will receive before it is applied. If a route misbehaves, compare the rendered config
against your expectation. Invalid configs are rejected before they reach Caddy, preventing the proxy from
reloading into a broken state.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Example generated Caddyfile (internal CA, lab hostname)
media.lab {
tls internal
encode zstd gzip
reverse_proxy localhost:8096 {
header_up X-Forwarded-Host {host}
header_up X-Real-IP {remote_host}
}
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options nosniff
}
}`}</code></pre>
<h2>Technitium DNS DNS automation</h2>
<p>
Technitium DNS is the DNS automation target for record creation and removal. When you deploy or adopt a service,
DashCaddy creates the corresponding A or CNAME record through the Technitium REST API so the new hostname
resolves immediately. Removing a service cleans up the record automatically no orphaned DNS entries.
</p>
<p>
DashCaddy needs three pieces of information to drive Technitium: the server URL, an API token with write access
to the target zone, and the zone name itself. All three are configured during the Smart Defaults Wizard or
later under <strong>Settings DNS</strong>. A common failure mode is a token with the wrong scope it can
read records but not create them which fails silently. Always verify the token can write to the zone you
intend to use.
</p>
<p>
Internal zones (e.g. <code>.lab</code>) only resolve if the client uses Technitium as its resolver. Public
resolvers like 8.8.8.8 will not know about them. For remote clients, either point their DNS at Technitium
directly or use <strong>Tailscale</strong> with a MagicDNS / split-DNS setup.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Create a record directly via the Technitium API (debugging)
curl -X POST "http://technitium-host:5380/api/zones/records/add" \\
-d "token=***" \\
-d "zone=lab" \\
-d "domain=media.lab" \\
-d "type=A" \\
-d "ipAddress=192.168.1.50"
# Verify the record resolves through Technitium
dig @technitium-host media.lab +short`}</code></pre>
<h2>DashCA internal certificate authority</h2>
<p>
DashCA is the certificate distribution system that makes internal HTTPS practical. Caddy&apos;s internal CA
issues certificates automatically for <code>.lab</code> and other private hostnames; DashCA provides the
distribution page where you download the root certificate and install it as a trusted CA across your devices.
Once trusted, every internal service is served over valid HTTPS with no browser warnings.
</p>
<p>
The root certificate must be installed on <strong>each client device</strong> that will access internal
services not just the server. A macOS laptop, a Windows desktop, and an Android phone each need the cert
installed separately. The DashCA page includes per-platform instructions (macOS Keychain, Windows certmgr,
Linux <code>update-ca-certificates</code>, and mobile profiles) to make this straightforward.
</p>
<p>
After installing the root CA, restart your browser or clear its certificate cache. Chrome and Firefox maintain
separate trust stores on some platforms; Firefox may need the import done from within its own settings.
</p>
<h2>DashCA</h2>
<h2>Tailscale private access</h2>
<p>
DashCA is the certificate distribution system that makes internal HTTPS practical by letting users trust the local CA across devices.
DashCaddy fits naturally into private access patterns with <strong>Tailscale</strong>. Services can be published
only on a Tailnet, keeping them off the public internet while still benefiting from DashCaddy&apos;s DNS, proxy,
and TLS automation. This is ideal for home labs, internal team tools, and any service that should never be
internet-facing.
</p>
<p>
The typical setup runs Tailscale on the DashCaddy host, advertises the host on the Tailnet, and optionally
enables MagicDNS so Tailnet hostnames resolve without a separate DNS server. Combine with Technitium split-DNS
for the most seamless experience: Technitium handles <code>.lab</code> zones for Tailnet clients, while public
domains resolve normally.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Install and authenticate Tailscale on the DashCaddy host
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up --advertise-routes=192.168.1.0/24 --accept-routes
# Verify the host is on the Tailnet
tailscale status
tailscale ip
# From another Tailnet device, reach the service directly
curl -k https://dashcaddy-host.tailnet-name.ts.net/media.lab`}</code></pre>
<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">Tip:</strong> If you publish services only on the Tailnet, set Caddy to
bind to the Tailscale interface IP rather than <code>0.0.0.0</code>. This guarantees the service is
unreachable from the LAN even if the firewall is misconfigured.
</p>
</blockquote>
<h2>Prometheus &amp; Grafana metrics &amp; observability</h2>
<p>
DashCaddy exports metrics in Prometheus format at <code>/metrics</code>, including service health, container
status, request counts, certificate expiry, and system resource indicators. Point your Prometheus scraper at
the endpoint and build Grafana dashboards on top for long-term observability, capacity planning, and alerting.
</p>
<p>
The metrics endpoint is unauthenticated by default for internal scraping. If your Prometheus instance is on a
different host or network, place it behind the same Tailscale Tailnet or restrict access with a reverse-proxy
basic-auth rule in Caddy.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# prometheus.yml — scrape DashCaddy
scrape_configs:
- job_name: 'dashcaddy'
metrics_path: /metrics
static_configs:
- targets: ['dashcaddy-host:3000']
# Optional: increase scrape frequency for faster alerting
scrape_interval: 15s
scrape_timeout: 10s`}</code></pre>
<p>
Useful PromQL starters once data is flowing: <code>dashcaddy_service_health == 0</code> (unhealthy services),
<code> rate(dashcaddy_http_requests_total[5m])</code> (request throughput), and
<code>dashcaddy_cert_expiry_days &lt; 14</code> (certificates expiring soon).
</p>
<h2>Private access layers</h2>
<h2>MCP Server &amp; AI assistants</h2>
<p>
DashCaddy also fits well into private/internal access patterns where services should not be directly exposed to the public internet.
The built-in <strong>MCP (Model Context Protocol) Server</strong> exposes DashCaddy operations to AI assistants
and external automation. Combined with the <strong>AI Intent Router</strong>, you can issue natural-language
commands &ldquo;restart the media server&rdquo;, &ldquo;deploy the postgres template&rdquo;, &ldquo;is the
database healthy?&rdquo; and have DashCaddy execute the real infrastructure action through the standard MCP
tool interface.
</p>
<p>
This turns DashCaddy into an AI-operable control plane: the same operations available in the dashboard are
available as MCP tools, so an assistant like Claude or GPT can inspect and manage your infrastructure directly.
Full setup instructions, the tool catalog, and intent examples are in the <a href="/docs/api">API and Automation</a> guide.
</p>
<h2>How the layers fit together</h2>
<p>
The table below maps each integration to the layer it provides and the DashCaddy feature that consumes it.
When a service fails, locate the row whose symptom matches, then debug that integration directly.
</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Integration</th>
<th>DashCaddy feature that uses it</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Runtime</strong></td>
<td>Docker / Docker Compose</td>
<td>Deploy, adopt, lifecycle, templates, service discovery</td>
</tr>
<tr>
<td><strong>Proxy</strong></td>
<td>Caddy (Admin API)</td>
<td>Reverse proxy routes, Caddyfile-as-Code, auto HTTPS</td>
</tr>
<tr>
<td><strong>DNS</strong></td>
<td>Technitium DNS</td>
<td>Automatic A/CNAME record creation &amp; cleanup</td>
</tr>
<tr>
<td><strong>Trust</strong></td>
<td>DashCA (internal CA)</td>
<td>Root certificate distribution for internal HTTPS</td>
</tr>
<tr>
<td><strong>Access</strong></td>
<td>Tailscale</td>
<td>Private networking, Tailnet-only publishing</td>
</tr>
<tr>
<td><strong>Observe</strong></td>
<td>Prometheus / Grafana</td>
<td>Metrics export, alerting, long-term dashboards</td>
</tr>
<tr>
<td><strong>Automate</strong></td>
<td>MCP Server + AI Intent Router</td>
<td>Natural-language ops, AI assistant tool surface</td>
</tr>
<tr>
<td><strong>Secure</strong></td>
<td>Security Center + audit log</td>
<td>Event aggregation, change auditing, RBAC</td>
</tr>
</tbody>
</table>
<h2>Operational flow: deploy to observe</h2>
<p>
When everything is wired correctly, a single service publish triggers the full chain automatically:
</p>
<ol>
<li><strong>Deploy / Adopt</strong> Docker runs the container (or adopts an existing one).</li>
<li><strong>Publish</strong> Caddy creates the reverse-proxy route and requests a TLS certificate.</li>
<li><strong>Resolve</strong> Technitium DNS creates the hostname record so the domain resolves.</li>
<li><strong>Trust</strong> DashCA distributes the root CA so clients accept the internal cert.</li>
<li><strong>Observe</strong> health checks, Prometheus metrics, and WebSocket live updates report state.</li>
<li><strong>Secure</strong> Security Center aggregates events; audit logging records every change.</li>
</ol>
<p>
Each step is independently observable. If a service is unreachable, walk the chain in order the first broken
step is your failure. For the full diagnostic procedure, see <a href="/docs/troubleshooting">Troubleshooting</a>.
</p>
</DocsLayout>
<Footer />
+256 -17
View File
@@ -8,38 +8,277 @@ export default function DocsOverviewPage() {
<Navbar />
<DocsLayout
title="Product Overview"
intro="DashCaddy is a self-hosted control plane for deploying, exposing, and managing Docker applications with DNS automation, reverse proxy integration, internal HTTPS, and centralized service visibility."
intro="DashCaddy is a self-hosted control plane for deploying, exposing, and managing Docker applications with automatic DNS, reverse proxy, internal HTTPS, real-time monitoring, AI-driven operations, and centralized fleet visibility."
>
<h2>What you'll learn</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">What you'll learn:</strong> This page covers the full DashCaddy architecture, design philosophy, component breakdown, and how it compares to manual self-hosting. By the end, you'll understand why DashCaddy exists, what problems it solves, and how its layers work together as a unified platform.
</p>
</blockquote>
<h2>What DashCaddy is</h2>
<p>
DashCaddy brings together the layers that self-hosters usually wire by hand: Docker deployment,
reverse proxy management through Caddy, DNS automation through Technitium DNS, internal certificate
distribution through DashCA, service monitoring, and operational controls.
DashCaddy brings together the layers that self-hosters usually wire by hand Docker deployment,
Caddy reverse proxy, Technitium DNS automation, internal certificate distribution, service monitoring,
and operational tooling and unifies them behind a single, cohesive control plane.
</p>
<p>
The goal is simple: make running self-hosted services feel cohesive instead of fragmented.
Instead of editing a dozen config files and praying the layers agree, you deploy a service once and
DashCaddy wires the runtime, the proxy route, the DNS record, the TLS certificate, and the health
checks for you. The goal is simple: make running self-hosted services feel like one product instead
of six fragile integrations.
</p>
<h2>Core product components</h2>
<h2>Platform architecture</h2>
<p>
DashCaddy is a production-grade platform built on eight distinct layers, each responsible for a
specific concern. Together they form a complete self-hosting stack that replaces dozens of manual
configuration steps with a single declarative action.
</p>
<h3>1. Application Layer</h3>
<p>
The Application Layer is what operators interact with directly. It is a React-based dashboard that
provides real-time visibility into every service, container, and infrastructure component managed by
DashCaddy. Beyond the visual interface, this layer exposes a REST API under <code>/api/v1/</code> and
a WebSocket channel for live updates. Every action available in the UI deploying a service, editing
a Caddy route, reviewing audit logs is available through the API, making the dashboard a thin client
over a fully programmable control plane. The application layer also handles authentication, role-based
access control, TOTP two-factor enrollment, and multi-user admin invitations.
</p>
<h3>2. Orchestration Layer</h3>
<p>
The Orchestration Layer is the Node.js/Express engine at the heart of DashCaddy. It receives deployment
requests, coordinates Docker container lifecycle, drives Caddy reverse proxy configuration through the
Admin API, manages Technitium DNS records programmatically, and handles certificate issuance and renewal.
This layer is responsible for ensuring that every deployment is atomic either all layers succeed or the
operation rolls back cleanly. It maintains the authoritative service state store, tracks health checks,
and publishes events over WebSocket for the dashboard. The orchestration engine also powers the Smart
Defaults Wizard, Service Discovery, and the Caddyfile-as-Code builder.
</p>
<h3>3. Runtime Layer</h3>
<p>
The Runtime Layer is Docker and Docker Compose the container workloads that actually run your services.
DashCaddy manages container creation, network attachment, volume mounts, environment variable injection,
and lifecycle operations (start, stop, restart, remove). For advanced deployments, the platform supports
Docker Swarm for multi-host orchestration and Fleet Management for coordinating services across multiple
servers. Every container managed by DashCaddy is tracked in the service state store, enabling features
like Service Discovery (adopting existing containers) and Disaster Recovery (full-system backup and restore
with SHA-256 checksum verification).
</p>
<h3>4. Edge Layer</h3>
<p>
The Edge Layer is Caddy the reverse proxy that terminates HTTPS connections and routes traffic to your
services. DashCaddy manages Caddy entirely through its Admin API, never requiring manual edits to a
Caddyfile. The Caddyfile-as-Code builder generates configuration declaratively, and the orchestration
layer applies changes atomically. Caddy handles automatic TLS certificate issuance and renewal using its
built-in ACME client for public domains or its internal CA for private networks. The Edge Layer also
provides load balancing, header manipulation, request logging, and rate limiting all configurable
through the DashCaddy dashboard without touching Caddy's native configuration syntax.
</p>
<h3>5. Name Resolution Layer</h3>
<p>
The Name Resolution Layer is Technitium DNS a self-hosted authoritative DNS server that DashCaddy
controls programmatically. When you deploy a service with hostname <code>plex.local</code>, the
orchestration layer creates an A record pointing to your host's IP address automatically. When you
remove the service, the record is cleaned up. This eliminates the manual DNS management that plagues
most self-hosting setups. Technitium DNS also supports zone transfers, forwarding, and custom record
types for advanced networking scenarios. The integration is optional if you don't configure DNS,
DashCaddy skips this layer and your services still deploy with direct IP access.
</p>
<h3>6. Trust Layer</h3>
<p>
The Trust Layer handles certificate authority management and internal HTTPS distribution. Caddy's built-in
CA issues certificates automatically for every service, but those certificates are only trusted if the
client device trusts the issuing CA. DashCaddy solves this with DashCA an internal certificate authority
distribution surface. The Smart Defaults Wizard initializes DashCA on first launch and offers the root
certificate for download. Once installed on client devices (browsers, phones, IoT devices), every service
managed by DashCaddy presents a trusted HTTPS connection without certificate warnings. This layer also
handles certificate renewal tracking and expiration alerts.
</p>
<h3>7. Observability Layer</h3>
<p>
The Observability Layer provides real-time health monitoring, structured audit logging, and metrics export.
Every service managed by DashCaddy has a health check that runs continuously, with status updates pushed
to the dashboard over WebSocket. The layer exports Prometheus-compatible metrics at <code>/metrics</code>,
enabling integration with Grafana, VictoriaMetrics, or any Prometheus-compatible monitoring stack. Audit
logs capture every administrative action who deployed what, when, and from which IP providing the
accountability required for multi-user environments. The Security Center aggregates logs from multiple
sources (Caddy access logs, container stdout, authentication events) into a unified event pipeline for
threat detection and forensic analysis.
</p>
<h3>8. Intelligence Layer</h3>
<p>
The Intelligence Layer makes DashCaddy AI-native. The AI Intent Router accepts natural-language commands
like "deploy Plex on port 32400" or "show me all unhealthy services" and translates them into API calls.
The MCP (Model Context Protocol) Server exposes DashCaddy operations to external AI assistants Claude,
ChatGPT, or any MCP-compatible client can deploy services, check health, or modify configuration through
the protocol. This layer also powers the Plugin system, allowing third-party extensions to hook into
DashCaddy's event stream and extend functionality without modifying core code.
</p>
<h2>Design philosophy</h2>
<p>
DashCaddy is built on three principles that guide every architectural decision:
</p>
<ul>
<li><strong>Dashboard</strong>: the main operator interface for service visibility and management.</li>
<li><strong>DashCaddy API</strong>: the orchestration engine for deployments, DNS, reverse proxy, certificates, and operational tooling.</li>
<li><strong>Installer</strong>: a guided installation path for users who want faster setup.</li>
<li><strong>DashCA</strong>: the internal certificate authority distribution surface for trusted internal HTTPS.</li>
<li><strong>Licensing</strong>: Premium feature gating tied to external validation/deactivation flows.</li>
<li>
<strong>Atomic operations:</strong> Deploying a service should succeed or fail as a single transaction.
You should never end up with a container running but no DNS record, or a Caddy route pointing to a
container that doesn't exist. The orchestration layer coordinates all layers and rolls back on failure.
</li>
<li>
<strong>Declarative configuration:</strong> You describe what you want (hostname, port, image), and
DashCaddy figures out the implementation details (Caddy route, DNS record, certificate). The
Caddyfile-as-Code builder generates configuration from your intent, not the other way around.
</li>
<li>
<strong>Progressive disclosure:</strong> The Smart Defaults Wizard gets you running in minutes with
sensible defaults. Advanced users can customize every layer Caddy headers, DNS record types,
container resource limits but complexity is optional, not mandatory.
</li>
</ul>
<h2>Who DashCaddy is for</h2>
<h2>Comparison: Manual vs. DashCaddy Free vs. DashCaddy Premium</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">Capability</th>
<th className="py-3 pr-4 font-semibold text-surface-200">Manual Setup</th>
<th className="py-3 pr-4 font-semibold text-surface-200">DashCaddy Free</th>
<th className="py-3 font-semibold text-surface-200">DashCaddy Premium</th>
</tr>
</thead>
<tbody className="text-surface-300">
<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">77 one-click templates</td>
<td className="py-3">Templates + Recipes</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Reverse proxy</td>
<td className="py-3 pr-4">Hand-edit Caddyfile</td>
<td className="py-3 pr-4">Caddyfile-as-Code builder</td>
<td className="py-3">Same + fleet-wide routes</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">DNS automation</td>
<td className="py-3 pr-4">Manual record creation</td>
<td className="py-3 pr-4">Technitium integration</td>
<td className="py-3">Same + multi-zone</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">TLS certificates</td>
<td className="py-3 pr-4">Let's Encrypt / manual</td>
<td className="py-3 pr-4">Automatic via Caddy + DashCA</td>
<td className="py-3">Same</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Monitoring</td>
<td className="py-3 pr-4">Custom scripts</td>
<td className="py-3 pr-4">Real-time health + Prometheus</td>
<td className="py-3">Same + fleet dashboard</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Multi-user access</td>
<td className="py-3 pr-4">None</td>
<td className="py-3 pr-4">TOTP 2FA + RBAC</td>
<td className="py-3">SSO (OIDC/SAML)</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Backup &amp; recovery</td>
<td className="py-3 pr-4">Manual snapshots</td>
<td className="py-3 pr-4">One-click backup/restore</td>
<td className="py-3">Same + scheduled</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">AI operations</td>
<td className="py-3 pr-4">None</td>
<td className="py-3 pr-4">Intent Router + MCP Server</td>
<td className="py-3">Same</td>
</tr>
<tr className="border-b border-surface-800">
<td className="py-3 pr-4">Multi-host orchestration</td>
<td className="py-3 pr-4">Manual Swarm/K8s</td>
<td className="py-3 pr-4">Single host</td>
<td className="py-3">Swarm + Fleet Management</td>
</tr>
<tr>
<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 / license duration</td>
</tr>
</tbody>
</table>
</div>
<h2>Real-world scenarios</h2>
<h3>Home lab operator</h3>
<p>
DashCaddy is built for self-hosters, home lab operators, small teams, and administrators who want one place to deploy apps,
publish them cleanly, trust internal HTTPS, and keep service infrastructure under control.
You run Plex, Nextcloud, Home Assistant, and a dozen other services on a single NUC. With DashCaddy,
you deploy each from a template, and every service gets a clean subdomain (<code>plex.local</code>,
<code>nextcloud.local</code>) with trusted HTTPS. Service Discovery adopts containers you already
had running, so you don't need to redeploy anything. The dashboard gives you one place to see health,
restart services, and review logs.
</p>
<h2>Ownership and licensing</h2>
<h3>Small business internal tools</h3>
<p>
DashCaddy is proprietary software and intellectual property of <strong>samiahmed7777</strong>. Public-facing documentation and branding
should reflect that commercial/proprietary positioning rather than an open-source default.
Your team needs Gitea, Grafana, and a wiki behind HTTPS with role-based access. DashCaddy Free handles
deployment, DNS, and certificates. TOTP 2FA and multi-user admin ensure only authorized team members
can modify infrastructure. Audit logs track who deployed what and when.
</p>
<h3>Multi-site fleet management</h3>
<p>
You manage DashCaddy instances across three offices. Premium's Fleet Management gives you a single
dashboard to monitor all hosts, deploy services to specific sites, and enforce configuration standards.
Swarm support lets you scale a service across multiple nodes within a site.
</p>
<h2>Premium licensing</h2>
<p>
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>
<li><strong>$50</strong> 90-day license</li>
<li><strong>$70</strong> 180-day license</li>
<li><strong>$99</strong> 365-day license</li>
</ul>
<p>
Premium features include SSO (OIDC/SAML), Recipes (multi-service deployment blueprints), Docker Swarm
orchestration, and Fleet Management for multi-host coordination. The core platform is fully functional
without a license; Premium is for teams that need enterprise-grade access control and multi-site
visibility.
</p>
<h2>Next steps</h2>
<ul>
<li>
<a href="/docs/installation" className="text-brand-400 hover:text-brand-300 underline">
Installation Guide
</a> get DashCaddy running on your host with the guided installer or manual setup.
</li>
<li>
<a href="/docs/first-service" className="text-brand-400 hover:text-brand-300 underline">
Deploy Your First Service
</a> walk through deploying Plex (or any template) end-to-end.
</li>
</ul>
</DocsLayout>
<Footer />
</div>
+262 -18
View File
@@ -8,29 +8,273 @@ export default function DocsPremiumPage() {
<Navbar />
<DocsLayout
title="Premium Features"
intro="DashCaddy keeps its Premium model intentionally narrow. The core platform remains useful without a license, while Premium unlocks a small set of advanced orchestration features."
intro="DashCaddy keeps its Premium model intentionally narrow. The core platform — deployment, DNS, reverse proxy, HTTPS, monitoring, templates, service discovery, and the API — is fully useful without a license. Premium unlocks a focused set of advanced orchestration features."
>
<h2>Premium-gated features</h2>
<ul>
<li><strong>SSO</strong>: Auto-Login SSO</li>
<li><strong>Recipes</strong>: multi-container stack deployment</li>
<li><strong>Swarm</strong>: Docker Swarm multi-node orchestration</li>
</ul>
<h2>Current plan model</h2>
<ul>
<li>1 month $25</li>
<li>3 months $50</li>
<li>6 months $65</li>
<li>12 months $99</li>
</ul>
<p>
Subscriptions only, one Premium tier, one active machine at a time, 7-day grace period, cancel at period end, and no free trial.
DashCaddy&apos;s philosophy is that the day-to-day platform should be free forever. Everything you need to run
a single host the dashboard, the full template catalog, Caddy + DNS + TLS automation, real-time monitoring,
Prometheus metrics, multi-user accounts with 2FA and RBAC, the Security Center, the AI Intent Router, the MCP
Server, the REST API, and backup/restore works without a license. Premium adds four capabilities aimed at
teams and multi-host operators who need single sign-on, multi-container stacks, cluster orchestration, or
fleet-wide management.
</p>
<p>
This guide explains exactly what each Premium feature does, how it differs from the free tier, how to set it
up, and how pricing and licensing work. If you only ever run one host, you may never need Premium and
that&apos;s by design.
</p>
<h2>License behavior</h2>
<h2>Free vs Premium at a glance</h2>
<p>
DashCaddys app-side licensing is already built around an external validation and deactivation service, rather than unlimited static license reuse.
The comparison table below covers every major capability. &ldquo;Free&rdquo; means available on an unlicensed
install; &ldquo;Premium&rdquo; means the feature requires an active license.
</p>
<table>
<thead>
<tr>
<th>Capability</th>
<th>Free</th>
<th>Premium</th>
</tr>
</thead>
<tbody>
<tr><td>Dashboard &amp; web UI</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>
<tr><td>DashCA internal certificate authority</td><td></td><td></td></tr>
<tr><td>Service Discovery</td><td></td><td></td></tr>
<tr><td>Real-time monitoring + WebSocket updates</td><td></td><td></td></tr>
<tr><td>Prometheus metrics endpoint</td><td></td><td></td></tr>
<tr><td>Multi-user accounts (invites, email magic link)</td><td></td><td></td></tr>
<tr><td>TOTP 2FA &amp; RBAC roles</td><td></td><td></td></tr>
<tr><td>Encrypted credential storage</td><td></td><td></td></tr>
<tr><td>Security Center &amp; audit logging</td><td></td><td></td></tr>
<tr><td>AI Intent Router &amp; MCP Server</td><td></td><td></td></tr>
<tr><td>REST API &amp; MCP Server</td><td></td><td></td></tr>
<tr><td>Backup / restore &amp; Disaster Recovery</td><td></td><td></td></tr>
<tr><td>Internationalization (31 languages)</td><td></td><td></td></tr>
<tr><td>Plugin &amp; extension system</td><td></td><td></td></tr>
<tr><td>Smart Defaults Wizard</td><td></td><td></td></tr>
<tr><td><strong>Auto-Login SSO</strong></td><td></td><td></td></tr>
<tr><td><strong>Recipes (multi-container stacks)</strong></td><td></td><td></td></tr>
<tr><td><strong>Docker Swarm orchestration</strong></td><td></td><td></td></tr>
<tr><td><strong>Multi-Host Fleet Management</strong></td><td></td><td></td></tr>
<tr><td>Priority support</td><td></td><td></td></tr>
</tbody>
</table>
<h2>Premium feature deep dive</h2>
<h3>Auto-Login SSO</h3>
<p>
<strong>Auto-Login SSO</strong> provides single sign-on across all services published through DashCaddy, so an
authenticated DashCaddy user reaches their apps without logging in again to each one. Once you sign into the
DashCaddy dashboard, SSO forwards a signed token to participating services that auto-authenticates the session.
This creates a seamless internal portal experience ideal for teams that want one front door to every tool.
</p>
<p>
SSO integrates with services that accept a shared authentication header or token exchange. Supported targets
include apps that read a configurable auth header (common in self-hosted dashboards, wikis, and admin panels)
as well as services that expose a login callback URL. The exact wiring is per-service: in the publish dialog,
enable <strong>SSO</strong> and provide the header name or callback endpoint the target expects. DashCaddy
handles token signing, rotation, and revocation.
</p>
<p>
SSO respects your existing RBAC roles. A user with read-only access in DashCaddy will be passed through to
services as a read-only identity where the target supports role mapping. Revoking a user in DashCaddy
immediately invalidates their SSO sessions across all linked services.
</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">Note:</strong> SSO is a pass-through convenience layer, not a replacement
for per-service authentication. Services that require their own login (e.g. a database admin tool) will still
prompt unless they explicitly support header/token SSO.
</p>
</blockquote>
<h3>Recipes multi-container stacks</h3>
<p>
<strong>Recipes</strong> let you deploy multi-container application stacks as a single coordinated unit. A
Recipe bundles several templates together with pre-wired networking, shared volumes, environment variable
links, and startup ordering, so a complex stack comes up in one click instead of a dozen manual steps.
</p>
<p>
Typical Recipe stacks include an application plus its dependencies: a web app + PostgreSQL + Redis, a media
suite with its transcoder and metadata store, an analytics pipeline with a database and dashboard, or a
development environment with a code server, language runtime, and database. Each Recipe declares its components,
the network connections between them, and any secrets or config the stack needs at launch.
</p>
<p>
You can also <strong>create your own Recipes</strong>. Define the component templates, wire the internal
network (e.g. app <code>db:5432</code>), set environment variable references, and save the Recipe to your
catalog. Custom Recipes are versioned and shareable, so a team can standardize on the same stack definition
across hosts.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Deploy a Recipe via the API
curl -X POST -H "Authorization: Bearer ***" \\
-H "Content-Type: application/json" \\
-d '{"recipe":"analytics-stack","name":"analytics"}' \\
https://dashcaddy-host/api/v1/recipes/deploy`}</code></pre>
<h3>Docker Swarm multi-node orchestration</h3>
<p>
<strong>Docker Swarm</strong> support extends DashCaddy&apos;s deployment model from a single host to a cluster.
Run services across a Swarm cluster instead of one machine, with DashCaddy managing placement, replicas,
rolling updates, routing, and TLS across every node. This is the right feature when a single host can no longer
carry the load or when you need redundancy for critical services.
</p>
<p>
Multi-node setup follows Docker&apos;s standard Swarm workflow: initialize the manager
(<code>docker swarm init</code>), join workers (<code>docker swarm join --token ... &lt;manager-ip&gt;</code>),
then enable Swarm mode in DashCaddy under <strong>Settings Cluster</strong>. DashCaddy detects the cluster
and switches from single-container operations to service-level operations deploy, scale, update, and rollback
all operate on Swarm services rather than individual containers.
</p>
<p>
Routing and TLS are handled cluster-wide: Caddy&apos;s ingress mesh routes traffic to the correct node, and
certificates are issued per published hostname regardless of which node the container lands on. DashCaddy&apos;s
service discovery tracks placement changes as the scheduler rebalances containers.
</p>
<h3>Multi-Host Fleet Management</h3>
<p>
<strong>Multi-Host Fleet Management</strong> lets you manage DashCaddy deployments across multiple hosts from
one control plane. Instead of opening a separate dashboard per server, you register every host in a single
fleet view and deploy, monitor, and operate services across the entire fleet with unified visibility. This is
designed for operators running DashCaddy on several physical boxes, VPSes, or edge locations.
</p>
<p>
The fleet workflow has three parts. <strong>Register hosts</strong> by installing the DashCaddy agent on each
machine and pairing it with your control plane each host reports its resources, running services, and health.
<strong> Health probes</strong> poll every host on an interval and surface failures (container down, disk full,
cert expiring) in a unified alert feed. <strong>Deploy plans</strong> let you target a service or Recipe at a
specific host or a group of hosts, so you can place the media stack on the box with GPU and the database on the
box with SSD without switching dashboards.
</p>
<p>
Fleet Management is distinct from Swarm: Swarm orchestrates containers across a single logical cluster, while
Fleet Management orchestrates DashCaddy instances across independent hosts. You can use both together a fleet
of hosts, some of which are themselves Swarm clusters.
</p>
<h2>Pricing</h2>
<p>
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>
<tr>
<th>Duration</th>
<th>Price</th>
<th>Effective monthly rate</th>
</tr>
</thead>
<tbody>
<tr><td>30 days</td><td><strong>$20</strong></td><td>~$20.00 / month</td></tr>
<tr><td>90 days</td><td><strong>$50</strong></td><td>~$16.67 / month</td></tr>
<tr><td>180 days</td><td><strong>$70</strong></td><td>~$11.67 / month</td></tr>
<tr><td>365 days</td><td><strong>$99</strong></td><td>~$8.25 / month</td></tr>
</tbody>
</table>
<p>
The 365-day plan offers the best value at roughly $8.25/month equivalent about 59% off the 30-day rate.
All durations unlock the identical Premium feature set; only the length and per-month cost differ.
</p>
<h2>Plan terms</h2>
<ul>
<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>
<li><strong>No perpetual licenses</strong> Premium is term-based; the free tier is permanent.</li>
<li><strong>No free trial</strong> the free tier is comprehensive enough to evaluate the platform first.</li>
</ul>
<h2>License lifecycle</h2>
<p>
A license moves through a defined lifecycle from purchase to deactivation. Understanding this flow helps you
renew on time, move between hosts, and recover from validation failures.
</p>
<ol>
<li>
<strong>Purchase</strong> buy a duration from the dashboard
(<strong>Settings Licensing</strong>). You receive a license key tied to your account.
</li>
<li>
<strong>Activate</strong> on first launch with the key, DashCaddy contacts the licensing server and binds
the license to that machine. Premium features unlock immediately.
</li>
<li>
<strong>Periodic validation</strong> DashCaddy re-validates the license against the licensing server on
launch and at regular intervals thereafter. This keeps the license tied to one active machine and enables
clean deactivation.
</li>
<li>
<strong>Grace period (7 days)</strong> if the license expires or the server is unreachable, DashCaddy
enters a 7-day grace window. Your services keep running; Premium features become read-only. Renew or
reactivate during this window to restore full functionality.
</li>
<li>
<strong>Deactivate</strong> to move a license to a new host, deactivate it on the old machine from
<strong> Settings Licensing</strong>. This releases the binding so the key can be activated on the new host.
</li>
</ol>
<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">Important:</strong> The one-active-machine limit is enforced by the
licensing server. If you reinstall the OS or replace the host without deactivating first, contact support to
release the stale binding.
</p>
</blockquote>
<h2>How to subscribe</h2>
<ol>
<li>Open the dashboard and go to <strong>Settings Licensing</strong>.</li>
<li>Choose a plan duration (30 / 90 / 180 / 365 days) and complete checkout.</li>
<li>Your license key is validated automatically Premium features unlock immediately.</li>
<li>Manage renewal, reactivation, and machine deactivation from the same panel.</li>
<li>If you migrate hosts, deactivate on the old machine before activating on the new one.</li>
</ol>
<h2>Frequently asked questions</h2>
<h3>Do I lose my services if my license expires?</h3>
<p>
No. Running services are never stopped by a license expiry. During the 7-day grace period everything keeps
running; Premium features become read-only. After grace, Premium-only features are disabled but the free tier
(including all your deployed services) continues to operate.
</p>
<h3>Can I use one license on multiple hosts?</h3>
<p>
No each license is bound to one active machine at a time. To move a license, deactivate it on the current
host and activate it on the new one. For managing multiple hosts simultaneously, use <strong>Fleet Management</strong>,
which is itself a Premium feature requiring a license per host you want under centralized control.
</p>
<h3>Is there a free trial?</h3>
<p>
No. The free tier is permanent and feature-rich you can evaluate the entire core platform without paying.
Premium adds orchestration features that you likely already know you need (SSO, Recipes, Swarm, Fleet).
</p>
<h3>What happens to my Premium Recipes and Swarm services if I let the license lapse?</h3>
<p>
They keep running under the grace period and continue to run as ordinary services after that. You lose the
ability to <em>modify</em> them through Premium tooling (e.g. redeploying a Recipe or scaling a Swarm service)
until you renew, but the workloads themselves are not destroyed.
</p>
<h3>How is the license validated?</h3>
<p>
DashCaddy contacts an external licensing server on launch and periodically thereafter. The host must be able
to reach the licensing server for validation to succeed. If the server is temporarily unreachable, the grace
period covers the gap.
</p>
<p>
Ready to upgrade? Head to <strong>Settings Licensing</strong> in your dashboard, or learn more about the
platform in the <a href="/docs/overview">Product Overview</a> and <a href="/docs/integrations">Integrations</a> guides.
</p>
</DocsLayout>
<Footer />
+291 -17
View File
@@ -8,30 +8,304 @@ export default function DocsTroubleshootingPage() {
<Navbar />
<DocsLayout
title="Troubleshooting"
intro="Because DashCaddy sits across runtime, DNS, reverse proxy, certificates, and dashboard state, the fastest way to debug it is layer by layer instead of guessing."
intro="Because DashCaddy sits across runtime, DNS, reverse proxy, certificates, and dashboard state, the fastest way to debug it is layer by layer instead of guessing. This guide walks each layer with the common failures and fixes."
>
<h2>Debug order</h2>
<ol>
<li>backend process or container</li>
<li>service port reachability</li>
<li>reverse proxy route</li>
<li>DNS</li>
<li>certificate trust</li>
<li>dashboard/API visibility</li>
</ol>
<p>
DashCaddy orchestrates several independent layers a container runtime, a DNS server, a reverse proxy, a
certificate authority, and its own API and dashboard. When a service is unreachable, the failure is almost
always in exactly one of these layers while the others are healthy. This guide gives you a structured,
layer-by-layer diagnostic procedure with the exact commands to run and the fixes to apply.
</p>
<p>
The single most important habit: <strong>localize before you fix</strong>. Resist the urge to restart
everything. Use the health endpoints to narrow down which layer is broken, then dig into that layer with the
commands below. You will solve problems far faster than by reloading the whole stack.
</p>
<h2>Common failures</h2>
<h2>Health check endpoints start here</h2>
<p>
Every investigation begins with the built-in probes. They tell you whether the DashCaddy process itself is
healthy and whether its dependencies are wired up, in two seconds:
</p>
<ul>
<li>service is down even though the dashboard is reachable</li>
<li>internal HTTPS shows warnings because DashCA trust is missing</li>
<li>DNS automation fails due to API token or zone issues</li>
<li>Caddy changes are not applying because Admin API is unavailable</li>
<li>Premium features do not unlock because license validation is failing</li>
<li><code>/healthz</code> <strong>liveness</strong>. Returns 200 if the DashCaddy process is up.</li>
<li><code>/readyz</code> <strong>readiness</strong>. Returns 200 only when DashCaddy can serve traffic, including connectivity to Docker, Caddy, and DNS where configured.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Print just the HTTP status codes
curl -s -o /dev/null -w "healthz: %{http_code}\\n" https://dashcaddy-host/healthz
curl -s -o /dev/null -w "readyz: %{http_code}\\n" https://dashcaddy-host/readyz`}</code></pre>
<p>
Interpret the result:
</p>
<ul>
<li><strong>Both 200</strong> DashCaddy and its dependencies are up. The problem is downstream of the platform (the service itself, DNS, cert trust, or the client).</li>
<li><strong><code>/healthz</code> 200, <code>/readyz</code> fails</strong> the process is up but a dependency is unreachable: Docker socket, Caddy Admin API, or Technitium DNS. Read the <code>/readyz</code> body for which dependency failed.</li>
<li><strong><code>/healthz</code> fails</strong> the DashCaddy process itself is down. Check <code>docker ps</code> and <code>docker logs dashcaddy</code>.</li>
</ul>
<h2>The debug order work bottom-up</h2>
<p>
When a specific service is unreachable, walk the stack from the container outward to the client. Each step
depends on the one before it, so the first failing step is your root cause:
</p>
<ol>
<li><strong>Backend container</strong> is it running and healthy? (<code>docker ps</code>, <code>docker logs</code>)</li>
<li><strong>Backend port</strong> is the service listening and reachable on the host? (<code>curl localhost:port</code>)</li>
<li><strong>Reverse proxy route</strong> did Caddy apply the route correctly? (Caddyfile-as-Code view, Admin API)</li>
<li><strong>DNS</strong> does the hostname resolve to the right host? (<code>dig</code>, <code>nslookup</code>)</li>
<li><strong>Certificate trust</strong> does the client trust the CA? (<code>openssl s_client</code>, browser cert store)</li>
<li><strong>Dashboard / API state</strong> does DashCaddy reflect reality? (compare UI vs. actual container state)</li>
</ol>
<p>
The sections below cover each layer in detail with the commands and fixes for the most common failures.
</p>
<h2>DNS issues</h2>
<p>
DNS problems show up as &ldquo;hostname does not resolve&rdquo; or &ldquo;resolves to the wrong address.&rdquo;
Because DashCaddy uses Technitium for internal zones, the most common cause is a client using a public resolver
that does not know about your private zones.
</p>
<ul>
<li><strong>Check</strong>: is the client using Technitium as its resolver? Public resolvers (8.8.8.8, 1.1.1.1) will not resolve internal <code>.lab</code> zones. Point the client&apos;s DNS at Technitium, or use Tailscale MagicDNS / split-DNS for remote clients.</li>
<li><strong>Check</strong>: is the record present in the correct zone? DNS automation fails silently when the zone name is wrong a record in <code>lab</code> vs <code>lab.</code> is a different zone.</li>
<li><strong>Check</strong>: is the Technitium API token valid and scoped for writes? An expired or read-only token will let records appear to &ldquo;work&rdquo; in the UI but fail to actually create.</li>
<li><strong>Fix</strong>: re-run the DNS step from the service&apos;s action menu, or recreate the record manually in Technitium and let DashCaddy reconcile.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Query Technitium directly (bypass the client's resolver)
dig @technitium-host media.lab +short
nslookup media.lab technitium-host
# Check what the client's resolver returns (may differ)
dig media.lab +short
# Trace the full resolution path
dig media.lab +trace`}</code></pre>
<p>
If <code>dig @technitium-host</code> returns the right IP but <code>dig media.lab</code> does not, the client
is not using Technitium. If Technitium itself returns nothing, the record was never created check the token
and zone, then recreate it.
</p>
<h2>TLS / certificate problems</h2>
<p>
Certificate problems show up as browser warnings (<code>NET::ERR_CERT_AUTHORITY_INVALID</code>) or TLS
handshake failures. There are two distinct causes, and the fix is different for each.
</p>
<h3>Cause 1: client does not trust the internal CA</h3>
<p>
For internal (<code>.lab</code>) services, Caddy uses its internal CA and DashCA distributes the root
certificate. The root cert must be installed as a trusted CA on <strong>each client device</strong> not just
the server. Download it from the <strong>DashCA</strong> page and follow the per-platform instructions (macOS
Keychain, Windows certmgr, Linux <code>update-ca-certificates</code>, mobile profiles).
</p>
<h3>Cause 2: certificate issuance failed</h3>
<p>
If Caddy could not reach its CA at deploy time (internal CA down, or ACME unreachable for public domains), no
certificate is issued and the TLS handshake fails outright. Confirm the Caddy Admin API is reachable, then
redeploy or re-trigger TLS for the service.
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Inspect the certificate a server presents
echo | openssl s_client -connect media.lab:443 -servername media.lab 2>/dev/null \\
| openssl x509 -noout -issuer -subject -dates
# Verify the chain against a specific CA bundle
openssl s_client -connect media.lab:443 -CAfile /path/to/dashca-root.crt </dev/null`}</code></pre>
<p>
If <code>openssl s_client</code> shows the issuer is Caddy&apos;s internal CA and your browser still warns,
the root cert is not installed on that client. If <code>s_client</code> shows no certificate at all, issuance
failed check Caddy.
</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">Tip:</strong> After installing the root CA, restart the browser. Chrome
and Firefox maintain separate trust stores on some platforms Firefox may need the import done from its own
settings rather than the OS store.
</p>
</blockquote>
<h2>Reverse proxy debugging (Caddy)</h2>
<p>
If the service is up, the port is reachable, and DNS resolves, but the URL returns 502, 504, or does not route,
the problem is in the Caddy layer. DashCaddy drives Caddy through its Admin API, so two things can go wrong:
the Admin API is unreachable, or the generated config is wrong.
</p>
<ul>
<li><strong>Check</strong>: is the Caddy Admin API reachable from the DashCaddy API server? (<code>curl localhost:2019/config/</code> on the host)</li>
<li><strong>Check</strong>: does the Caddy route point at the correct upstream host:port? Use the <strong>Caddyfile-as-Code view</strong> to inspect the generated config.</li>
<li><strong>Check</strong>: Caddy logs <code>docker logs caddy</code> or your Caddy service logs for upstream connection errors and reload failures.</li>
<li><strong>Fix</strong>: re-apply the route from the service&apos;s action menu; DashCaddy reconciles the Caddy configuration atomically. If the config is invalid, DashCaddy rejects it before Caddy ever sees it.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Query the live Caddy config via the Admin API
curl -s localhost:2019/config/ | jq
# Find the route for a specific hostname
curl -s localhost:2019/config/ | jq '.. | .match? // empty | select(.host[]? | contains("media.lab"))'
# Tail Caddy logs for upstream errors
docker logs caddy --tail 50 -f`}</code></pre>
<h2>Container health</h2>
<p>
If a service shows <em>Unhealthy</em> or <em>Down</em> on the dashboard, the problem is the container itself.
Go straight to Docker.
</p>
<ul>
<li><strong>Check</strong>: <code>docker ps -a</code> is the container running, restarting, or exited?</li>
<li><strong>Check</strong>: <code>docker logs &lt;container&gt;</code> look for crash loops, missing files, bad config, or auth failures.</li>
<li><strong>Check</strong>: the container&apos;s healthcheck (if defined). DashCaddy surfaces container healthchecks in the UI; a failing healthcheck means the app is up but not ready (e.g. still migrating a database).</li>
<li><strong>Check</strong>: are volumes mounted and environment variables correct? Bad secrets (wrong DB password, missing API key) are the most common cause of immediate exits.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# List all containers including stopped ones
docker ps -a --filter "name=media"
# Tail recent logs
docker logs media --tail 100
# Inspect the healthcheck status and exit codes
docker inspect media --format '{{.State.Health.Status}} {{.State.ExitCode}}'
# Check resource usage if the container is OOM-killing
docker stats --no-stream media`}</code></pre>
<h2>Performance issues</h2>
<p>
If DashCaddy itself is slow or unresponsive, the cause is usually resource pressure on the host or an
overloaded dependency.
</p>
<ul>
<li><strong>Host resources</strong>: check CPU, memory, and disk with <code>htop</code>, <code>free -h</code>, and <code>df -h</code>. DashCaddy is lightweight, but a host running dozens of containers can starve it.</li>
<li><strong>Disk I/O</strong>: slow disks make Docker operations (deploy, inspect, logs) sluggish. Check <code>iostat -x 1</code> for high <code>%util</code>.</li>
<li><strong>Docker daemon load</strong>: a wedged Docker daemon slows every operation. <code>docker info</code> and <code>systemctl status docker</code> reveal daemon-level issues.</li>
<li><strong>DNS latency</strong>: if Technitium is overloaded or remote, every DNS operation in DashCaddy slows down. Check Technitium&apos;s own health and resource usage.</li>
<li><strong>Polling overhead</strong>: if you have many scripts polling the REST API, switch them to the WebSocket channel or Prometheus endpoint to reduce load.</li>
</ul>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Quick host health snapshot
free -h && df -h | grep -E "^/dev|Filesystem"
docker stats --no-stream
uptime`}</code></pre>
<h2>Common error messages</h2>
<p>
The table maps the most frequently seen errors to their likely cause and fix. For the full catalog of
structured error codes across all modules, see the <a href="/docs/api">API guide</a>.
</p>
<table>
<thead>
<tr>
<th>Error</th>
<th>Likely cause</th>
<th>Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>NET::ERR_CERT_AUTHORITY_INVALID</code></td>
<td>Client does not trust the DashCA root certificate</td>
<td>Install the root CA from the DashCA page on the client device</td>
</tr>
<tr>
<td><code>502 Bad Gateway</code></td>
<td>Caddy route points at a wrong/unreachable upstream port</td>
<td>Check the Caddyfile-as-Code view; fix the upstream host:port; re-apply</td>
</tr>
<tr>
<td><code>504 Gateway Timeout</code></td>
<td>Upstream is up but too slow to respond within the proxy timeout</td>
<td>Inspect container logs; increase Caddy proxy timeout if the app legitimately needs more time</td>
</tr>
<tr>
<td>Hostname does not resolve</td>
<td>Client is not using Technitium as its resolver, or the record was not created</td>
<td>Point client DNS at Technitium; verify the record exists; re-run DNS step</td>
</tr>
<tr>
<td><code>DNS_TOKEN_INVALID</code></td>
<td>Technitium API token expired or revoked</td>
<td>Regenerate the token in Technitium; update it under Settings DNS</td>
</tr>
<tr>
<td><code>PROXY_CADDY_UNREACHABLE</code></td>
<td>Caddy Admin API (localhost:2019) is down or firewalled</td>
<td>Restart Caddy; confirm the Admin API port is open to DashCaddy</td>
</tr>
<tr>
<td><code>DEPLOY_PORT_CONFLICT</code></td>
<td>Another container already holds the requested host port</td>
<td>Stop the conflicting container or choose a different port</td>
</tr>
<tr>
<td><code>LICENSE_EXPIRED</code></td>
<td>Premium license expired past the 7-day grace period</td>
<td>Renew from Settings Licensing; free-tier features remain available</td>
</tr>
<tr>
<td><code>LICENSE_MACHINE_LIMIT</code></td>
<td>License already bound to another machine</td>
<td>Deactivate on the old host before activating on the new one</td>
</tr>
<tr>
<td><code>AUTH_PERMISSION_DENIED</code></td>
<td>User/API key lacks the RBAC role for the action</td>
<td>Assign the needed role in Settings Users</td>
</tr>
<tr>
<td>WebSocket updates stall</td>
<td>A reverse proxy or firewall is dropping the WS upgrade</td>
<td>Allow WebSocket upgrades on the DashCaddy route in Caddy/firewall</td>
</tr>
<tr>
<td><code>429 Too Many Requests</code></td>
<td>API client exceeded the per-token rate limit</td>
<td>Back off and retry after <code>Retry-After</code>; switch polling to WS/Prometheus</td>
</tr>
</tbody>
</table>
<h2>Debug mode</h2>
<p>
When the standard checks do not reveal the problem, enable debug logging for verbose output from every layer.
Set the <code>LOG_LEVEL</code> environment variable to <code>debug</code> and restart DashCaddy:
</p>
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Enable debug logging (docker run)
docker run -d \\
-e LOG_LEVEL=debug \\
-v /var/run/docker.sock:/var/run/docker.sock \\
-p 3000:3000 \\
ghcr.io/dashcaddy/dashcaddy:latest
# Or in docker-compose.yml
services:
dashcaddy:
environment:
- LOG_LEVEL=debug
# Then tail the logs
docker logs dashcaddy -f --tail 200`}</code></pre>
<p>
Debug mode emits detailed logs for Docker operations, Caddy Admin API calls, DNS requests, certificate
workflows, and the AI/MCP layer. Reproduce the problem while debug logging is on, then grep the logs for the
relevant module. Disable debug mode when done it is verbose and not recommended for long-term production use.
</p>
<h2>Support resources</h2>
<p>
If you have worked through the layers above and are still stuck, the following resources can help:
</p>
<ul>
<li><strong>Integrations guide</strong> <a href="/docs/integrations">Infrastructure Integrations</a> explains what each layer expects and how to configure it.</li>
<li><strong>API error reference</strong> the <a href="/docs/api">API and Automation</a> guide lists all 80 structured error codes across 12 modules.</li>
<li><strong>Installation</strong> <a href="/docs/installation">Installation Guide</a> covers first-run setup and the Smart Defaults Wizard.</li>
<li><strong>Premium / licensing</strong> <a href="/docs/premium">Premium Features</a> covers license validation, grace periods, and machine binding.</li>
<li><strong>Priority support</strong> Premium license holders get priority support. Open a ticket from Settings Support in the dashboard.</li>
</ul>
<h2>Mindset</h2>
<p>
Most DashCaddy problems are really one dependency layer failing while the others are healthy. Debugging that dependency chain is the right way to recover quickly.
Most DashCaddy problems are really one dependency layer failing while the others are healthy. Use the health
endpoints to localize, then walk the debug order from the container outward. Fixing the right layer first is
always faster than reloading the whole stack. When in doubt, enable debug mode, reproduce the issue, and read
the logs for the failing module the answer is almost always there.
</p>
</DocsLayout>
<Footer />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

+248 -126
View File
@@ -16,79 +16,120 @@ interface FeatureSection {
title: string;
description: string;
icon: string;
badge?: string;
features: FeatureItem[];
}
export default function FeaturesPage() {
const features: FeatureSection[] = [
{
id: 'deployment',
id: 'ai-self-hosting',
title: 'AI-Powered Self-Hosting',
description:
'Talk to your homelab. DashCaddy\'s AI Intent Router understands natural language commands, and the built-in MCP Server lets AI assistants like Claude and GPT control your infrastructure directly. The first self-hosting platform built for the agentic era.',
icon: '🤖',
badge: 'New',
features: [
{ icon: '🎯', label: 'AI Intent Router — natural language commands' },
{ icon: '🔌', label: 'MCP Server for Claude & GPT control' },
{ icon: '💬', label: '"deploy Plex", "check SSL status", "restart jellyfin"' },
{ icon: '🧠', label: 'Context-aware infrastructure reasoning' },
],
},
{
id: 'app-deployment',
title: 'App Deployment',
description: 'Deploy your favorite applications instantly from our library of 50+ pre-configured Docker templates. No manual configuration needed—just click, deploy, and go live.',
description:
'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: '50+ app templates' },
{ icon: '⚙️', label: 'Auto-configuration' },
{ icon: '📦', label: '77 app templates' },
{ icon: '⚙️', label: 'Automatic configuration' },
{ icon: '🔍', label: 'Service Discovery & auto-adopt' },
{ icon: '🧙', label: 'Smart Defaults Wizard' },
],
},
{
id: 'ssl-security',
title: 'SSL & Security',
description: 'Enterprise-grade security built in. Automatic SSL certificates, TOTP 2FA, encrypted credentials, and comprehensive audit logs to track every action.',
description:
'Enterprise-grade security, baked in. Automatic SSL via Caddy\'s internal CA, TOTP 2FA with email magic link, multi-user admin with role-based access, encrypted credential vault, PII-masked audit logs, and a Security Center that unifies events from every source — with per-IP perimeter analytics for spotting attack sources.',
icon: '🔒',
features: [
{ icon: '🔐', label: 'Automatic SSL certificates' },
{ icon: '📱', label: 'TOTP 2FA authentication' },
{ icon: '🔑', label: 'Encrypted credentials' },
{ icon: '📝', label: 'Audit logs' },
{ icon: '📱', label: 'TOTP 2FA + email magic link' },
{ icon: '👥', label: 'Multi-user admin (RBAC + invites)' },
{ icon: '🔑', label: 'Encrypted credential vault' },
{ icon: '📝', label: 'PII-masked audit logging' },
{ icon: '🛡️', label: 'Security Center (multi-source events)' },
{ icon: '🌐', label: 'Perimeter analytics (per-IP, per-vhost)' },
],
},
{
id: 'dns',
title: 'DNS Management',
description: 'Manage all your app subdomains from one dashboard. Automatic DNS record creation with Technitium DNS integration makes domain management effortless.',
id: 'dns-networking',
title: 'DNS & Networking',
description:
'Full DNS automation powered by Technitium DNS, plus a Caddyfile-as-code visual builder for crafting reverse proxy rules without touching config files. Manage subdomains, routes, and proxies from a single dashboard.',
icon: '🌐',
features: [
{ icon: '✨', label: 'Automatic DNS records' },
{ icon: '🔗', label: 'Technitium integration' },
{ icon: '📋', label: 'Subdomain management' },
{ icon: '🎯', label: 'Zero DNS config' },
{ icon: '🔗', label: 'Technitium DNS integration' },
{ icon: '🏗️', label: 'Caddyfile-as-code visual builder' },
{ icon: '🔁', label: 'Reverse proxy management' },
],
},
{
id: 'monitoring',
title: 'Monitoring & Health',
description: 'Real-time visibility into your infrastructure. Monitor container health, response times, and resource usage with detailed metrics and alerts.',
id: 'monitoring-ops',
title: 'Monitoring & Operations',
description:
'Real-time visibility into every container and service. WebSocket live updates keep your dashboard in sync instantly, health checks catch problems early, and Prometheus metrics export feeds your existing observability stack.',
icon: '📊',
features: [
{ icon: '🟢', label: 'Real-time status' },
{ icon: '⏱️', label: 'Response time tracking' },
{ icon: '💾', label: 'Resource monitoring' },
{ icon: '📈', label: 'Performance metrics' },
{ icon: '🟢', label: 'Real-time health monitoring' },
{ icon: '', label: 'WebSocket live updates' },
{ icon: '📈', label: 'Prometheus metrics export' },
{ icon: '💓', label: 'Container health checks' },
{ icon: '⏱️', label: 'Response time & resource tracking' },
],
},
{
id: 'docker',
title: 'Docker Management',
description: 'Control your entire Docker environment visually. View, manage, and scale containers, access logs, and perform updates without touching the command line.',
icon: '🐳',
id: 'backup-recovery',
title: 'Backup & Recovery',
description:
'One-click backup and disaster recovery built for peace of mind. Full-system backups with checksum verification, point-in-time restore, and a disaster recovery workflow that gets you back online fast when it matters most.',
icon: '💾',
features: [
{ icon: '🎮', label: 'Container control' },
{ icon: '📜', label: 'Live logs access' },
{ icon: '🔄', label: 'Auto-updates' },
{ icon: '📊', label: 'Resource monitoring' },
{ icon: '🔙', label: 'One-click backup & restore' },
{ icon: '🆘', label: 'Disaster Recovery mode' },
{ icon: '', label: 'Full-system backup w/ checksums' },
{ icon: '⏮️', label: 'Point-in-time restore' },
],
},
{
id: 'developer-platform',
title: 'Developer Platform',
description:
'DashCaddy isn\'t just a UI—it\'s a platform. A full REST API with JSON endpoints, a plugin/extension system for custom workflows, and internationalization across 31 languages including full RTL support.',
icon: '🛠️',
features: [
{ icon: '🌐', label: 'REST API (JSON, MCP protocol)' },
{ icon: '🔌', label: 'Plugin / Extension system' },
{ icon: '🔄', label: 'Auto-update manager' },
{ icon: '🌍', label: 'i18n: EN/ES/FR/DE/AR (RTL)' },
],
},
{
id: 'premium',
title: 'Premium Features',
description: 'Advanced features for power users and production deployments. SSO integration, multi-container recipes, and Docker Swarm orchestration.',
description:
'Power features for production and multi-host deployments. Auto-Login SSO across your apps, multi-container Recipes, Docker Swarm orchestration, and Fleet Management to register remote instances with parallel health probes and multi-host deploy plans.',
icon: '⭐',
features: [
{ icon: '🔑', label: 'Auto-Login SSO', premium: true },
{ icon: '📚', label: 'Recipes (stack deployment)', premium: true },
{ icon: '🚀', label: 'Docker Swarm orchestration', premium: true },
{ icon: '📚', label: 'Recipes (multi-container stacks)', premium: true },
{ icon: '🐳', label: 'Docker Swarm orchestration', premium: true },
{ icon: '📡', label: 'Multi-Host Fleet Management', premium: true },
{ icon: '🚀', label: 'Priority support', premium: true },
],
},
@@ -105,11 +146,18 @@ export default function FeaturesPage() {
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<div className="mb-6 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-300">
<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>
AI-native self-hosting platform
</div>
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
Powerful <span className="text-brand-400">Features</span> Built In
Every Tool You Need to <span className="text-brand-400">Self-Host</span>
</h1>
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
Everything you need to manage Docker applications professionally. From deployment to monitoring, SSL to securityit's all included.
From AI-powered deployment to enterprise security, real-time monitoring, and multi-host fleet management DashCaddy brings the entire self-hosting stack under one roof.
</p>
</div>
</section>
@@ -119,12 +167,22 @@ export default function FeaturesPage() {
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="space-y-24">
{features.map((feature, idx) => (
<div key={feature.id} className={`grid grid-cols-1 lg:grid-cols-2 gap-12 items-center ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}>
<div
key={feature.id}
className={`grid grid-cols-1 lg:grid-cols-2 gap-12 items-center ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}
>
{/* Content Side */}
<div className={idx % 2 === 1 ? 'lg:order-2' : ''}>
<div className="mb-6 inline-flex rounded-lg bg-brand-500/10 p-4 text-brand-400">
<div className="text-3xl">{feature.icon}</div>
</div>
{feature.badge && (
<span className="mb-3 ml-2 inline-block align-middle text-xs px-2 py-1 bg-brand-500/20 text-brand-300 rounded-full font-semibold uppercase tracking-wide">
{feature.badge}
</span>
)}
<h2 className="text-3xl sm:text-4xl font-bold mb-4 text-surface-50">
{feature.title}
</h2>
@@ -144,7 +202,9 @@ export default function FeaturesPage() {
}`}
>
<span className="text-xl flex-shrink-0">{item.icon}</span>
<span className={`text-sm font-medium ${item.premium ? 'text-brand-300' : 'text-surface-300'}`}>
<span
className={`text-sm font-medium ${item.premium ? 'text-brand-300' : 'text-surface-300'}`}
>
{item.label}
{item.premium && (
<span className="ml-1 inline-block text-xs px-2 py-0.5 bg-brand-500/20 text-brand-300 rounded font-semibold">
@@ -164,7 +224,10 @@ export default function FeaturesPage() {
<div className="relative rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8">
<div className="space-y-4">
{feature.features.slice(0, 3).map((item, itemIdx) => (
<div key={itemIdx} className="flex items-center gap-3 p-3 bg-surface-900/50 rounded-lg border border-surface-700/30">
<div
key={itemIdx}
className="flex items-center gap-3 p-3 bg-surface-900/50 rounded-lg border border-surface-700/30"
>
<div className="w-2 h-2 rounded-full bg-brand-400" />
<span className="text-sm text-surface-300">{item.label}</span>
</div>
@@ -172,7 +235,10 @@ export default function FeaturesPage() {
</div>
<div className="mt-6 pt-6 border-t border-surface-700/30">
<div className="text-sm text-surface-500 text-center">
<span className="text-brand-400 font-semibold">{feature.features.length} features</span> included
<span className="text-brand-400 font-semibold">
{feature.features.length} features
</span>{' '}
included
</div>
</div>
</div>
@@ -184,112 +250,168 @@ export default function FeaturesPage() {
</div>
</section>
{/* Feature Comparison Grid */}
{/* AI Features Highlight */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-900 to-surface-950">
<div className="absolute inset-0 -z-10">
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[40rem] h-96 bg-brand-500/10 rounded-full blur-3xl opacity-40" />
</div>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
What's Included in <span className="text-brand-400">Free</span>
<span className="rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
The Agentic Era of Self-Hosting
</span>
<h2 className="mt-6 text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
Control Your Homelab With <span className="text-brand-400">Natural Language</span>
</h2>
<p className="text-lg text-surface-400">
Start with the free tier and upgrade anytime when you need advanced features.
<p className="text-lg text-surface-400 max-w-2xl mx-auto">
DashCaddy speaks AI natively. Type what you want, or let your favorite assistant do it.
</p>
</div>
{/* Feature Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<FeatureCard
icon="🚀"
title="One-Click Deployment"
description="Deploy from 50+ pre-configured templates with zero configuration."
/>
<FeatureCard
icon="🔒"
title="Automatic SSL"
description="Secure your apps with automatically renewed SSL certificates."
/>
<FeatureCard
icon="🌐"
title="DNS Management"
description="Automatic DNS record creation with Technitium integration."
/>
<FeatureCard
icon="📊"
title="Real-Time Monitoring"
description="Track container health, response times, and resource usage."
/>
<FeatureCard
icon="🐳"
title="Docker Control"
description="Manage containers, access logs, and updates from one dashboard."
/>
<FeatureCard
icon="🔐"
title="Security Built-In"
description="TOTP 2FA, encrypted credentials, and audit logging included."
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* AI Intent Router */}
<div className="group relative overflow-hidden rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8 transition-all duration-300 hover:border-brand-500/50 hover:shadow-lg hover:shadow-brand-500/10">
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 transition-all duration-300 pointer-events-none" />
<div className="relative z-10">
<div className="mb-4 inline-flex rounded-lg bg-brand-500/10 p-3 text-brand-400 group-hover:bg-brand-500/20 group-hover:text-brand-300 transition-all duration-300">
<div className="text-3xl">🎯</div>
</div>
<h3 className="mb-2 text-xl font-semibold text-surface-50">AI Intent Router</h3>
<p className="text-surface-300 text-sm leading-relaxed mb-6">
Skip the menus. Just type a command in plain English and DashCaddy figures out the right actions.
</p>
<div className="space-y-2">
{['"deploy Plex"', '"check SSL status"', '"restart jellyfin"', '"show failing containers"'].map(
(cmd, i) => (
<div
key={i}
className="flex items-center gap-3 rounded-lg bg-surface-900/60 border border-surface-700/30 px-3 py-2"
>
<span className="text-brand-400 font-mono text-xs select-none"></span>
<span className="text-sm text-surface-300 font-mono">{cmd}</span>
</div>
)
)}
</div>
</div>
</div>
{/* MCP Server */}
<div className="group relative overflow-hidden rounded-2xl border border-surface-700/50 bg-surface-800/50 backdrop-blur p-8 transition-all duration-300 hover:border-brand-500/50 hover:shadow-lg hover:shadow-brand-500/10">
<div className="absolute inset-0 bg-gradient-to-br from-brand-500/0 to-brand-500/0 group-hover:from-brand-500/5 group-hover:to-brand-500/10 transition-all duration-300 pointer-events-none" />
<div className="relative z-10">
<div className="mb-4 inline-flex rounded-lg bg-brand-500/10 p-3 text-brand-400 group-hover:bg-brand-500/20 group-hover:text-brand-300 transition-all duration-300">
<div className="text-3xl">🔌</div>
</div>
<h3 className="mb-2 text-xl font-semibold text-surface-50">MCP Server</h3>
<p className="text-surface-300 text-sm leading-relaxed mb-6">
DashCaddy runs as a Model Context Protocol server, so AI assistants can manage your homelab directly.
</p>
<div className="space-y-2">
{[
'Claude desktop & API',
'GPT / ChatGPT integrations',
'Any MCP-compatible client',
'Scoped, audited, secure access',
].map((item, i) => (
<div
key={i}
className="flex items-center gap-3 rounded-lg bg-surface-900/60 border border-surface-700/30 px-3 py-2"
>
<div className="w-2 h-2 rounded-full bg-brand-400" />
<span className="text-sm text-surface-300">{item}</span>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</section>
{/* Premium Section */}
{/* Free vs Premium Comparison */}
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 right-0 w-96 h-96 bg-brand-600/10 rounded-full blur-3xl opacity-20" />
</div>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="rounded-2xl border border-brand-500/30 bg-gradient-to-br from-brand-950/50 to-surface-900 p-12">
<div className="max-w-3xl">
<div className="mb-6 inline-block">
<span className="rounded-full bg-brand-500/20 px-4 py-2 text-sm font-semibold text-brand-300">
Premium Features
<div className="mb-16 text-center">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
Free vs <span className="text-brand-400">Premium</span>
</h2>
<p className="text-lg text-surface-400">
A powerful free tier for everyone. Premium unlocks multi-host, SSO, and more.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Free Column */}
<div className="rounded-2xl border border-surface-700/50 bg-surface-800/40 backdrop-blur p-8">
<div className="mb-6">
<h3 className="text-2xl font-bold text-surface-50">Free</h3>
<p className="text-sm text-surface-400 mt-1">Everything to self-host a single node.</p>
</div>
<ul className="space-y-3">
{[
'77 one-click app templates',
'Automatic SSL certificates',
'Technitium DNS automation',
'Reverse proxy + Caddyfile builder',
'Real-time monitoring & WebSocket updates',
'TOTP 2FA + multi-user admin',
'Encrypted credential vault',
'Audit logging',
'Security Center',
'One-click backup & restore',
'AI Intent Router + MCP Server',
'REST API (350+ endpoints)',
'Plugin / Extension system',
'i18n (31 languages, RTL)',
'Prometheus metrics export',
].map((item, i) => (
<li key={i} className="flex items-start gap-3">
<svg className="mt-0.5 h-5 w-5 flex-shrink-0 text-brand-400" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
<span className="text-sm text-surface-300">{item}</span>
</li>
))}
</ul>
</div>
{/* Premium Column */}
<div className="relative rounded-2xl border border-brand-500/40 bg-gradient-to-br from-brand-950/40 to-surface-900 p-8">
<div className="absolute -top-3 right-6">
<span className="rounded-full bg-brand-500 px-3 py-1 text-xs font-semibold text-white uppercase tracking-wide">
Premium
</span>
</div>
<h2 className="text-3xl sm:text-4xl font-bold mb-6 text-surface-50">
Level Up With <span className="text-brand-400">Premium</span>
</h2>
<p className="text-lg text-surface-300 mb-8">
Get advanced features designed for power users and production deployments. Auto-Login SSO, stack recipes, Docker Swarm orchestration, and priority support.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">🔑</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Auto-Login SSO</h3>
<p className="text-sm text-surface-400">Deploy apps with automatic single sign-on integration.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">📚</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Recipes</h3>
<p className="text-sm text-surface-400">Deploy multi-container stacks with one click.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0">🚀</span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Docker Swarm</h3>
<p className="text-sm text-surface-400">Orchestrate multi-node clusters effortlessly.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 rounded-lg bg-surface-800/50 border border-surface-700/30">
<span className="text-2xl flex-shrink-0"></span>
<div>
<h3 className="font-semibold text-surface-50 mb-1">Priority Support</h3>
<p className="text-sm text-surface-400">Get faster responses from our support team.</p>
</div>
</div>
<div className="mb-6">
<h3 className="text-2xl font-bold text-surface-50">Premium</h3>
<p className="text-sm text-surface-400 mt-1">For production, multi-host, and teams.</p>
</div>
<p className="text-sm text-brand-300 mb-4 font-medium">
Everything in Free, plus:
</p>
<ul className="space-y-3">
{[
'Auto-Login SSO for deployed apps',
'Recipes — multi-container stack deployment',
'Docker Swarm orchestration',
'Multi-Host Fleet Management (remote instances, parallel health probes, multi-host deploy plans)',
'Disaster Recovery (full-system backup w/ checksums)',
'Priority support',
].map((item, i) => (
<li key={i} className="flex items-start gap-3">
<svg className="mt-0.5 h-5 w-5 flex-shrink-0 text-brand-400" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-sm text-surface-200">{item}</span>
</li>
))}
</ul>
<Link
href="/pricing"
className="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-6 py-3 font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
className="mt-8 inline-flex items-center gap-2 rounded-lg bg-brand-500 px-6 py-3 font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
>
Explore Premium Plans
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
@@ -301,7 +423,7 @@ export default function FeaturesPage() {
</div>
</section>
{/* FAQ Section */}
{/* FAQ CTA Section */}
<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-3xl px-4 sm:px-6 lg:px-8">
<div className="mb-12 text-center">
+75 -13
View File
@@ -1,5 +1,9 @@
@import "tailwindcss";
/* ── Theme system: light is default, .dark activates dark mode ── */
/* Surface colors are remapped so bg-surface-950 = page background, */
/* text-surface-50 = main text — in BOTH themes. The scale flips. */
@theme inline {
--color-brand-50: #eef6ff;
--color-brand-100: #d9eaff;
@@ -13,22 +17,55 @@
--color-brand-900: #18378f;
--color-brand-950: #142357;
--color-surface-50: #f8fafc;
--color-surface-100: #f1f5f9;
--color-surface-200: #e2e8f0;
--color-surface-300: #cbd5e1;
--color-surface-400: #94a3b8;
--color-surface-500: #64748b;
--color-surface-600: #475569;
--color-surface-700: #334155;
--color-surface-800: #1e293b;
--color-surface-900: #0f172a;
--color-surface-950: #020617;
/* Surface scale — mapped to semantic vars that flip with theme */
--color-surface-50: var(--s-50);
--color-surface-100: var(--s-100);
--color-surface-200: var(--s-200);
--color-surface-300: var(--s-300);
--color-surface-400: var(--s-400);
--color-surface-500: var(--s-500);
--color-surface-600: var(--s-600);
--color-surface-700: var(--s-700);
--color-surface-800: var(--s-800);
--color-surface-900: var(--s-900);
--color-surface-950: var(--s-950);
--font-sans: "Inter", system-ui, -apple-system, sans-serif;
--font-mono: "JetBrains Mono", "Fira Code", monospace;
}
/* ── Light theme (default) ── */
:root {
--s-50: #020617; /* main text — near black */
--s-100: #0f172a; /* heading text */
--s-200: #1e293b; /* strong text */
--s-300: #475569; /* secondary text */
--s-400: #64748b; /* muted text */
--s-500: #94a3b8; /* very muted */
--s-600: #cbd5e1; /* faint text */
--s-700: #e2e8f0; /* borders */
--s-800: #f1f5f9; /* card backgrounds */
--s-900: #f8fafc; /* secondary page bg */
--s-950: #ffffff; /* page background — white */
color-scheme: light;
}
/* ── Dark theme ── */
.dark {
--s-50: #f8fafc; /* main text — near white */
--s-100: #f1f5f9; /* heading text */
--s-200: #e2e8f0; /* strong text */
--s-300: #cbd5e1; /* secondary text */
--s-400: #94a3b8; /* muted text */
--s-500: #64748b; /* very muted */
--s-600: #475569; /* faint text */
--s-700: #334155; /* borders */
--s-800: #1e293b; /* card backgrounds */
--s-900: #0f172a; /* secondary page bg */
--s-950: #020617; /* page background — near black */
color-scheme: dark;
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
@@ -39,6 +76,7 @@ body {
background-color: var(--color-surface-950);
color: var(--color-surface-100);
font-family: var(--font-sans);
transition: background-color 0.3s ease, color 0.3s ease;
}
::selection {
@@ -54,9 +92,18 @@ body {
}
.glass-card {
background: rgba(30, 41, 59, 0.5);
background: var(--glass-bg);
backdrop-filter: blur(16px);
border: 1px solid rgba(148, 163, 184, 0.1);
border: 1px solid var(--glass-border);
}
:root {
--glass-bg: rgba(241, 245, 249, 0.5);
--glass-border: rgba(0, 0, 0, 0.08);
}
.dark {
--glass-bg: rgba(30, 41, 59, 0.5);
--glass-border: rgba(148, 163, 184, 0.1);
}
.glow-border {
@@ -71,3 +118,18 @@ body {
transparent
);
}
/* Theme toggle button */
.theme-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
transition: background-color 0.2s, color 0.2s;
cursor: pointer;
}
.theme-toggle:hover {
background-color: var(--color-surface-800);
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Some files were not shown because too many files have changed in this diff Show More