# 2026-03-22 – Hosted Dev IDE via Traefik Wildcard

## Summary

Long session. Explored multiple approaches to masking the raw API IP in the
IDE browser URL. Ended with the correct architecture working and curl-verified.

---

## Architecture Decision

Rejected: Traefik direct routing to container (removes API auth boundary, insecure)
Rejected: Caddy on API host (HSTS/HTTPS issues with zerolaghub.dev domain)
Chosen: Traefik on `zlh-zpack-proxy` → API → container

This is correct because:
- Traefik already handles TLS for the platform
- API remains the auth and proxy boundary
- No per-container DNS or routing side effects needed
- Clean hostname with HTTPS without purchasing a cert

---

## What Was Built

### Traefik wildcard TLS

- `zpackv2` certResolver already configured with Cloudflare DNS-01 challenge
- Stale `_acme-challenge` TXT records in Cloudflare blocked initial cert issuance — deleted manually
- Wildcard cert `*.zerolaghub.dev` issued successfully via Let's Encrypt

### Traefik dynamic config

```yaml
http:
  routers:
    dev-ide:
      rule: "HostRegexp(`dev-{vmid:[0-9]+}.zerolaghub.dev`)"
      entryPoints:
        - websecure
      service: dev-ide-api
      tls:
        certResolver: zpackv2
        domains:
          - main: "zerolaghub.dev"
            sans:
              - "*.zerolaghub.dev"
  services:
    dev-ide-api:
      loadBalancer:
        passHostHeader: true
        servers:
          - url: "http://10.60.0.245:4000"
```

`passHostHeader: true` is critical — preserves `dev-6070.zerolaghub.dev`
through to the API so `handleHostedProxy` can extract the vmid.

### API devProxy.js

`handleHostedProxy` added — extracts vmid from `Host` header, validates
token, sets cookie, proxies to container. This was the missing piece that
caused 404s until the code was deployed.

`.env` aligned:
- `DEV_IDE_RETURN_HOSTED_URL=true`
- `DEV_IDE_HOST_SUFFIX=zerolaghub.dev`
- `DEV_IDE_PUBLIC_SCHEME=https`

---

## Curl-Verified Response Chain

```
GET https://dev-6070.zerolaghub.dev/?token=<valid>
  → 302 + Set-Cookie: zlh_dev_ide_token

GET https://dev-6070.zerolaghub.dev/ (with cookie)
  → 302 → /?folder=/home/dev/workspace

GET https://dev-6070.zerolaghub.dev/?folder=/home/dev/workspace
  → 200 (code-server HTML)
```

Full chain confirmed: Browser → Traefik → API → container:6000

---

## Key Lessons

- ERR_CONNECTION_CLOSED from browser (but curl works) = H2 mismatch or wrong
  target. In this case it was the API not running the new code (404), not H2.
- `passHostHeader: true` in Traefik is the equivalent of Caddy's
  `header_up Host {host}` — without it Express resolves relative redirects
  against the internal IP, leaking it to the browser.
- Wildcard certs require DNS-01 challenge — stale TXT records in Cloudflare
  will block issuance silently. Check and clear them first.
- Traefik and the API are on different subnets (10.70.x vs 10.60.x) — always
  verify cross-subnet reachability with curl before debugging proxy config.

---

## Remaining

- Browser validation (curl is confirmed, browser WebSocket not yet tested)
- Portal "Open IDE" button confirmation under hosted flow
- Legacy `/__ide/:id` compatibility cleanup once browser is confirmed
