Files
dashcaddy/CLAUDE.md
SamiandClaude Sonnet 4.6 a2e6566958
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
refactor(desloppify): SSO login-page route, CLAUDE.md rewrite, gitignore cleanup
- sso-gate.js: add GET /api/v1/auth/login-page?service= route; auto-login
  HTML for chat/plex/jellyfin/emby now served from code instead of inline
  Caddyfile respond blobs. Fix merge() try-block syntax error (was missing
  closing } before catch, breaking Jellyfin/Emby localStorage merge).
- middleware.js: add /api/v1/auth/login-page to PUBLIC_ROUTES.
- CLAUDE.md: complete rewrite — was describing the old Windows-local
  C:/caddy/ layout; now accurately describes DNS2 production (paths,
  container, caddy-apply workflow, SSO architecture, common mistakes).
- .gitignore: cover runtime JSON/log/cert files that were sitting untracked
  in dev root (audit-log, backup-history, credentials, health-history, etc.),
  plus generated-certs/, pki/, assets/.
- Remove tracked dev-root noise: comprehensive-test.js, license-keygen.js,
  test-security-fixes.js (scripts that don't belong at repo root).
- Remove stale routes/openclaw.js (leftover from old monolithic structure).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 03:48:11 -07:00

12 KiB

DashCaddy Project Guidelines for AI Assistants

HARD RULE: Docker Storage on E: Drive

ALL Docker container data, volumes, bind mounts, and app configs MUST use E:/dockerdata/ via bind mounts or CIFS volumes. No exceptions.

  • E: is a network share (\\Sami-pc\e_share) shared across all home network computers
  • The ONLY thing allowed on C: is the Docker Desktop WSL engine VHD (C:/dockerdata/DockerDesktopWSL/) — this is the absolute bare minimum WSL2 requires (local NTFS). WSL2 cannot create VHDs on network shares.
  • Keep C: Docker usage under 5GB
  • When deploying new containers, always use E:/dockerdata/<app-name>/ for bind mount paths
  • For CIFS volumes in docker-compose, use //Sami-pc/e_share/dockerdata/... as the device path

CRITICAL: Production is on DNS2 (not this machine)

DashCaddy runs on DNS2 (100.121.150.22 via Tailscale / 194.233.88.206 public).
SSH in with: ssh root@100.121.150.22

Production Layout on DNS2

/opt/dashcaddy/                         # git repo (auto-updated)
├── dashcaddy-api/
│   ├── *.js                            # API server source
│   └── data/
│       ├── services.json               # LIVE services list
│       ├── config.json                 # LIVE DashCaddy config
│       ├── dns-credentials.json        # DNS API credentials
│       └── credentials.json            # Encrypted app credentials
├── status/                             # Dashboard frontend (built)
│   ├── index.html
│   ├── dist/                           # Bundled JS (core/features/onboarding/init)
│   ├── js/                             # Source JS (also served statically)
│   ├── css/
│   └── assets/
├── ca/                                 # DashCA static site
├── updates/                            # Auto-updater staging + history
└── start.sh                            # Container launch script (run by @reboot cron)

Docker Container

  • Name: dashcaddy-api
  • Image: dashcaddy-dashcaddy-api:latest
  • Port: 127.0.0.1:3001 (Caddy proxies to it)
  • Started by: /opt/dashcaddy/start.sh via root @reboot cron

Key container mounts:

Container path Host path
/app/data/ /opt/dashcaddy/dashcaddy-api/data/
/app/assets /opt/dashcaddy/status/assets
/caddyfile /etc/caddy/Caddyfile
/app/backups /opt/dashcaddy/backups

Caddy

  • Config: /etc/caddy/Caddyfile (git-guarded — edit then run caddy-apply)
  • Admin API: http://localhost:2019 (NOT 2021)
  • TLS storage: /var/lib/caddy/
  • Static files: Caddy serves /opt/dashcaddy/status/ for status.sami

Development Files (for editing)

e:/CaddyCerts/sites/
├── dashcaddy-api/          # API server source (NOT caddy-api/)
│   ├── server.js
│   ├── src/app.js          # Express app factory
│   ├── routes/             # Route handlers
│   ├── middleware.js
│   └── ...
└── status/                 # Dashboard frontend source
    ├── index.html          # HTML template (~853 lines)
    ├── js/                 # Source JS modules
    ├── css/
    ├── dist/               # Built output (run node build.js)
    └── build.js            # Build script (uses esbuild)

When Making Changes

To add/remove services from dashboard:

Edit /opt/dashcaddy/dashcaddy-api/data/services.json on DNS2 directly, OR use the dashboard UI at https://status.sami.

To modify Caddy reverse proxy rules:

ssh root@100.121.150.22
# Edit /etc/caddy/Caddyfile
caddy-apply "reason for change"   # validates + reloads + git commits

To modify API server code:

  1. Edit e:/CaddyCerts/sites/dashcaddy-api/ locally
  2. scp changed files to root@100.121.150.22:/opt/dashcaddy/dashcaddy-api/
  3. Rebuild container: ssh root@100.121.150.22 "bash /opt/dashcaddy/start.sh"

To modify dashboard frontend:

  1. Edit source in e:/CaddyCerts/sites/status/js/ or status/index.html
  2. Build: cd e:/CaddyCerts/sites/status && node build.js
  3. Deploy: scp -r dist/ index.html sw.js root@100.121.150.22:/opt/dashcaddy/status/

To modify DashCA:

Edit files in e:/CaddyCerts/sites/ca/, then:

  1. Regenerate: cd e:/CaddyCerts/sites/ca/scripts && bash generate-all.sh
  2. Deploy: scp -r e:/CaddyCerts/sites/ca/* root@100.121.150.22:/opt/dashcaddy/ca/

DashCA - Certificate Authority Distribution

Purpose: One-click CA cert install page so *.sami domains are trusted on all devices.
Access: https://ca.sami

Certificate Info:

  • CN: Sami Home Network Root CA
  • Algorithm: ECDSA P-256 with SHA-256
  • Valid Until: Dec 22, 2034
  • Fingerprint: 08:98:A5:63:F5:A1:A2:58:5F:02:D7:A8:A2:54:87:E6:BC:33:96:21:29:0E

Certificate Source (on DNS2):

  • Root CA: /etc/ssl/sami-ca/root.crt
  • Intermediate CA: auto-generated by Caddy at /var/lib/caddy/pki/authorities/local/

API Endpoints

  • GET /api/ca/info — certificate metadata
  • GET /api/health/ca — CA expiration health (healthy / warning / critical)

Key Services

Service Where Port Notes
Caddy (HTTPS) DNS2 443 Reverse proxy
Caddy Admin DNS2 2019 Caddy API
DashCaddy API DNS2 3001 Dashboard backend (container)
Technitium DNS (primary) DNS2 5380 100.121.150.22
Technitium DNS (secondary) DNS1 (this PC) 5380 100.71.97.12

SSO Architecture

import dashcaddy_auth <serviceId> in the Caddyfile expands to a forward_auth gate that:

  1. Checks the DashCaddy TOTP session (cookie domain .sami — shared across all *.sami)
  2. Injects credentials (API key, Basic Auth, app cookies) into upstream request headers

For client-side auto-login (chat, Plex, Jellyfin, Emby):

  • Caddy redirects path / to /dashcaddy-login
  • /dashcaddy-login proxies to GET /api/v1/auth/login-page?service=<id> on the API
  • That page's JS fetches /dashcaddy-api/api/auth/app-token/<id> and stores the token in localStorage

Common Mistakes to Avoid

  1. Wrong API source dir: It's dashcaddy-api/, NOT caddy-api/ (old name, no longer exists)
  2. Wrong services file: Edit the one in /opt/dashcaddy/dashcaddy-api/data/ on DNS2, not the dev copy
  3. Caddyfile edits without caddy-apply: Always use caddy-apply — it validates, reloads, and git-commits
  4. Caddy admin port: It's 2019, not 2021
  5. Frontend changes without build: Edit JS source, then node build.js, then deploy dist/
  6. DNS2 Tailscale IP: 100.121.150.22 (NOT the old 100.104.4.5 or 100.74.102.61)

Linux Deployment (DNS2 / Contabo VPS)

The Windows path sections above describe the SAMI-PC deployment. DashCaddy also runs as a Docker container on Linux (DNS2 = 194.233.88.206 / Tailscale 100.121.150.22). The Linux deployment uses a different layout driven by start.sh and docker run bind mounts.

Production paths (Linux)

/opt/dashcaddy/
├── dashcaddy-api/         # Built image source (rebuilt on update)
│   ├── Dockerfile
│   └── ...
├── status/                # Dashboard frontend SOURCE (build context)
├── credentials.json       # Encrypted credentials (mounted to /app/data)
├── .encryption-key        # AES key (mounted to /app/data)
└── services.json          # Live service list (mounted to /app/data)

/var/www/dashcaddy-status/ # Dashboard frontend LIVE (served by Caddy)
                           # Built bundle output from status/ — NOT the source
                           # tree, NOT the docker build context

/etc/dashcaddy/
└── Caddyfile              # Active Caddy configuration

/root/.dashcaddy/          # Per-user state, credentials backup, license

Container mount points (Linux)

Container path Host path
/app/data/credentials.json /opt/dashcaddy/credentials.json
/app/data/.encryption-key /opt/dashcaddy/.encryption-key
/app/data/services.json /opt/dashcaddy/services.json
/caddyfile /etc/dashcaddy/Caddyfile

Note: the app must auto-resolve both /app/data/... AND the older /app/... layout (where files mounted directly to /app/). The credential-manager.js and crypto-utils.js modules handle this fallback. This is intentional — fresh installs get /app/data/, legacy installs keep working without env-var overrides.

Three-filesystem frontend trap (Linux)

The dashboard frontend lives on three separate paths that get confused:

  1. Source/opt/dashcaddy/status/ — what you edit
  2. Live/var/www/dashcaddy-status/ — what Caddy serves to browsers
  3. Build context/opt/dashcaddy/dashcaddy-api/ — what docker build uses

Editing /opt/dashcaddy/status/index.html and restarting the container does nothing visible until you run the build (which writes to /var/www/dashcaddy-status/). Always rebuild + container-recreate together. See the dashcaddy skill § Deploy cycle for the exact sequence.

Common commands (Linux)

# Edit Caddyfile then reload (no restart needed)
curl -X POST http://localhost:2019/load \
  -H "Content-Type: text/caddyfile" \
  --data-binary @/etc/dashcaddy/Caddyfile

# View container logs
docker logs dashcaddy-api --tail 200

# Rebuild + restart after API code change
cd /opt/dashcaddy && git pull
cd /opt/dashcaddy/dashcaddy-api && docker build -t dashcaddy-api:local .
docker stop dashcaddy-api && docker rm dashcaddy-api
# (then re-run the container with the mount table above)

# Edit a service in the live list
vi /opt/dashcaddy/services.json   # live-reloaded by the watcher

Differences from Windows

Concern Windows (SAMI-PC) Linux (DNS2)
Drive letter C:/, E:/ /opt/, /etc/, /var/www/
Network share for state \\Sami-pc\e_share (none — all local)
Docker engine Docker Desktop on WSL2 Docker Engine on host
Backend admin PowerShell bash + curl
Caddyfile reload POST to localhost:2019/load POST to localhost:2019/load (same)
Caddy admin port 2019 2019
Self-update host-side PowerShell updater host-side bash updater (start.sh)
Tailscale Same 100.x.x.x magic DNS Same
DNS server DNS2 (100.74.102.61) primary DNS2 (100.121.150.22 / 194.233.88.206) — is the primary

Linux-specific gotchas

  • Caddy needs network_mode: host (or --network host) so it can bind :80 and :443 directly. Bridge mode + port mapping also works, but network_mode: host is simpler for a single-host setup.
  • credentials.json permissions matter — file mode 0600, owned by the same UID the container runs as. If the host root creates it but the container runs as node (uid 1000), the API will fail to read it. Either chown 1000:1000 or run the container as --user 0.
  • Don't use localhost in the API's CORS_ORIGINS — it conflicts with the Tailscale IP. Use the actual https://dashcaddy<your-tld> URL.
  • Tailscale cert provisioning — set TS_AUTHKEY in /etc/dashcaddy/tailscale.env (mode 0600) before first start. Without it, the magic DNS hostname will resolve but TLS will fail.

Project Info

  • Name: DashCaddy
  • Version: 1.13.4 (current; CHANGELOG.md [Unreleased] tracks the next bump)
  • Purpose: Unified management for Docker + Caddy + DNS
  • Local TLD (Windows): .sami
  • Local TLD (Linux, DNS2): .home (default; configurable via siteConfig.tld)
  • Repo: /opt/dashcaddy/ on DNS2 (git, auto-updated by self-updater)