Files
dashcaddy/CLAUDE.md
Hermes 140aa5d4b1 [grade=A] P2-1 through P2-4: version sync, dead file cleanup, ESLint fixes
P2-1: VERSION file 1.14.9→1.15.0 (matches package.json), CLAUDE.md 1.13.4→1.15.0
P2-2: git rm dashcaddy-api/scripts/legacy/comprehensive-test.js + test-security-fixes.js
P2-3: .eslintrc.js add no-empty rule with allowEmptyCatch:true (3 errors→0)
P2-4: routes/auth/session-handlers.js:39 fix no-useless-escape (\- → .- in char class)

1539/1539 tests pass. ESLint errors eliminated.
2026-08-10 20:28:36 -07:00

252 lines
12 KiB
Markdown

# 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:
```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 <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)
```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<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.15.0 (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)