diff --git a/package-lock.json b/package-lock.json index b671361..845b023 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@stripe/stripe-js": "^9.2.0", + "marked": "^14.1.4", "next": "16.2.4", "react": "19.2.4", "react-dom": "19.2.4", @@ -4865,6 +4866,18 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/marked": { + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.4.tgz", + "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", diff --git a/package.json b/package.json index 3178994..b002542 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@stripe/stripe-js": "^9.2.0", + "marked": "^14.1.4", "next": "16.2.4", "react": "19.2.4", "react-dom": "19.2.4", diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 0000000..ed18802 --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,57 @@ +# DashCaddy + +> DashCaddy is an AI-native self-hosting platform: deploy, secure, and manage +> self-hosted apps from one dashboard. It ships 77 one-click application +> templates, automatic SSL, a Security Center, multi-host fleet management, +> 400+ registered REST route handlers, and a built-in MCP server so AI assistants +> can operate it directly. Runs on any Linux host via Docker. + +This file follows the llms.txt convention (https://llmstxt.org) to give AI +assistants a concise, accurate map of DashCaddy and the exact commands to +install it. + +## Install DashCaddy (Linux, Docker) + +One-line install (interactive, single-keystroke choices): + +``` +curl -fsSL https://get.dashcaddy.net | bash +``` + +Zero-typing local mode (instant access, no domain needed): + +``` +curl -fsSL https://get.dashcaddy.net | bash -s -- quick +``` + +With your own domain (the only thing you type is the domain; SSL is automatic): + +``` +curl -fsSL https://get.dashcaddy.net | bash -s -- --domain my.example.com +``` + +Requirements: a Linux host with root/sudo (the installer sets up Docker and Caddy +if they are missing). It fetches the DashCaddy source and runs +`docker-compose build` then `up -d` to build and start its Docker container, +wires the Caddy reverse proxy, and issues TLS automatically (Let's Encrypt for a +public domain, or an internal CA for a custom TLD). Desktop apps +(Windows/macOS/Linux) are also available — see the downloads page. + +## For AI assistants operating DashCaddy + +After install, DashCaddy exposes a built-in MCP (Model Context Protocol) server +with named tools — for example listing services, checking service and system +health, searching the app catalog, and running container actions. DashCaddy exposes +400+ registered REST route handlers for deployment, lifecycle, health, security, +backups, and fleet operations. It also exposes a WebSocket channel for live +updates. + +## Key links + +- [Install guide](https://dashcaddy.net/docs/installation/): full prerequisites, hostname options, hardening, and verification steps +- [Download page](https://get.dashcaddy.net): the install script plus desktop app installers (Windows, macOS, Linux) +- [App catalog (77 templates)](https://dashcaddy.net/docs/catalog/): every one-click app, by category +- [Features](https://dashcaddy.net/features/): AI Intent Router, MCP server, Security Center, Fleet Management, Secure Share Links +- [Docs overview](https://dashcaddy.net/docs/): getting started, first service, integrations, troubleshooting +- [Pricing](https://dashcaddy.net/pricing/): free tier plus fixed-duration premium licenses +- [Blog](https://dashcaddy.net/blog/): guides and product updates diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..d33f955 --- /dev/null +++ b/src/app/blog/[slug]/page.tsx @@ -0,0 +1,133 @@ +import fs from "fs"; +import path from "path"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { marked } from "marked"; +import Navbar from "@/components/Navbar"; +import Footer from "@/components/Footer"; +import { POSTS } from "@/content/blog/manifest"; + +export const dynamicParams = false; + +const BLOG_DIR = path.join(process.cwd(), "src", "content", "blog"); + +export function generateStaticParams() { + // Fail the build loudly if a manifest entry has no matching markdown file — + // prevents a future publisher from registering a post it never wrote. + for (const p of POSTS) { + if (!fs.existsSync(path.join(BLOG_DIR, `${p.slug}.md`))) { + throw new Error( + `blog: manifest slug "${p.slug}" has no src/content/blog/${p.slug}.md` + ); + } + } + // Reverse guard: reject orphaned markdown files not represented in POSTS, so a + // stray/forgotten draft can't silently ship without an index entry. + const slugs = new Set(POSTS.map((p) => p.slug)); + for (const f of fs.readdirSync(BLOG_DIR)) { + if (f.endsWith(".md") && !slugs.has(f.replace(/\.md$/, ""))) { + throw new Error( + `blog: orphaned src/content/blog/${f} has no POSTS manifest entry` + ); + } + } + return POSTS.map((p) => ({ slug: p.slug })); +} + +function getPost(slug: string) { + return POSTS.find((p) => p.slug === slug) ?? null; +} + +function readBody(slug: string): string | null { + const file = path.join(BLOG_DIR, `${slug}.md`); + if (!fs.existsSync(file)) return null; + return fs.readFileSync(file, "utf8"); +} + +function formatDate(iso: string): string { + return new Date(iso + "T00:00:00Z").toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + timeZone: "UTC", + }); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + const post = getPost(slug); + if (!post) return { title: "Post not found — DashCaddy Blog" }; + return { + title: `${post.title} — DashCaddy Blog`, + description: post.description, + alternates: { canonical: `https://dashcaddy.net/blog/${post.slug}/` }, + openGraph: { + title: post.title, + description: post.description, + type: "article", + url: `https://dashcaddy.net/blog/${post.slug}/`, + }, + }; +} + +export default async function BlogPostPage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + const post = getPost(slug); + const body = post ? readBody(slug) : null; + if (!post || body === null) notFound(); + + const html = marked.parse(body, { async: false }) as string; + + return ( +
+ + +
+
+ + ← All posts + + +
+
+ + · + {post.readingMinutes} min read + · + {post.author} +
+

{post.title}

+
+ {post.tags.map((tag) => ( + + {tag} + + ))} +
+
+ +
+
+
+ +
+ ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx new file mode 100644 index 0000000..6eeb4f4 --- /dev/null +++ b/src/app/blog/page.tsx @@ -0,0 +1,85 @@ +import Link from "next/link"; +import Navbar from "@/components/Navbar"; +import Footer from "@/components/Footer"; +import { POSTS } from "@/content/blog/manifest"; + +export const metadata = { + title: "Blog — DashCaddy", + description: + "Guides, product updates, and self-hosting deep-dives from the DashCaddy team: AI-powered deployment, the MCP server, security, and fleet management.", + alternates: { canonical: "https://dashcaddy.net/blog/" }, +}; + +function formatDate(iso: string): string { + return new Date(iso + "T00:00:00Z").toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + timeZone: "UTC", + }); +} + +export default function BlogIndexPage() { + const posts = [...POSTS].sort((a, b) => (a.date < b.date ? 1 : -1)); + return ( +
+ + +
+
+
+
+
+

+ The DashCaddy Blog +

+

+ Self-hosting deep-dives, product updates, and practical guides to + running your own infrastructure. +

+
+
+ +
+
+ {posts.length === 0 ? ( +

No posts yet — check back soon.

+ ) : ( +
    + {posts.map((post) => ( +
  • + +
    + + · + {post.readingMinutes} min read +
    +

    + {post.title} +

    +

    {post.description}

    +
    + {post.tags.map((tag) => ( + + {tag} + + ))} +
    + +
  • + ))} +
+ )} +
+
+ +
+ ); +} diff --git a/src/app/docs/installation/page.tsx b/src/app/docs/installation/page.tsx index 73c71a0..4fd40e6 100644 --- a/src/app/docs/installation/page.tsx +++ b/src/app/docs/installation/page.tsx @@ -99,7 +99,7 @@ curl -fsSL https://get.dashcaddy.net | bash -s -- quick`}
  • Detect your environment — operating system, package manager, free disk space, available memory, public and LAN IP addresses
  • Install prerequisites — Docker Engine and the Docker Compose plugin if missing
  • Write configuration — generates /etc/dashcaddy/, /opt/dockerdata/, and the DashCaddy config file with sensible defaults
  • -
  • Pull the DashCaddy container image — fetches the latest release from the public registry
  • +
  • Build the DashCaddy container image — fetches the source (release tarball or git) and runs docker-compose build
  • Start the DashCaddy container — launches dashcaddy-api on port 3001, mounts persistent data volumes
  • Health check — waits for the API to respond on /healthz before continuing
  • Print the access URL — shows the dashboard address and any follow-up commands
  • diff --git a/src/app/globals.css b/src/app/globals.css index 6f0eec2..3c4715c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -133,3 +133,78 @@ body { .theme-toggle:hover { background-color: var(--color-surface-800); } + +/* Blog post typography (rendered markdown via .blog-prose) */ +.blog-prose { + color: var(--color-surface-200); + line-height: 1.75; + font-size: 1.05rem; +} +.blog-prose h2 { + color: #fff; + font-size: 1.6rem; + font-weight: 700; + margin-top: 2.25rem; + margin-bottom: 0.9rem; + line-height: 1.3; +} +.blog-prose h3 { + color: #fff; + font-size: 1.25rem; + font-weight: 600; + margin-top: 1.75rem; + margin-bottom: 0.6rem; +} +.blog-prose p { + margin-bottom: 1.25rem; +} +.blog-prose a { + color: var(--color-brand-400); + text-decoration: underline; + text-underline-offset: 2px; +} +.blog-prose a:hover { + color: var(--color-brand-300); +} +.blog-prose strong { + color: #fff; + font-weight: 600; +} +.blog-prose ul, +.blog-prose ol { + margin: 0 0 1.25rem 1.5rem; +} +.blog-prose ul { + list-style: disc; +} +.blog-prose ol { + list-style: decimal; +} +.blog-prose li { + margin-bottom: 0.5rem; +} +.blog-prose code { + background-color: var(--color-surface-800); + padding: 0.15rem 0.4rem; + border-radius: 6px; + font-size: 0.9em; +} +.blog-prose pre { + background-color: var(--color-surface-900); + border: 1px solid var(--color-surface-800); + border-radius: 10px; + padding: 1rem 1.25rem; + overflow-x: auto; + margin-bottom: 1.5rem; +} +.blog-prose pre code { + background: none; + padding: 0; +} +.blog-prose blockquote { + border-left: 3px solid var(--color-brand-500); + padding-left: 1rem; + color: var(--color-surface-300); + font-style: italic; + margin-bottom: 1.5rem; +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index fccd3c9..93bc72f 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -28,6 +28,7 @@ export default function Navbar() { { href: '/features', label: 'Features' }, { href: '/pricing', label: 'Pricing' }, { href: '/docs', label: 'Docs' }, + { href: '/blog', label: 'Blog' }, { href: '/about', label: 'About' }, ]; diff --git a/src/content/blog/deploy-self-hosted-apps-with-ai.md b/src/content/blog/deploy-self-hosted-apps-with-ai.md new file mode 100644 index 0000000..4a54047 --- /dev/null +++ b/src/content/blog/deploy-self-hosted-apps-with-ai.md @@ -0,0 +1,83 @@ +Self-hosting has always carried a tax: every app you want to run means a new +container to configure, a reverse proxy to wire up, a certificate to issue, and a +dashboard to babysit. DashCaddy was built to erase that tax — and the clearest +example is the **AI Intent Router**, which lets you describe what you want in +plain English and have it deployed, secured, and running. + +## From a sentence to a running service + +Traditional self-hosting asks you to think like an operator: pick an image, map +the ports, mount the volumes, set the environment, then hand-write a reverse-proxy +block and chase down a TLS certificate. DashCaddy collapses that into intent. + +Instead of memorizing flags, you tell DashCaddy what you're trying to accomplish. +The AI Intent Router turns that plain-English request into a structured deploy +intent matched to one of DashCaddy's **77 pre-configured application templates**; +the deploy flow then provisions the service with sane defaults — container, +networking, and TLS via DashCaddy's built-in CA and Caddy. The result is a +working, HTTPS-secured app instead of a half-finished `docker run` command. + +## 77 one-click templates, not a blank canvas + +A blank Docker host is powerful and unforgiving. DashCaddy ships **77 one-click +templates** spanning media servers, productivity tools, developer platforms, +networking and DNS utilities, and more. Each template encodes the knowledge that +usually lives in a dozen browser tabs: a working default port and a health check, +wired and ready to run. + +Because the templates are curated rather than improvised, "install Nextcloud" or +"set up a Git server" becomes a single action — deployable from the dashboard or +the REST API. + +## Built for AI assistants: the MCP server + +DashCaddy doesn't just use AI internally — it exposes itself *to* AI. The built-in +**MCP (Model Context Protocol) server** gives an AI assistant a set of named tools +— among them listing your services, checking service and system health, searching +the app catalog, and running container actions — over a standard protocol. Instead +of translating intent into API calls by hand, you can let an assistant do the +work. Self-hosting becomes something you can delegate. + +## A real platform underneath: 400+ registered REST route handlers + +The friendly front end sits on top of a genuine platform. DashCaddy registers +**400+ REST route handlers** covering deployment, lifecycle, health, security, +backups, and fleet operations — with a WebSocket channel for live updates. +Scripts and CI pipelines get a first-class path too. The AI layer is a +convenience, not a lock-in: the API is always there for the operators who want +it. + +## Security that isn't an afterthought + +Self-hosting is only worth it if it's safe to expose. DashCaddy includes a +**Security Center** — a dashboard for security events and the hosts it tracks — +so security visibility isn't a separate project you keep meaning to set up. TLS +is handled by the built-in CA and Caddy. And when you do want to let someone in, +**Secure Share Links** issue time-boxed public links (with an optional status +subscription) or single-use Tailscale invitations, so sharing a service doesn't +mean opening it to the world. + +## Scales from one box to a fleet + +What starts as a single-host homelab rarely stays that way. DashCaddy includes a +deploy bridge (its shipdeck integration) with live health re-probes and fleet +credentials for reaching other hosts, so growing beyond one machine doesn't mean +starting over with a new tool. + +## Speaks your language — literally + +DashCaddy's interface is localized into **31 languages**, including full +right-to-left support for Arabic. Self-hosting shouldn't be gated behind English +fluency, and the dashboard reflects that. + +## Why this matters + +The promise of self-hosting is control: your data, your infrastructure, your +rules. The cost has always been complexity. By turning intent into deployment, +curating 77 templates instead of handing you a blank canvas, and exposing 400+ +registered REST route handlers plus an MCP server, DashCaddy keeps the control +while removing most of the complexity. + +Ready to try it? Browse the [full feature set](/features), see the +[77-app catalog](/docs/catalog), or jump straight to the +[installation guide](/docs/installation). diff --git a/src/content/blog/manifest.ts b/src/content/blog/manifest.ts new file mode 100644 index 0000000..425bdc2 --- /dev/null +++ b/src/content/blog/manifest.ts @@ -0,0 +1,27 @@ +// Blog post manifest — single source of truth for the index and the +// [slug] static-params generator. To publish, insert a new entry into the +// POSTS array (newest first) and add a matching markdown file at +// src/content/blog/.md. The build fails if a slug has no matching file. + +export interface PostMeta { + slug: string; + title: string; + description: string; + date: string; // ISO yyyy-mm-dd + author: string; + tags: string[]; + readingMinutes: number; +} + +export const POSTS: PostMeta[] = [ + { + slug: "deploy-self-hosted-apps-with-ai", + title: "Deploy Self-Hosted Apps From Plain English: DashCaddy's AI Intent Router", + description: + "How DashCaddy turns a plain-English request into a running, SSL-secured container — 77 one-click app templates, an MCP server for AI assistants, and 400+ registered REST route handlers.", + date: "2026-10-05", + author: "The DashCaddy Team", + tags: ["self-hosting", "ai", "docker", "homelab"], + readingMinutes: 6, + }, +];