Round-1 docs claimed things the code does not do. This commit rewrites
all 77 per-template pages + the catalog index to match reality:
CORRECTED:
- REST API payload shape: was {template, subdomain, port, environment}
Real Joi schema (src/utilities/validate.js appDeploy) is
{appId, config: {subdomain, port, mediaPath?, plexClaimToken?, ...}}
All optional fields now documented per-schema.
- Auth methods: was just "Authorization: Bearer" — real middleware
accepts three (TOTP session cookie, Authorization: Bearer JWT,
X-API-Key header). All three now shown.
- Deploy is synchronous: was "poll GET /api/v1/apps/{id}/status" — there
is no status endpoint. Response shape documented as
{success, containerId, url, message, setupInstructions}.
- AI Intent Router: was "starts the deployment" — it returns a
structured intent; the caller must POST /api/v1/apps/deploy to
actually provision. Documented accurately.
- MCP dashcaddy_deploy_app: was treated as full deploy — it only writes
the Caddy route + services.json entry, NOT the container. Documented
as such with manual docker pull as next step.
- Watchtower: was claimed to auto-update every 24h — DashCaddy does NOT
poll for new digests. Watchtower is a separate template with default
schedule 0 0 4 * * * (cron 04:00). Documented.
- Update button: was "Apps → {Name} → Update" — no such endpoint exists.
Manual docker pull + restart now documented.
- Restore-on-install: was "prompt to restore from snapshot during install"
— no such prompt. POST /api/v1/apps/{appId}/restore documented.
- Backups: was "default daily snapshot" — backup schedule is
configurable via backup-config.json, not "nightly" by default.
Volumes ARE included; documented.
- Dashboard menu: was "Apps → Catalog" — actual entry is the
"App Selector" button on the dashboard home. Documented.
- Dashboard URL: status.sami confirmed correct (configurable via
dashboardHost).
Also fixes default port resolution: was using portList[0].split(":")[0]
which leaks "{{PORT}}" literal when the template uses the placeholder.
Now uses t.defaultPort (Joi-validated) first, falling back to portList.
Build: 78 routes prerender as static, TypeScript clean.
155 lines
12 KiB
TypeScript
155 lines
12 KiB
TypeScript
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>
|
||
);
|
||
}
|