# 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//` 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: ```bash 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 ` 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=` on the API - That page's JS fetches `/dashcaddy-api/api/auth/app-token/` 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) ```bash # 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` 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)