Compare commits
32
Commits
c5d7d99852
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03b8242467 | ||
|
|
ac2feb5ab7 | ||
|
|
e58130756e | ||
|
|
e2b4ce4447 | ||
|
|
414c962d3c | ||
|
|
dcb8eeda4e | ||
|
|
81493a9076 | ||
|
|
fbb6db24d2 | ||
|
|
e51419a302 | ||
|
|
5905176a74 | ||
|
|
8c4aeb6065 | ||
|
|
843d85519f | ||
|
|
7b910aa913 | ||
|
|
8ba4c1b479 | ||
|
|
09113f1c19 | ||
|
|
b16671ccee | ||
|
|
8998c585d0 | ||
|
|
be8953fe3b | ||
|
|
6165b8e464 | ||
|
|
8579a29185 | ||
|
|
15eabc4dc0 | ||
|
|
7550ae09d8 | ||
|
|
0bc10c373b | ||
|
|
9b10f401f4 | ||
|
|
d4be17052c | ||
|
|
5b9c88ab5a | ||
|
|
0fa99b1490 | ||
|
|
ee4066f19c | ||
|
|
a3648f66d8 | ||
|
|
34704066c3 | ||
|
|
3b2023e97a | ||
|
|
69c2179a43 |
@@ -0,0 +1,31 @@
|
|||||||
|
# Stripe Configuration
|
||||||
|
# Get your keys from https://dashboard.stripe.com/apikeys
|
||||||
|
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 (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_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
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
+6
-7
@@ -1,7 +1,6 @@
|
|||||||
import type { NextConfig } from "next";
|
const nextConfig = {
|
||||||
|
output: "export",
|
||||||
const nextConfig: NextConfig = {
|
trailingSlash: true,
|
||||||
/* config options here */
|
images: { unoptimized: true }
|
||||||
};
|
};
|
||||||
|
export default nextConfig;
|
||||||
export default nextConfig;
|
|
||||||
|
|||||||
Generated
+6599
-6599
File diff suppressed because it is too large
Load Diff
@@ -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 |
Executable
+14
@@ -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/"
|
||||||
+204
-197
@@ -1,197 +1,204 @@
|
|||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="flex-1">
|
<main className="flex-1">
|
||||||
{/* Hero */}
|
{/* Hero */}
|
||||||
<section className="relative pt-32 pb-20 px-4">
|
<section className="relative pt-32 pb-20 px-4">
|
||||||
<div className="absolute inset-0 hero-glow pointer-events-none" />
|
<div className="absolute inset-0 hero-glow pointer-events-none" />
|
||||||
<div className="max-w-4xl mx-auto text-center relative z-10">
|
<div className="max-w-4xl mx-auto text-center relative z-10">
|
||||||
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
|
<h1 className="text-4xl md:text-5xl font-bold text-white mb-6">
|
||||||
Built for the{" "}
|
Built for the{" "}
|
||||||
<span className="gradient-text">Self-Hosting Community</span>
|
<span className="gradient-text">Self-Hosting Community</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
|
<p className="text-xl text-surface-300 max-w-2xl mx-auto">
|
||||||
DashCaddy was born from the frustration of managing dozens of
|
DashCaddy was born from the frustration of managing dozens of
|
||||||
Docker containers, SSL certificates, and DNS records by hand. We
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Story */}
|
{/* Story */}
|
||||||
<section className="py-20 px-4">
|
<section className="py-20 px-4">
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h2 className="text-2xl font-bold text-white mb-6">Our Story</h2>
|
<h2 className="text-2xl font-bold text-white mb-6">Our Story</h2>
|
||||||
<div className="space-y-4 text-surface-300 leading-relaxed">
|
<div className="space-y-4 text-surface-300 leading-relaxed">
|
||||||
<p>
|
<p>
|
||||||
Self-hosting is powerful. You own your data, you control your
|
Self-hosting is powerful. You own your data, you control your
|
||||||
infrastructure, and you're not at the mercy of SaaS
|
infrastructure, and you're not at the mercy of SaaS
|
||||||
providers who can change their terms, raise prices, or shut down
|
providers who can change their terms, raise prices, or shut down
|
||||||
overnight. But let's be honest — it can also be a
|
overnight. But let's be honest — it can also be a
|
||||||
pain.
|
pain.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Every new service means editing Caddyfiles, creating DNS
|
Every new service means editing Caddyfiles, creating DNS
|
||||||
records, configuring SSL certificates, writing Docker Compose
|
records, configuring SSL certificates, writing Docker Compose
|
||||||
files, and hoping everything plays nicely together. Multiply that
|
files, and hoping everything plays nicely together. Multiply that
|
||||||
by 20, 30, or 50 services, and you've got a full-time
|
by 20, 30, or 50 services, and you've got a full-time
|
||||||
operations job on your hands.
|
operations job on your hands.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
DashCaddy was built to solve this. One click to deploy an app.
|
DashCaddy was built to solve this. One click to deploy an app.
|
||||||
SSL, DNS, and reverse proxy configuration happen automatically.
|
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
|
||||||
goes wrong, you know about it immediately — not when a
|
everything. Natural language AI commands so you can just say
|
||||||
family member texts you that Plex is down.
|
“deploy Plex” and it happens. And when something
|
||||||
</p>
|
goes wrong, you know about it immediately — not when a
|
||||||
<p>
|
family member texts you that Plex is down.
|
||||||
We believe self-hosting should be accessible to everyone, not
|
</p>
|
||||||
just people who enjoy writing YAML at 2 AM. DashCaddy makes it
|
<p>
|
||||||
beautiful and effortless.
|
We believe self-hosting should be accessible to everyone, not
|
||||||
</p>
|
just people who enjoy writing YAML at 2 AM. DashCaddy makes it
|
||||||
</div>
|
beautiful, effortless, and intelligent.
|
||||||
</div>
|
</p>
|
||||||
</section>
|
</div>
|
||||||
|
</div>
|
||||||
{/* Values */}
|
</section>
|
||||||
<section className="py-20 px-4 border-t border-surface-800">
|
|
||||||
<div className="max-w-5xl mx-auto">
|
{/* Values */}
|
||||||
<h2 className="text-2xl font-bold text-white mb-12 text-center">
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
What We Believe In
|
<div className="max-w-5xl mx-auto">
|
||||||
</h2>
|
<h2 className="text-2xl font-bold text-white mb-12 text-center">
|
||||||
<div className="grid md:grid-cols-3 gap-8">
|
What We Believe In
|
||||||
{[
|
</h2>
|
||||||
{
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
icon: "🔓",
|
{[
|
||||||
title: "Open Core",
|
{
|
||||||
description:
|
icon: "🔒",
|
||||||
"The core of DashCaddy is free and always will be. Premium features fund development, but the essentials are open to everyone.",
|
title: "Proprietary & Polished",
|
||||||
},
|
description:
|
||||||
{
|
"DashCaddy is proprietary software built with care. The core platform is free to use, with Premium features for advanced orchestration.",
|
||||||
icon: "🏠",
|
},
|
||||||
title: "Your Data, Your Server",
|
{
|
||||||
description:
|
icon: "🏠",
|
||||||
"DashCaddy runs entirely on your hardware. No cloud dependency, no telemetry, no phoning home. Your data never leaves your network.",
|
title: "Your Data, Your Server",
|
||||||
},
|
description:
|
||||||
{
|
"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",
|
{
|
||||||
description:
|
icon: "🤖",
|
||||||
"We use proven technologies — Caddy, Docker, Node.js. No bleeding-edge frameworks that break every six months. Stable, reliable, boring (in the best way).",
|
title: "AI-Native",
|
||||||
},
|
description:
|
||||||
].map((value) => (
|
"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.",
|
||||||
<div
|
},
|
||||||
key={value.title}
|
].map((value) => (
|
||||||
className="glass-card rounded-xl p-8 text-center"
|
<div
|
||||||
>
|
key={value.title}
|
||||||
<div className="text-4xl mb-4">{value.icon}</div>
|
className="glass-card rounded-xl p-8 text-center"
|
||||||
<h3 className="text-lg font-semibold text-white mb-3">
|
>
|
||||||
{value.title}
|
<div className="text-4xl mb-4">{value.icon}</div>
|
||||||
</h3>
|
<h3 className="text-lg font-semibold text-white mb-3">
|
||||||
<p className="text-surface-400">{value.description}</p>
|
{value.title}
|
||||||
</div>
|
</h3>
|
||||||
))}
|
<p className="text-surface-400">{value.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
</section>
|
</div>
|
||||||
|
</div>
|
||||||
{/* Tech Stack */}
|
</section>
|
||||||
<section className="py-20 px-4 border-t border-surface-800">
|
|
||||||
<div className="max-w-5xl mx-auto">
|
{/* Tech Stack */}
|
||||||
<h2 className="text-2xl font-bold text-white mb-12 text-center">
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
Built With
|
<div className="max-w-5xl mx-auto">
|
||||||
</h2>
|
<h2 className="text-2xl font-bold text-white mb-12 text-center">
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
Built With
|
||||||
{[
|
</h2>
|
||||||
{
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||||
name: "Caddy",
|
{[
|
||||||
role: "Reverse Proxy & SSL",
|
{ name: "Caddy", role: "Reverse Proxy & SSL", icon: "🔒" },
|
||||||
icon: "🔒",
|
{ name: "Docker", role: "Container Runtime", icon: "🐳" },
|
||||||
},
|
{ name: "Node.js", role: "API Backend", icon: "🟢" },
|
||||||
{
|
{ name: "Technitium", role: "DNS Server", icon: "🌐" },
|
||||||
name: "Docker",
|
{ name: "Express", role: "REST API + WebSocket", icon: "⚡" },
|
||||||
role: "Container Runtime",
|
{ name: "MCP", role: "AI Protocol Layer", icon: "🤖" },
|
||||||
icon: "🐳",
|
{ name: "Stripe", role: "Payment Processing", icon: "💳" },
|
||||||
},
|
{ name: "Jest", role: "Testing (2,500+ tests)", icon: "✅" },
|
||||||
{
|
].map((tech) => (
|
||||||
name: "Node.js",
|
<div
|
||||||
role: "API Backend",
|
key={tech.name}
|
||||||
icon: "🟢",
|
className="glass-card rounded-xl p-6 text-center hover:border-brand-500/30 transition-colors"
|
||||||
},
|
>
|
||||||
{
|
<div className="text-3xl mb-3">{tech.icon}</div>
|
||||||
name: "Technitium",
|
<div className="font-semibold text-white">{tech.name}</div>
|
||||||
role: "DNS Server",
|
<div className="text-sm text-surface-400">{tech.role}</div>
|
||||||
icon: "🌐",
|
</div>
|
||||||
},
|
))}
|
||||||
].map((tech) => (
|
</div>
|
||||||
<div
|
</div>
|
||||||
key={tech.name}
|
</section>
|
||||||
className="glass-card rounded-xl p-6 text-center hover:border-brand-500/30 transition-colors"
|
|
||||||
>
|
{/* Stats */}
|
||||||
<div className="text-3xl mb-3">{tech.icon}</div>
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
<div className="font-semibold text-white">{tech.name}</div>
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="text-sm text-surface-400">{tech.role}</div>
|
<h2 className="text-2xl font-bold text-white mb-12 text-center">
|
||||||
</div>
|
By the Numbers
|
||||||
))}
|
</h2>
|
||||||
</div>
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||||
</div>
|
{[
|
||||||
</section>
|
{ value: "77", label: "App Templates" },
|
||||||
|
{ value: "2,500+", label: "Test Cases" },
|
||||||
{/* Contact / Support */}
|
{ value: "350+", label: "API Endpoints" },
|
||||||
<section className="py-20 px-4 border-t border-surface-800">
|
{ value: "31", label: "Languages" },
|
||||||
<div className="max-w-3xl mx-auto text-center">
|
].map((stat) => (
|
||||||
<h2 className="text-2xl font-bold text-white mb-6">Get In Touch</h2>
|
<div key={stat.label} className="text-center">
|
||||||
<p className="text-surface-300 mb-8">
|
<div className="text-4xl font-bold text-brand-400 mb-2">
|
||||||
Have questions, feedback, or want to contribute? We'd love to
|
{stat.value}
|
||||||
hear from you.
|
</div>
|
||||||
</p>
|
<p className="text-sm text-surface-400">{stat.label}</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
</div>
|
||||||
<a
|
))}
|
||||||
href="mailto:support@dashcaddy.net"
|
</div>
|
||||||
className="px-6 py-3 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-medium transition-colors"
|
</div>
|
||||||
>
|
</section>
|
||||||
Email Us
|
|
||||||
</a>
|
{/* Contact / Support */}
|
||||||
<a
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
href="#"
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
<h2 className="text-2xl font-bold text-white mb-6">Get In Touch</h2>
|
||||||
>
|
<p className="text-surface-300 mb-8">
|
||||||
Join Discord
|
Have questions or feedback? We'd love to hear from you.
|
||||||
</a>
|
</p>
|
||||||
<a
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
href="#"
|
<a
|
||||||
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
href="mailto:support@dashcaddy.net"
|
||||||
>
|
className="px-6 py-3 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-medium transition-colors"
|
||||||
GitHub
|
>
|
||||||
</a>
|
Email Us
|
||||||
</div>
|
</a>
|
||||||
</div>
|
<Link
|
||||||
</section>
|
href="/docs"
|
||||||
|
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||||
{/* CTA */}
|
>
|
||||||
<section className="py-20 px-4 border-t border-surface-800">
|
Read the Docs
|
||||||
<div className="max-w-3xl mx-auto text-center">
|
</Link>
|
||||||
<h2 className="text-3xl font-bold text-white mb-4">
|
</div>
|
||||||
Ready to simplify your homelab?
|
</div>
|
||||||
</h2>
|
</section>
|
||||||
<p className="text-surface-300 mb-8">
|
|
||||||
Start with the free tier. Upgrade when you're ready.
|
{/* CTA */}
|
||||||
</p>
|
<section className="py-20 px-4 border-t border-surface-800">
|
||||||
<Link
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
href="/pricing"
|
<h2 className="text-3xl font-bold text-white mb-4">
|
||||||
className="inline-block px-8 py-4 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-semibold text-lg transition-colors"
|
Ready to simplify your homelab?
|
||||||
>
|
</h2>
|
||||||
View Pricing
|
<p className="text-surface-300 mb-8">
|
||||||
</Link>
|
Install DashCaddy and start deploying services today. Free forever.
|
||||||
</div>
|
</p>
|
||||||
</section>
|
<Link
|
||||||
</main>
|
href="/pricing"
|
||||||
<Footer />
|
className="inline-block px-8 py-4 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-semibold text-lg transition-colors"
|
||||||
</>
|
>
|
||||||
);
|
View Pricing
|
||||||
}
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,74 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import Stripe from "stripe";
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
return NextResponse.json({ error: 'Checkout disabled for static export' }, { status: 503 });
|
||||||
apiVersion: "2026-03-25.dahlia",
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const PRICE_IDS: Record<string, string | undefined> = {
|
|
||||||
monthly: process.env.STRIPE_PRICE_MONTHLY,
|
|
||||||
yearly: process.env.STRIPE_PRICE_YEARLY,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const body = await request.json();
|
|
||||||
const { plan, email } = body;
|
|
||||||
|
|
||||||
if (!plan || !PRICE_IDS[plan]) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Invalid plan. Choose 'monthly' or 'yearly'." },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const priceId = PRICE_IDS[plan];
|
|
||||||
if (!priceId) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Price not configured. Please contact support." },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const appUrl = process.env.NEXT_PUBLIC_APP_URL || "https://dashcaddy.net";
|
|
||||||
|
|
||||||
const sessionParams: Stripe.Checkout.SessionCreateParams = {
|
|
||||||
mode: "subscription",
|
|
||||||
payment_method_types: ["card"],
|
|
||||||
line_items: [
|
|
||||||
{
|
|
||||||
price: priceId,
|
|
||||||
quantity: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
success_url: `${appUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
||||||
cancel_url: `${appUrl}/pricing`,
|
|
||||||
allow_promotion_codes: true,
|
|
||||||
billing_address_collection: "required",
|
|
||||||
subscription_data: {
|
|
||||||
trial_period_days: 14,
|
|
||||||
metadata: {
|
|
||||||
plan,
|
|
||||||
source: "dashcaddy-website",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
plan,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pre-fill email if provided
|
|
||||||
if (email) {
|
|
||||||
sessionParams.customer_email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await stripe.checkout.sessions.create(sessionParams);
|
|
||||||
|
|
||||||
return NextResponse.json({ url: session.url });
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Stripe checkout error:", error);
|
|
||||||
const message =
|
|
||||||
error instanceof Error ? error.message : "Internal server error";
|
|
||||||
return NextResponse.json({ error: message }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
return NextResponse.json({ error: 'Webhooks disabled for static export' }, { status: 503 });
|
||||||
|
}
|
||||||
@@ -1,99 +1,4 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import Stripe from "stripe";
|
export async function POST(request: NextRequest) {
|
||||||
|
return NextResponse.json({ error: 'disabled' }, { status: 503 });
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
}
|
||||||
apiVersion: "2026-03-25.dahlia",
|
|
||||||
});
|
|
||||||
|
|
||||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const body = await request.text();
|
|
||||||
const signature = request.headers.get("stripe-signature");
|
|
||||||
|
|
||||||
if (!signature) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Missing stripe-signature header" },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let event: Stripe.Event;
|
|
||||||
|
|
||||||
try {
|
|
||||||
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
|
|
||||||
} catch (err) {
|
|
||||||
const message = err instanceof Error ? err.message : "Unknown error";
|
|
||||||
console.error(`Webhook signature verification failed: ${message}`);
|
|
||||||
return NextResponse.json({ error: message }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch (event.type) {
|
|
||||||
case "checkout.session.completed": {
|
|
||||||
const session = event.data.object as Stripe.Checkout.Session;
|
|
||||||
console.log("Checkout completed:", {
|
|
||||||
sessionId: session.id,
|
|
||||||
customerEmail: session.customer_email,
|
|
||||||
plan: session.metadata?.plan,
|
|
||||||
subscriptionId: session.subscription,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Generate and deliver license key to customer
|
|
||||||
// This is where you'd:
|
|
||||||
// 1. Generate a DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX license code
|
|
||||||
// 2. Store it in your database
|
|
||||||
// 3. Email it to the customer
|
|
||||||
// 4. Associate it with the Stripe subscription ID
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "customer.subscription.updated": {
|
|
||||||
const subscription = event.data.object as Stripe.Subscription;
|
|
||||||
console.log("Subscription updated:", {
|
|
||||||
subscriptionId: subscription.id,
|
|
||||||
status: subscription.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Update license expiration based on subscription status
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "customer.subscription.deleted": {
|
|
||||||
const subscription = event.data.object as Stripe.Subscription;
|
|
||||||
console.log("Subscription cancelled:", {
|
|
||||||
subscriptionId: subscription.id,
|
|
||||||
status: subscription.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Deactivate/expire the license key
|
|
||||||
// The DashCaddy instance will gracefully downgrade to free tier
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "invoice.payment_failed": {
|
|
||||||
const invoice = event.data.object as Stripe.Invoice;
|
|
||||||
console.log("Payment failed:", {
|
|
||||||
invoiceId: invoice.id,
|
|
||||||
customerEmail: invoice.customer_email,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Notify customer about failed payment
|
|
||||||
// Consider a grace period before deactivating license
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.log(`Unhandled event type: ${event.type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ received: true });
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Webhook handler error:", error);
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Webhook handler failed" },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,344 @@
|
|||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import DocsLayout from '@/components/docs/DocsLayout';
|
||||||
|
|
||||||
|
export default function DocsApiPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
<DocsLayout
|
||||||
|
title="API and Automation"
|
||||||
|
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."
|
||||||
|
>
|
||||||
|
<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 (“restart the media
|
||||||
|
server”, “is postgres up?”, “deploy redis”) 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: “deploy the postgres template as <code>db</code> on <code>db.lab</code>”,
|
||||||
|
“list all unhealthy services”, “rotate the TLS cert for <code>wiki.lab</code>”,
|
||||||
|
“create a DNS record for <code>api.lab</code> pointing at 10.0.0.5”.
|
||||||
|
</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'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's tools automatically and can invoke them in response to
|
||||||
|
your requests — “ask DashCaddy which services are down”, “have DashCaddy deploy Grafana”,
|
||||||
|
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 & 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 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>
|
||||||
|
<p>
|
||||||
|
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 />
|
||||||
|
</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 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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "actual-budget",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "budget",
|
||||||
|
"port": 5006
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Actual Budget on my home host and expose it at budget.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "adminer",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "adminer",
|
||||||
|
"port": 8087
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Adminer on my home host and expose it at adminer.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "airsonic",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "airsonic",
|
||||||
|
"port": 4040
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Airsonic Advanced on my home host and expose it at airsonic.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "audiobookshelf",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "audiobooks",
|
||||||
|
"port": 13378,
|
||||||
|
"mediaPath": "/media/audiobooks"
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Audiobookshelf on my home host and expose it at audiobooks.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "authentik",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "auth",
|
||||||
|
"port": 9010
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Authentik on my home host and expose it at auth.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "bazarr",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "bazarr",
|
||||||
|
"port": 6767
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Bazarr on my home host and expose it at bazarr.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "bind9",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "dns2",
|
||||||
|
"port": 953
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy BIND9 DNS Server on my home host and expose it at dns2.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "bookstack",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "wiki",
|
||||||
|
"port": 8091
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy BookStack on my home host and expose it at wiki.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "calibre-web",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "books",
|
||||||
|
"port": 8083,
|
||||||
|
"mediaPath": "/media/books"
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Calibre-Web on my home host and expose it at books.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "changedetection",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "watch",
|
||||||
|
"port": 5001
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Change Detection on my home host and expose it at watch.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "coredns",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "dns4",
|
||||||
|
"port": 53
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy CoreDNS on my home host and expose it at dns4.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "crowdsec",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "crowdsec",
|
||||||
|
"port": 8091
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy CrowdSec on my home host and expose it at crowdsec.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "dashca",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "ca",
|
||||||
|
"port": 32400
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy DashCA on my home host and expose it at ca.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "digital-clock",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "digital-clock",
|
||||||
|
"port": 32400
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Digital Clock on my home host and expose it at digital-clock.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "dozzle",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "logs",
|
||||||
|
"port": 8088
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Dozzle on my home host and expose it at logs.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "drone",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "drone",
|
||||||
|
"port": 8090
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Drone CI on my home host and expose it at drone.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "emby",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "emby",
|
||||||
|
"port": 8096,
|
||||||
|
"mediaPath": "/media"
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Emby on my home host and expose it at emby.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "excalidraw",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "draw",
|
||||||
|
"port": 8086
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Excalidraw on my home host and expose it at draw.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "filebrowser",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "files",
|
||||||
|
"port": 8085
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy FileBrowser on my home host and expose it at files.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "gitea",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "gitea",
|
||||||
|
"port": 3005
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Gitea on my home host and expose it at gitea.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "grafana",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "grafana",
|
||||||
|
"port": 3000
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Grafana on my home host and expose it at grafana.sami" }'</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/{appId}/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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "homarr",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "homarr",
|
||||||
|
"port": 7575
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Homarr on my home host and expose it at homarr.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "homeassistant",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "home",
|
||||||
|
"port": 8123
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Home Assistant on my home host and expose it at home.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "homepage",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "dashboard",
|
||||||
|
"port": 3008
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Homepage on my home host and expose it at dashboard.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "immich",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "photos",
|
||||||
|
"port": 2283
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Immich on my home host and expose it at photos.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "it-tools",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "tools",
|
||||||
|
"port": 8087
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy IT Tools on my home host and expose it at tools.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "jdownloader",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "jdownloader",
|
||||||
|
"port": 5800
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy JDownloader 2 on my home host and expose it at jdownloader.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "jellyfin",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "jellyfin",
|
||||||
|
"port": 8096,
|
||||||
|
"mediaPath": "/media"
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Jellyfin on my home host and expose it at jellyfin.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "jenkins",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "jenkins",
|
||||||
|
"port": 8089
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Jenkins on my home host and expose it at jenkins.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "kavita",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "kavita",
|
||||||
|
"port": 5004,
|
||||||
|
"mediaPath": "/media/reading"
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Kavita on my home host and expose it at kavita.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "komga",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "komga",
|
||||||
|
"port": 25600,
|
||||||
|
"mediaPath": "/media/comics"
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Komga on my home host and expose it at komga.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "lidarr",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "lidarr",
|
||||||
|
"port": 8686
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Lidarr on my home host and expose it at lidarr.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "mailserver",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "mail",
|
||||||
|
"port": 25
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Docker Mailserver on my home host and expose it at mail.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "matrix",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "matrix",
|
||||||
|
"port": 8008
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Matrix Synapse on my home host and expose it at matrix.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "mealie",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "mealie",
|
||||||
|
"port": 9925
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Mealie on my home host and expose it at mealie.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "minecraft",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "mc",
|
||||||
|
"port": 25565
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Minecraft Server on my home host and expose it at mc.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "mongodb",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "mongo",
|
||||||
|
"port": 27017
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy MongoDB on my home host and expose it at mongo.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "navidrome",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "music",
|
||||||
|
"port": 4533
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Navidrome on my home host and expose it at music.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "nextcloud",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "cloud",
|
||||||
|
"port": 8080
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Nextcloud on my home host and expose it at cloud.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "nodered",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "nodered",
|
||||||
|
"port": 1880
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Node-RED on my home host and expose it at nodered.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "nzbget",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "nzbget",
|
||||||
|
"port": 6789
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy NZBGet on my home host and expose it at nzbget.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "outline",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "outline",
|
||||||
|
"port": 3006
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Outline on my home host and expose it at outline.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "paperless-ngx",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "paperless",
|
||||||
|
"port": 8095
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Paperless-ngx on my home host and expose it at paperless.sami" }'</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 <container> 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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "photoprism",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "gallery",
|
||||||
|
"port": 2342
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy PhotoPrism on my home host and expose it at gallery.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "pihole",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "pihole",
|
||||||
|
"port": 80
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Pi-hole on my home host and expose it at pihole.sami" }'</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/{appId}/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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "plex",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "plex",
|
||||||
|
"port": 32400,
|
||||||
|
"mediaPath": "/media",
|
||||||
|
"plexClaimToken": "<get fresh token from https://plex.tv/claim>"
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Plex on my home host and expose it at plex.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "portainer",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "portainer",
|
||||||
|
"port": 9000
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Portainer on my home host and expose it at portainer.sami" }'</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/{appId}/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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "postgres",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "postgres",
|
||||||
|
"port": 5432
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy PostgreSQL on my home host and expose it at postgres.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "powerdns",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "dns3",
|
||||||
|
"port": 8081
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy PowerDNS on my home host and expose it at dns3.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "prowlarr",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "prowlarr",
|
||||||
|
"port": 9696
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Prowlarr on my home host and expose it at prowlarr.sami" }'</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/{appId}/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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "qbittorrent",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "torrent",
|
||||||
|
"port": 8080
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy qBittorrent on my home host and expose it at torrent.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "radarr",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "radarr",
|
||||||
|
"port": 7878
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Radarr on my home host and expose it at radarr.sami" }'</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/{appId}/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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "readarr",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "readarr",
|
||||||
|
"port": 8787
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Readarr on my home host and expose it at readarr.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "redis",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "redis",
|
||||||
|
"port": 6379
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Redis on my home host and expose it at redis.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "rocketchat",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "chat",
|
||||||
|
"port": 3004
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Rocket.Chat on my home host and expose it at chat.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "roundcube",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "webmail",
|
||||||
|
"port": 8086
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Roundcube on my home host and expose it at webmail.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "sabnzbd",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "sabnzbd",
|
||||||
|
"port": 8092
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy SABnzbd on my home host and expose it at sabnzbd.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "sami-files",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "files",
|
||||||
|
"port": 8765
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Sami Files on my home host and expose it at files.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "seerr",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "requests",
|
||||||
|
"port": 5055
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Seerr on my home host and expose it at requests.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "sonarr",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "sonarr",
|
||||||
|
"port": 8989
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Sonarr on my home host and expose it at sonarr.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "speedtest",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "speedtest",
|
||||||
|
"port": 8093
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Speedtest Tracker on my home host and expose it at speedtest.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "standardnotes",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "notes",
|
||||||
|
"port": 3007
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Standard Notes on my home host and expose it at notes.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "stirling-pdf",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "pdf",
|
||||||
|
"port": 8084
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Stirling PDF on my home host and expose it at pdf.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "syncthing",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "sync",
|
||||||
|
"port": 8384
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Syncthing on my home host and expose it at sync.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "tautulli",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "tautulli",
|
||||||
|
"port": 8181
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Tautulli on my home host and expose it at tautulli.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "technitium",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "dns1",
|
||||||
|
"port": 5380
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Technitium DNS Server on my home host and expose it at dns1.sami" }'</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 -> 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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "transmission",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "transmission",
|
||||||
|
"port": 9092
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Transmission on my home host and expose it at transmission.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "trilium",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "notes",
|
||||||
|
"port": 8085
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Trilium Notes on my home host and expose it at notes.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "uptime-kuma",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "uptime",
|
||||||
|
"port": 3002
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Uptime Kuma on my home host and expose it at uptime.sami" }'</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/{appId}/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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "valheim",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "valheim",
|
||||||
|
"port": 2456
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Valheim Server on my home host and expose it at valheim.sami" }'</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 > 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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "vaultwarden",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "vault",
|
||||||
|
"port": 8088
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Vaultwarden on my home host and expose it at vault.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "vintage-radio",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "radio",
|
||||||
|
"port": 8090
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Vintage Stereo on my home host and expose it at radio.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "vscode-server",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "code",
|
||||||
|
"port": 8443
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy VS Code Server on my home host and expose it at code.sami" }'</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/{appId}/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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "watchtower",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "watchtower",
|
||||||
|
"port": 8089
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Watchtower on my home host and expose it at watchtower.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "weather",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "weather",
|
||||||
|
"port": 32400
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Weather on my home host and expose it at weather.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "whoami",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "whoami",
|
||||||
|
"port": 8094
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy Whoami on my home host and expose it at whoami.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>{success, containerId, url, message, setupInstructions}</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 <jwt></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 '{
|
||||||
|
"appId": "wireguard",
|
||||||
|
"config": {
|
||||||
|
"subdomain": "vpn",
|
||||||
|
"port": 51820
|
||||||
|
}
|
||||||
|
}'</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 (1–65535). 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>{memory, cpus}</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 '{ "message": "Deploy WireGuard VPN on my home host and expose it at vpn.sami" }'</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 <containerId></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/{appId}/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 <containerId></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,381 @@
|
|||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import DocsLayout from '@/components/docs/DocsLayout';
|
||||||
|
|
||||||
|
export default function DocsFirstServicePage() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
<DocsLayout
|
||||||
|
title="Deploy Your First Service"
|
||||||
|
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>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>
|
||||||
|
|
||||||
|
<h3>Step 4: Deploy</h3>
|
||||||
|
<p>
|
||||||
|
Click <strong>Deploy</strong>. DashCaddy now executes the full deployment chain:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<h3>Step 5: Verify</h3>
|
||||||
|
<p>
|
||||||
|
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>
|
||||||
|
|
||||||
|
<h3>How it works</h3>
|
||||||
|
<p>
|
||||||
|
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 & 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 <container_name></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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import DocsLayout from '@/components/docs/DocsLayout';
|
||||||
|
|
||||||
|
export default function DocsInstallationPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
<DocsLayout
|
||||||
|
title="Installation Guide"
|
||||||
|
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>Before you start</h2>
|
||||||
|
<p>
|
||||||
|
You only need three things to install DashCaddy:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>A Linux host that stays on</strong> — Ubuntu 22.04 LTS, Debian 12, or any recent Linux distribution. 2 GB RAM and a few GB of disk are enough to start.</li>
|
||||||
|
<li><strong>Root or sudo access</strong> — the installer writes to <code>/etc/dashcaddy</code>, <code>/opt/dockerdata</code>, and the system Docker daemon.</li>
|
||||||
|
<li><strong>A hostname</strong> — a domain you own, a custom-TLD like <code>dashcaddy.home</code>, or just your machine's IP for local-only use.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Pick your hostname</h2>
|
||||||
|
<p>
|
||||||
|
DashCaddy uses a single base hostname and creates subdomains under it for every service you add (for example
|
||||||
|
<code> plex.dashcaddy.home</code>, <code>homeassistant.dashcaddy.home</code>). Choose the option that matches
|
||||||
|
your situation:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Option A — A real domain you own (recommended)</h3>
|
||||||
|
<p>
|
||||||
|
If you own <code>example.com</code>, register <code>dashcaddy.example.com</code> as a DNS A record pointing at
|
||||||
|
your host's public IP. You can buy a domain for about $10/year from any registrar (Namecheap, Cloudflare,
|
||||||
|
Porkbun, Google Domains). With a real domain, DashCaddy automatically gets a trusted Let's Encrypt
|
||||||
|
certificate on first launch — no certificate warnings, no client setup.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>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'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's internal CA certificate (DashCA) on each device that connects.
|
||||||
|
The setup wizard walks you through this on first run.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Option C — A direct IP address</h3>
|
||||||
|
<p>
|
||||||
|
If you don't want to set up DNS at all, you can reach DashCaddy at <code>https://192.168.x.x:port</code>.
|
||||||
|
This works for a single machine on a LAN, but the URL changes when the host gets a new IP and you lose
|
||||||
|
subdomain support. Useful for a quick test, not a permanent setup.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<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't want to open ports 80 and 443, DashCaddy works fine behind a Tailscale tunnel or Cloudflare
|
||||||
|
Tunnel. Set up the tunnel first, point your hostname at the tunnel's origin, then run the installer
|
||||||
|
with <code>--no-caddy</code> and let the tunnel handle HTTPS termination.
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<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><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>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If the installer ever hits a problem, it stops at the step that failed and tells you exactly which command to
|
||||||
|
re-run to retry. It does not silently leave a half-installed state behind.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>The first-run setup wizard</h2>
|
||||||
|
<p>
|
||||||
|
When you open the dashboard URL the installer printed, you land on the setup wizard. It runs once and writes
|
||||||
|
your configuration to disk — every choice is editable later from the settings page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The wizard walks through these screens:</p>
|
||||||
|
<ol>
|
||||||
|
<li><strong>Pick a configuration profile</strong> — <em>Professional Home Lab</em> (recommended for most users), <em>Simple</em> (minimal config), or <em>Public</em> (for VPS deployments exposed to the internet)</li>
|
||||||
|
<li><strong>Configure your network</strong> — your domain, the LAN IP that'll be used for service routing, and whether you want to use Tailscale for remote access</li>
|
||||||
|
<li><strong>Set storage limits</strong> — max disk usage for Docker data and how much headroom to keep free</li>
|
||||||
|
<li><strong>Review your choices</strong> — a summary screen with the resolved values so you can sanity-check before saving</li>
|
||||||
|
<li><strong>Disk-safety confirmation</strong> — confirms you understand that bound storage will be created and what happens on uninstall</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
After the wizard saves, you're asked to create the first admin account. Pick a strong password — this
|
||||||
|
account controls every service DashCaddy manages.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Set up two-factor authentication</h2>
|
||||||
|
<p>
|
||||||
|
Right after creating your admin account, the dashboard prompts you to enroll a TOTP authenticator
|
||||||
|
(Google Authenticator, Authy, 1Password, or any RFC 6238 app). Scan the QR code with your phone, enter the
|
||||||
|
6-digit code to confirm, and store the recovery codes somewhere safe — they are the only way back into your
|
||||||
|
account if you lose your phone.
|
||||||
|
</p>
|
||||||
|
<blockquote className="border-l-4 border-brand-500/50 bg-brand-500/5 p-4 rounded-r-lg">
|
||||||
|
<p className="text-surface-300">
|
||||||
|
<strong className="text-brand-400">Don't skip 2FA.</strong> DashCaddy can manage DNS records, start and
|
||||||
|
stop containers, and read credentials for the services it deploys. A leaked password without a second
|
||||||
|
factor is a full compromise. A leaked password with 2FA is just a login screen.
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<h2>Install the DashCA root certificate (custom-TLD installs only)</h2>
|
||||||
|
<p>
|
||||||
|
If you chose Option B (a custom TLD like <code>dashcaddy.home</code>) or Option C (a direct IP), your browser
|
||||||
|
will warn that the certificate is not trusted — because it isn't, to anyone outside your network. DashCaddy
|
||||||
|
ships an internal certificate authority called <strong>DashCA</strong> so internal hostnames get a real TLS
|
||||||
|
certificate instead of self-signed warnings.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Visit the <strong>DashCA</strong> page in the dashboard and follow the per-platform instructions:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>macOS</strong> — open the downloaded <code>.crt</code>, add it to the System keychain, set it to “Always Trust”</li>
|
||||||
|
<li><strong>Windows</strong> — double-click the <code>.crt</code>, install to “Trusted Root Certification Authorities”</li>
|
||||||
|
<li><strong>Linux</strong> — copy to <code>/usr/local/share/ca-certificates/</code> and run <code>sudo update-ca-certificates</code></li>
|
||||||
|
<li><strong>iOS / Android</strong> — download the profile, install it via Settings, then enable full trust for the CA</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
You need to install the root certificate on every device that connects to your DashCaddy services, not just
|
||||||
|
the server. There's no way around this for custom-TLD installs — TLS is what keeps your traffic private.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Harden the host right after install</h2>
|
||||||
|
<p>
|
||||||
|
DashCaddy protects the services it manages, but it runs on top of a normal Linux box that's still exposed
|
||||||
|
on your network. Spend five minutes on these four steps:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>1. Enable the firewall</h3>
|
||||||
|
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# UFW (Ubuntu / Debian)
|
||||||
|
sudo ufw default deny incoming
|
||||||
|
sudo ufw allow ssh # if you SSH in
|
||||||
|
sudo ufw allow 80/tcp # HTTP (Let's Encrypt + redirect)
|
||||||
|
sudo ufw allow 443/tcp # HTTPS
|
||||||
|
sudo ufw enable
|
||||||
|
|
||||||
|
# If you put DashCaddy behind a tunnel (Tailscale / Cloudflare), skip 80/443 above
|
||||||
|
# and only allow the tunnel interface — see your tunnel provider's docs`}</code></pre>
|
||||||
|
|
||||||
|
<h3>2. Disable password SSH login</h3>
|
||||||
|
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Edit /etc/ssh/sshd_config and set:
|
||||||
|
PasswordAuthentication no
|
||||||
|
PermitRootLogin prohibit-password # or "no" if you use a sudo user
|
||||||
|
|
||||||
|
sudo systemctl restart sshd`}</code></pre>
|
||||||
|
|
||||||
|
<h3>3. Turn on automatic security updates</h3>
|
||||||
|
<pre className="mt-4 overflow-x-auto rounded-lg border border-surface-700/50 bg-surface-950/80 p-4 text-sm"><code>{`# Debian / Ubuntu
|
||||||
|
sudo apt install -y unattended-upgrades
|
||||||
|
sudo dpkg-reconfigure -plow unattended-upgrades # answer "Yes"`}</code></pre>
|
||||||
|
|
||||||
|
<h3>4. Set up backups</h3>
|
||||||
|
<p>
|
||||||
|
All your DashCaddy state — services, config, encrypted credentials, DashCA — lives in
|
||||||
|
<code> /opt/dockerdata</code> and <code>/etc/dashcaddy</code>. Snapshot those two directories somewhere off
|
||||||
|
the host. Any tool works: <code>rsync</code> to a NAS, <code>restic</code> to Backblaze B2, a systemd timer
|
||||||
|
that tars them up nightly. The dashboard's Backup button exports a single JSON file you can store
|
||||||
|
anywhere if you want a no-setup option.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Verify the install</h2>
|
||||||
|
<p>
|
||||||
|
After the wizard finishes and you've logged in, run through this checklist to confirm everything is wired
|
||||||
|
up correctly:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li><strong>Container is running</strong> — <code>docker ps</code> should show <code>dashcaddy-api</code> with status <code>Up</code></li>
|
||||||
|
<li><strong>API is healthy</strong> — from the host, <code>curl http://localhost:3001/healthz</code> returns <code>{`{"status":"alive"}`}</code> and <code>/readyz</code> returns 200 with all checks passing</li>
|
||||||
|
<li><strong>Dashboard responds</strong> — opening the URL the installer printed shows the dashboard, not a browser error</li>
|
||||||
|
<li><strong>TLS works</strong> — the address bar shows a padlock with no warnings (a real cert if you used Option A; trusted if you used Option B with DashCA installed)</li>
|
||||||
|
<li><strong>2FA works</strong> — log out and back in with your password + TOTP code</li>
|
||||||
|
<li><strong>Updates work</strong> — the dashboard shows the current version in the footer and the update button is enabled</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Troubleshooting a fresh install</h2>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-left text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-surface-700">
|
||||||
|
<th className="py-3 pr-4 font-semibold text-surface-200">Symptom</th>
|
||||||
|
<th className="py-3 pr-4 font-semibold text-surface-200">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 “Docker not found”</td>
|
||||||
|
<td className="py-3 pr-4">Docker Engine isn't installed or you're not in the docker group</td>
|
||||||
|
<td className="py-3">Run <code>sudo usermod -aG docker $USER</code>, log out, log back in. Re-run the installer.</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-b border-surface-800">
|
||||||
|
<td className="py-3 pr-4">Installer fails on “port 80/443 already in use”</td>
|
||||||
|
<td className="py-3 pr-4">Another web server (nginx, Apache, another Caddy) is bound to those ports</td>
|
||||||
|
<td className="py-3">Stop the conflicting service, or move it to different ports and re-run the installer</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-b border-surface-800">
|
||||||
|
<td className="py-3 pr-4">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 “Your connection is not private”</td>
|
||||||
|
<td className="py-3 pr-4">Custom-TLD install with no DashCA trust, OR the domain doesn't point at this host</td>
|
||||||
|
<td className="py-3">For custom TLDs, install the DashCA root certificate. For real domains, check DNS: <code>dig +short your-hostname</code> should return the host's public IP.</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-b border-surface-800">
|
||||||
|
<td className="py-3 pr-4">Let's Encrypt certificate never issues</td>
|
||||||
|
<td className="py-3 pr-4">Domain doesn't resolve publicly, OR port 80 is blocked by your ISP</td>
|
||||||
|
<td className="py-3">Verify the A record with <code>dig</code>. If you're on CGNAT, switch to Tailscale Funnel or Cloudflare Tunnel.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="py-3 pr-4">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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import DocsLayout from '@/components/docs/DocsLayout';
|
||||||
|
|
||||||
|
export default function DocsIntegrationsPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
<DocsLayout
|
||||||
|
title="Infrastructure Integrations"
|
||||||
|
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."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
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>Docker — container runtime</h2>
|
||||||
|
<p>
|
||||||
|
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'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 & 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's built-in internal CA auto-generates and renews certificates for every published service. For
|
||||||
|
public domains, Caddy can also use ACME (Let'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>
|
||||||
|
|
||||||
|
<h3>Caddyfile-as-Code</h3>
|
||||||
|
<p>
|
||||||
|
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'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'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>Tailscale — private access</h2>
|
||||||
|
<p>
|
||||||
|
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'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 & Grafana — metrics & 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 < 14</code> (certificates expiring soon).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>MCP Server & AI assistants</h2>
|
||||||
|
<p>
|
||||||
|
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 — “restart the media server”, “deploy the postgres template”, “is the
|
||||||
|
database healthy?” — 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 & 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 />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import DocsLayout from '@/components/docs/DocsLayout';
|
||||||
|
|
||||||
|
export default function DocsOverviewPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
<DocsLayout
|
||||||
|
title="Product Overview"
|
||||||
|
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,
|
||||||
|
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>
|
||||||
|
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>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>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>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 & 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>
|
||||||
|
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>
|
||||||
|
|
||||||
|
<h3>Small business internal tools</h3>
|
||||||
|
<p>
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
+82
-389
@@ -1,389 +1,82 @@
|
|||||||
'use client';
|
import Link from 'next/link';
|
||||||
|
import Navbar from '@/components/Navbar';
|
||||||
import Link from 'next/link';
|
import Footer from '@/components/Footer';
|
||||||
import Navbar from '@/components/Navbar';
|
|
||||||
import Footer from '@/components/Footer';
|
const docs = [
|
||||||
|
{
|
||||||
export default function DocsPage() {
|
href: '/docs/overview',
|
||||||
return (
|
title: 'Product Overview',
|
||||||
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
description: 'What DashCaddy is, who it is for, and how the platform fits together.',
|
||||||
<Navbar />
|
},
|
||||||
|
{
|
||||||
{/* Hero Section */}
|
href: '/docs/installation',
|
||||||
<section className="relative py-16 sm:py-20 lg:py-24">
|
title: 'Installation Guide',
|
||||||
<div className="absolute inset-0 -z-10">
|
description: 'Installer-based and manual setup paths, prerequisites, and first launch expectations.',
|
||||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
|
},
|
||||||
</div>
|
{
|
||||||
|
href: '/docs/first-service',
|
||||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
title: 'Deploy Your First Service',
|
||||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
description: 'Learn the actual deployment flow and how DashCaddy wires Docker, DNS, and Caddy together.',
|
||||||
Getting <span className="text-brand-400">Started</span>
|
},
|
||||||
</h1>
|
{
|
||||||
<p className="text-xl text-surface-300 max-w-2xl">
|
href: '/docs/integrations',
|
||||||
Get DashCaddy up and running on your server in just a few minutes.
|
title: 'Infrastructure Integrations',
|
||||||
</p>
|
description: 'How DashCaddy works with Docker, Caddy, Technitium DNS, DashCA, and private access workflows.',
|
||||||
</div>
|
},
|
||||||
</section>
|
{
|
||||||
|
href: '/docs/premium',
|
||||||
{/* Main Content */}
|
title: 'Premium Features',
|
||||||
<section className="relative py-12 sm:py-16 lg:py-20">
|
description: 'Free vs Premium, current plan model, and the exact premium-gated feature set.',
|
||||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
},
|
||||||
{/* Table of Contents */}
|
{
|
||||||
<div className="mb-16 rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
|
href: '/docs/api',
|
||||||
<h2 className="text-2xl font-bold text-surface-50 mb-6">Table of Contents</h2>
|
title: 'API and Automation',
|
||||||
<ul className="space-y-3">
|
description: 'How the API fits into service management, deployment workflows, and automation.',
|
||||||
<li>
|
},
|
||||||
<a href="#prerequisites" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
{
|
||||||
<span>→</span> Prerequisites
|
href: '/docs/troubleshooting',
|
||||||
</a>
|
title: 'Troubleshooting',
|
||||||
</li>
|
description: 'A practical debugging guide for DNS, TLS, reverse proxy, certificates, and service health.',
|
||||||
<li>
|
},
|
||||||
<a href="#installation" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
];
|
||||||
<span>→</span> Installation
|
|
||||||
</a>
|
export default function DocsHomePage() {
|
||||||
</li>
|
return (
|
||||||
<li>
|
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
||||||
<a href="#configuration" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
<Navbar />
|
||||||
<span>→</span> Configuration
|
|
||||||
</a>
|
<section className="relative overflow-hidden py-16 sm:py-20 lg:py-24">
|
||||||
</li>
|
<div className="absolute inset-0 -z-10">
|
||||||
<li>
|
<div className="absolute left-1/2 top-1/2 h-96 w-96 -translate-x-1/2 -translate-y-1/2 rounded-full bg-brand-500/20 blur-3xl opacity-30" />
|
||||||
<a href="#first-run" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
</div>
|
||||||
<span>→</span> First Run
|
<div className="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
||||||
</a>
|
<p className="mb-4 text-sm font-semibold uppercase tracking-[0.25em] text-brand-400">Documentation</p>
|
||||||
</li>
|
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">DashCaddy Docs</h1>
|
||||||
<li>
|
<p className="mt-6 max-w-3xl text-lg leading-8 text-surface-300">
|
||||||
<a href="#troubleshooting" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
Everything you need to understand, install, operate, and extend DashCaddy as a real self-hosting platform.
|
||||||
<span>→</span> Troubleshooting
|
</p>
|
||||||
</a>
|
</div>
|
||||||
</li>
|
</section>
|
||||||
</ul>
|
|
||||||
</div>
|
<section className="pb-20">
|
||||||
|
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
|
||||||
{/* Prerequisites Section */}
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||||
<section id="prerequisites" className="mb-16">
|
{docs.map((doc) => (
|
||||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
<Link
|
||||||
<span className="text-brand-400">📋</span> Prerequisites
|
key={doc.href}
|
||||||
</h2>
|
href={doc.href}
|
||||||
<p className="text-surface-300 mb-6 leading-relaxed">
|
className="rounded-2xl border border-surface-700/50 bg-surface-900/50 p-6 transition-all hover:border-brand-500/50 hover:bg-surface-900"
|
||||||
Before you begin, ensure you have the following installed on your server:
|
>
|
||||||
</p>
|
<h2 className="text-xl font-semibold text-surface-50">{doc.title}</h2>
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6 space-y-4">
|
<p className="mt-3 text-sm leading-6 text-surface-300">{doc.description}</p>
|
||||||
<div className="flex items-start gap-4">
|
<p className="mt-5 text-sm font-medium text-brand-400">Read guide →</p>
|
||||||
<div className="text-xl flex-shrink-0">🐳</div>
|
</Link>
|
||||||
<div>
|
))}
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">Docker</h3>
|
</div>
|
||||||
<p className="text-sm text-surface-400">Version 20.10+ required. <a href="https://docs.docker.com/get-docker/" className="text-brand-400 hover:text-brand-300">Install Docker</a></p>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-4">
|
<Footer />
|
||||||
<div className="text-xl flex-shrink-0">⚙️</div>
|
</div>
|
||||||
<div>
|
);
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">Caddy</h3>
|
}
|
||||||
<p className="text-sm text-surface-400">Version 2.7+ required. <a href="https://caddyserver.com/docs/install" className="text-brand-400 hover:text-brand-300">Install Caddy</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="text-xl flex-shrink-0">🟢</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">Node.js</h3>
|
|
||||||
<p className="text-sm text-surface-400">Version 18+ required. <a href="https://nodejs.org/" className="text-brand-400 hover:text-brand-300">Install Node.js</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="text-xl flex-shrink-0">🔧</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">Git</h3>
|
|
||||||
<p className="text-sm text-surface-400">For cloning the repository. <a href="https://git-scm.com/" className="text-brand-400 hover:text-brand-300">Install Git</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="border-t border-surface-700/30 pt-4 mt-4">
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-2">Optional</h3>
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="text-xl flex-shrink-0">🌐</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">Technitium DNS</h3>
|
|
||||||
<p className="text-sm text-surface-400">For automatic DNS management. If not installed, you can still manage DNS manually.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Installation Section */}
|
|
||||||
<section id="installation" className="mb-16">
|
|
||||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
|
||||||
<span className="text-brand-400">📦</span> Installation
|
|
||||||
</h2>
|
|
||||||
<p className="text-surface-300 mb-8 leading-relaxed">
|
|
||||||
Follow these steps to install DashCaddy on your server:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Step 1 */}
|
|
||||||
<div className="mb-10">
|
|
||||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 1: Clone the Repository</h3>
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
|
||||||
<code className="text-sm text-green-400 font-mono block">
|
|
||||||
git clone https://github.com/dashcaddy/dashcaddy.git
|
|
||||||
<br />
|
|
||||||
cd dashcaddy
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 2 */}
|
|
||||||
<div className="mb-10">
|
|
||||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 2: Install Dependencies</h3>
|
|
||||||
<p className="text-surface-300 mb-4">Install Node.js dependencies:</p>
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
|
||||||
<code className="text-sm text-green-400 font-mono block">
|
|
||||||
npm install
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 3 */}
|
|
||||||
<div className="mb-10">
|
|
||||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 3: Configure Environment Variables</h3>
|
|
||||||
<p className="text-surface-300 mb-4">Copy the example environment file and customize it:</p>
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-4">
|
|
||||||
<code className="text-sm text-green-400 font-mono block">
|
|
||||||
cp .env.example .env
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
<p className="text-surface-300 mb-4">Then edit <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">.env</code> with your configuration:</p>
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
|
||||||
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
|
|
||||||
{`# Server
|
|
||||||
PORT=3000
|
|
||||||
NODE_ENV=production
|
|
||||||
|
|
||||||
# Database (optional - defaults to SQLite)
|
|
||||||
DATABASE_URL=sqlite:./data/dashcaddy.db
|
|
||||||
|
|
||||||
# Security
|
|
||||||
JWT_SECRET=your-secure-random-secret-here
|
|
||||||
SESSION_SECRET=another-secure-random-secret
|
|
||||||
|
|
||||||
# Caddy
|
|
||||||
CADDY_PORT=80
|
|
||||||
CADDY_HTTPS_PORT=443
|
|
||||||
CADDY_ADMIN_LISTEN=localhost:2019
|
|
||||||
|
|
||||||
# Technitium DNS (optional)
|
|
||||||
TECHNITIUM_API_URL=http://localhost:5380/api
|
|
||||||
TECHNITIUM_API_KEY=your-api-key
|
|
||||||
|
|
||||||
# Email (optional - for notifications)
|
|
||||||
SMTP_HOST=smtp.example.com
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_USER=your-email@example.com
|
|
||||||
SMTP_PASSWORD=your-password`}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 4 */}
|
|
||||||
<div className="mb-10">
|
|
||||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 4: Build and Start</h3>
|
|
||||||
<p className="text-surface-300 mb-4">Build the application:</p>
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-6">
|
|
||||||
<code className="text-sm text-green-400 font-mono block">
|
|
||||||
npm run build
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
<p className="text-surface-300 mb-4">Start DashCaddy:</p>
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
|
||||||
<code className="text-sm text-green-400 font-mono block">
|
|
||||||
npm start
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
<p className="text-surface-300 mt-4 text-sm">
|
|
||||||
The application will be available at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://localhost:3000</code>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 5 */}
|
|
||||||
<div className="mb-10">
|
|
||||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 5: Configure Caddy</h3>
|
|
||||||
<p className="text-surface-300 mb-4">Update your Caddy configuration to proxy requests to DashCaddy:</p>
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
|
||||||
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
|
|
||||||
{`dashcaddy.local {
|
|
||||||
reverse_proxy localhost:3000
|
|
||||||
|
|
||||||
# Enable automatic HTTPS
|
|
||||||
encode gzip
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
header Strict-Transport-Security "max-age=31536000"
|
|
||||||
header X-Content-Type-Options "nosniff"
|
|
||||||
header X-Frame-Options "DENY"
|
|
||||||
}`}
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Configuration Section */}
|
|
||||||
<section id="configuration" className="mb-16">
|
|
||||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
|
||||||
<span className="text-brand-400">⚙️</span> Configuration
|
|
||||||
</h2>
|
|
||||||
<p className="text-surface-300 mb-8 leading-relaxed">
|
|
||||||
Key environment variables for DashCaddy configuration:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">PORT</h3>
|
|
||||||
<p className="text-surface-300 text-sm mb-2">The port DashCaddy runs on. Default: <code className="bg-surface-800 px-1 rounded">3000</code></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">JWT_SECRET</h3>
|
|
||||||
<p className="text-surface-300 text-sm mb-2">Secret key for JWT tokens. Generate a secure random string:</p>
|
|
||||||
<code className="bg-surface-800 px-2 py-1 rounded text-brand-400 text-xs">openssl rand -hex 32</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">DATABASE_URL</h3>
|
|
||||||
<p className="text-surface-300 text-sm">Connection string for your database. Defaults to SQLite if not provided.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">CADDY_ADMIN_LISTEN</h3>
|
|
||||||
<p className="text-surface-300 text-sm mb-2">Caddy admin API endpoint. Default: <code className="bg-surface-800 px-1 rounded">localhost:2019</code></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">TECHNITIUM_API_URL</h3>
|
|
||||||
<p className="text-surface-300 text-sm">URL to your Technitium DNS API. Optional for DNS management features.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* First Run Section */}
|
|
||||||
<section id="first-run" className="mb-16">
|
|
||||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
|
||||||
<span className="text-brand-400">🚀</span> First Run
|
|
||||||
</h2>
|
|
||||||
<div className="rounded-lg border border-brand-500/30 bg-brand-950/50 p-8">
|
|
||||||
<ol className="space-y-4 list-decimal list-inside text-surface-300">
|
|
||||||
<li>Access the dashboard at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://dashcaddy.local</code></li>
|
|
||||||
<li>Create your admin account with a strong password</li>
|
|
||||||
<li>Enable TOTP 2FA for enhanced security</li>
|
|
||||||
<li>Configure your Technitium DNS API key (optional)</li>
|
|
||||||
<li>Deploy your first application from the app templates library</li>
|
|
||||||
<li>Monitor your apps in real-time from the dashboard</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Troubleshooting Section */}
|
|
||||||
<section id="troubleshooting" className="mb-16">
|
|
||||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
|
||||||
<span className="text-brand-400">🔧</span> Troubleshooting
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
|
||||||
<span className="text-red-400">❌</span> Docker daemon not running
|
|
||||||
</h3>
|
|
||||||
<p className="text-surface-300 text-sm mb-3">Make sure the Docker daemon is started:</p>
|
|
||||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">sudo systemctl start docker</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
|
||||||
<span className="text-red-400">❌</span> Port 3000 already in use
|
|
||||||
</h3>
|
|
||||||
<p className="text-surface-300 text-sm mb-3">Change the PORT in your .env file or stop the process using that port:</p>
|
|
||||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">lsof -i :3000</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
|
||||||
<span className="text-red-400">❌</span> Cannot connect to Caddy admin API
|
|
||||||
</h3>
|
|
||||||
<p className="text-surface-300 text-sm mb-3">Verify Caddy is running and the admin API is accessible:</p>
|
|
||||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">curl http://localhost:2019/config/</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
|
||||||
<span className="text-red-400">❌</span> Database connection errors
|
|
||||||
</h3>
|
|
||||||
<p className="text-surface-300 text-sm mb-3">Check that your DATABASE_URL is correct and the database is accessible. For SQLite, ensure the data directory exists:</p>
|
|
||||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">mkdir -p ./data</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
|
||||||
<span className="text-red-400">❌</span> SSL certificate issues
|
|
||||||
</h3>
|
|
||||||
<p className="text-surface-300 text-sm mb-3">DashCaddy uses Caddy's internal CA for certificate generation. If you have issues, check the Caddy logs:</p>
|
|
||||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">journalctl -u caddy -f</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Next Steps */}
|
|
||||||
<section className="mb-16">
|
|
||||||
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
|
|
||||||
<h2 className="text-2xl font-bold text-surface-50 mb-6">Next Steps</h2>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<Link
|
|
||||||
href="/features"
|
|
||||||
className="flex items-start gap-4 p-4 rounded-lg border border-brand-500/20 bg-brand-950/30 hover:bg-brand-950/50 transition-colors"
|
|
||||||
>
|
|
||||||
<span className="text-2xl flex-shrink-0">✨</span>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">Explore Features</h3>
|
|
||||||
<p className="text-sm text-surface-400">Learn about all the powerful features DashCaddy offers.</p>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
|
||||||
>
|
|
||||||
<span className="text-2xl flex-shrink-0">📚</span>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">API Reference</h3>
|
|
||||||
<p className="text-sm text-surface-400">Full API documentation for developers.</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
href="/pricing"
|
|
||||||
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
|
||||||
>
|
|
||||||
<span className="text-2xl flex-shrink-0">💎</span>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">View Pricing</h3>
|
|
||||||
<p className="text-sm text-surface-400">Check out our free and premium plans.</p>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="mailto:support@dashcaddy.net"
|
|
||||||
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
|
||||||
>
|
|
||||||
<span className="text-2xl flex-shrink-0">💬</span>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-surface-50 mb-1">Get Support</h3>
|
|
||||||
<p className="text-sm text-surface-400">Contact our support team for help.</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,283 @@
|
|||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import DocsLayout from '@/components/docs/DocsLayout';
|
||||||
|
|
||||||
|
export default function DocsPremiumPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col bg-surface-950 text-surface-50">
|
||||||
|
<Navbar />
|
||||||
|
<DocsLayout
|
||||||
|
title="Premium 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."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
DashCaddy'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's by design.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Free vs Premium at a glance</h2>
|
||||||
|
<p>
|
||||||
|
The comparison table below covers every major capability. “Free” means available on an unlicensed
|
||||||
|
install; “Premium” 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 & 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 & RBAC roles</td><td>✓</td><td>✓</td></tr>
|
||||||
|
<tr><td>Encrypted credential storage</td><td>✓</td><td>✓</td></tr>
|
||||||
|
<tr><td>Security Center & audit logging</td><td>✓</td><td>✓</td></tr>
|
||||||
|
<tr><td>AI Intent Router & MCP Server</td><td>✓</td><td>✓</td></tr>
|
||||||
|
<tr><td>REST API & MCP Server</td><td>✓</td><td>✓</td></tr>
|
||||||
|
<tr><td>Backup / restore & Disaster Recovery</td><td>✓</td><td>✓</td></tr>
|
||||||
|
<tr><td>Internationalization (31 languages)</td><td>✓</td><td>✓</td></tr>
|
||||||
|
<tr><td>Plugin & 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'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's standard Swarm workflow: initialize the manager
|
||||||
|
(<code>docker swarm init</code>), join workers (<code>docker swarm join --token ... <manager-ip></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'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'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 />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user