# traefik — ОСК reverse proxy / SNI router

Replaces **nginx-proxy-manager** on serverweb. One `:443` entrypoint that does
**both**:

- **TLS pass-through (Layer 4)** for the Windows services that use Windows-Integrated
  auth — TLS terminates at the backend, native Kerberos/NTLM/Negotiate is preserved
  (NPM/openresty cannot do this):
  - `workfolders.osk.team` → serverfile `192.168.0.11:443` (Work Folders)
  - `mail.osk.team`, `autodiscover.osk.team` → servermail `192.168.0.6:443` (Exchange 2019)
  - `kdcproxy.osk.team` → serverwsus `192.168.0.7:443` (KDC proxy)
- **TLS termination + Let's Encrypt** for the web dashboards:
  - `start.osk.team` (flame), `portainer.osk.team`, `traefik.osk.team` (dashboard)

## Files

| File | Purpose |
|------|---------|
| `docker-compose.yaml` | Traefik v3 service, binds 80/443, mounts config + acme |
| `traefik.yml` | static config (entrypoints, providers, ACME) |
| `dynamic/passthrough.yml` | the TCP SNI-passthrough routers → Windows hosts |
| `dynamic/web.yml` | HTTP routers (dashboards) + dashboard basic-auth |

Config files (`traefik.yml`, `dynamic/`) are the source of truth in git, but must be
**placed on the host** at `/mnt/containers/traefik/container-data/` and mounted by
**absolute path**. Portainer GitOps runs compose with paths relative to its own
container (`/data/compose/<id>/…`), which the Docker daemon can't resolve — so relative
`./` bind mounts silently become empty dirs (`is a directory` crash loop). Keep the host
copies in sync with this repo; Traefik's `watch: true` still hot-reloads `dynamic/` when
the host files change. `acme.json` is **not** in git (private keys) and persists on the host.

## One-time host prep (serverweb)

```bash
mkdir -p /mnt/containers/traefik/container-data/acme
touch    /mnt/containers/traefik/container-data/acme/acme.json
chmod 600 /mnt/containers/traefik/container-data/acme/acme.json
mkdir -p /mnt/containers/traefik/container-data/dynamic
```

Then copy the config from this repo onto the host (re-copy whenever they change):

```bash
scp traefik.yml   root@serverweb:/mnt/containers/traefik/container-data/traefik.yml
scp dynamic/*.yml root@serverweb:/mnt/containers/traefik/container-data/dynamic/
```

## Before deploying — edit

1. `traefik.yml`: set the real ACME `email`.
2. `dynamic/web.yml`: set a real `dash-auth` hash (`htpasswd -nbB admin 'pass'`),
   and confirm the portainer backend (add portainer to `reverseproxy-nw`, or point
   at `https://192.168.0.8:9443`).

## Deploy (Portainer GitOps)

- Decommission NPM first (it owns 80/443): stop/remove the `nginx-proxy-manager`
  stack. **Do not** delete its data yet (rollback insurance).
- Add a new Portainer **Git** stack, compose path `traefik/docker-compose.yaml`,
  same repo. Deploy.
- Test while the edge still points 443 → serverfile: from inside the LAN, hit each
  SNI name against serverweb and confirm routing/cert.
- **Flip the edge** last:
  - `TCP/80  → serverweb 192.168.0.8` (ACME http-01 + OWA http→https)
  - `TCP/443 → serverweb 192.168.0.8` (was → serverfile)
  - `UDP/443 → serverfile 192.168.0.11` (SMB over QUIC — unchanged, bypasses Traefik)

## Recommended consolidation

Once Traefik fronts 443, **retire the `kdcproxy` `:8443` hack**: route
`kdcproxy.osk.team` through Traefik on 443 (passthrough above) and revert the
client GPO `KdcProxy\ProxyServers\CORP.LOCAL` back to `<https kdcproxy.osk.team />`
(no port). Then the edge `TCP/8443` forward can be removed.

## Caveats

- **SNI required.** Passthrough routes only by SNI; any client not sending SNI
  can't be routed (all modern Outlook/ActiveSync/Work Folders do send it).
- **Passthrough backends keep their own certs.** Traefik's LE only covers the
  dashboards it terminates — **serverfile's Work Folders LE cert still renews on
  serverfile** (the Sept-3 expiry is unchanged), and Exchange/KDC-proxy keep theirs.
- **Exchange cert** on servermail must cover `mail.osk.team` + `autodiscover.osk.team`
  (and any other published names).
- **QUIC** stays a direct edge forward to serverfile; Traefik does not touch UDP/443.
- The dashboards' LE certs need public `:80` reachability for http-01. If they are
  internal-only, use a DNS-01 challenge or a default cert instead.
