# 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)
