david.boone
employee
7db1aa0b77477d43
Normalized: david boone
github
linear
toggl
Entity Properties (gold employee table)
employee_id
7db1aa0b77477d43
name
David Boone
email
david.boone@morelandconnect.com
nickname
Boone
doer
0.85
squad
Discovery
loaded_cost_hr
83.08
canonical_metadata
—
created_at
2026-02-12 05:22:47.072254+00:00
updated_at
2026-02-12 05:22:47.072254+00:00
62%
Separation Confidence
How distinct this entity is from others. Higher means no close matches existed when it was created. Lower means a near-match was rejected just below the 80% threshold — worth reviewing.
Clearly distinct from other entities
100%
Avg Match Confidence
The average confidence score across all active source mappings. Shows overall quality of linkage between source records and this canonical entity.
Strong source linkage
Source Mappings (3)
| Source | Source ID | Display Name | Confidence | Method | Status | Actions | |
|---|---|---|---|---|---|---|---|
| github | 3656386 | - | 1.00 | exact | Active |
|
|
|
Source Record
bronze_github_pull_requests
id
3598225760
number
23
state
closed
title
Non-prod deploy: docker stack, OIDC pipeline, and WSL host bootstrap
body
## Summary
Brings the entire non-prod deploy story onto `main`. Up until now this work has been living on `qa` only. This PR consolidates it so `main` has the source of truth, and so future prod work can branch from a complete picture.
The PR is two layers stacked together:
1. **Eight commits already on `qa`** (`db23aeb`..`df50f52`) that built and iteratively fixed the Docker + Caddy + Azure pipeline.
2. **One new commit** (`cd67710`) that finishes the host-side story by registering WSL as a Windows service via NSSM and replacing mirrored networking (unsupported on Server 2025) with `netsh portproxy`.
## What lands here
### Compose stack + reverse proxy
- `docker-compose.prod.yml` — api, web, sync-worker, redis, caddy.
- `deploy/Caddyfile` — single-hostname routing: `/` → web, `/v1`, `/api/auth`, `/docs`, `/health` → api, `/git` → forgejo on the host.
- `deploy/.env.host.example` — host-only template; everything else comes from Key Vault.
- Hardened multi-stage Dockerfiles for api / web / sync-worker.
### CI/CD pipeline
- `.github/workflows/deploy-nonprod.yml` — push to `qa` triggers OIDC build (GitHub-hosted) → ACR push, then deploy on a self-hosted runner inside WSL on `azuwsdrepport01`.
- `deploy/vm-deploy.sh` — runs on the VM, uses the VM's system-assigned managed identity for ACR + Key Vault, runs Prisma migrations against Azure SQL MI, rolls the compose stack.
### Auth model
Two separate Azure identities, deliberately split:
- **Entra app** (federated cred subject `…:environment:staging`, no client secret) → `AcrPush` + `Virtual Machine Contributor`. Used by GitHub Actions only.
- **VM system-assigned MI** → `AcrPull` + `Key Vault Secrets User`. Used by `vm-deploy.sh` after `az login --identity`.
A GitHub Actions compromise can push images and run the workflow, but cannot read Key Vault. A VM compromise can read secrets, but cannot push images.
### NEW in this PR — Windows host bootstrap (`deploy/windows/`)
Without these, a reboot of the VM kills everything until someone logs in. The non-prod VM is Windows Server 2025 + WSL2, where:
- WSL is **not** a service — it shuts down 60s after the last process exits.
- WSL2 mirrored networking (the usual fix for LAN exposure) is **unsupported** on Server 2025 (microsoft/WSL#12569 — HNS rejects with \\`Mirrored Networking can only be enabled on Client or WVD OS\\`).
Three idempotent PowerShell scripts, run once per VM:
| Script | What it does |
|---|---|
| `01-install-nssm.ps1` | Downloads NSSM 2.24.1 to `C:\\Tools\\nssm\\nssm.exe` (uses fawno/nssm.cc GitHub mirror; nssm.cc itself has been flaky). |
| `02-register-wsl-keepalive.ps1` | Registers `OateyWslKeepAlive` Windows service: `wsl.exe -d Ubuntu -u root sleep infinity`, runs as `oatey\\booned`. Interactive password prompt; password is read as `SecureString`, used once, cleared. |
| `03-configure-portproxy.ps1` | `netsh interface portproxy add v4tov4 listenport=80,443 listenaddress=0.0.0.0 connectport=80,443 connectaddress=localhost` + Windows Firewall inbound rule. Persists in the registry, survives reboots. |
`connectaddress=localhost` deliberately exploits WSL2's localhost forwarding so we never have to chase WSL's dynamic eth0 IP.
## Architecture (post-merge)
\\`\\`\\`
LAN client
│ http://azuwsdrepport01.private.oatey.com/...
▼
Windows host (Server 2025)
├─ OateyWslKeepAlive (NSSM) → wsl.exe -d Ubuntu -u root sleep ∞
└─ netsh portproxy :80,:443 → localhost (WSL)
▼
WSL2 Ubuntu (systemd)
├─ docker.service (restart: always containers)
│ api / web / sync-worker / caddy / redis / forgejo
└─ actions.runner.*.service ← polls GitHub for jobs
▼ when a deploy job arrives
azure/login@v2 OIDC → Entra app token (AcrPush + VM Contributor)
deploy/vm-deploy.sh
├─ az login --identity → VM MI (AcrPull + KV Secrets User)
├─ az acr login → docker compose pull
├─ az keyvault secret show → secrets.env (mode 600)
├─ prisma migrate deploy
└─ docker compose up -d
\\`\\`\\`
## Test plan
- [x] Build job succeeds on GitHub-hosted runner; images push to ACR.
- [x] Deploy job runs on self-hosted `oatey-nonprod` runner; compose stack comes up healthy.
- [x] Prisma migrations apply against Azure SQL MI (`oatey_qa` database) using the rotated `sqlmiadmin` credentials in Key Vault.
- [x] `OateyWslKeepAlive` service installs, runs as `oatey\\booned`, keeps WSL alive.
- [x] `netsh portproxy` rules let LAN clients hit `http://azuwsdrepport01.private.oatey.com/{health/ready,/,/docs,/git/}` — all 200.
- [x] Self-hosted runner registered as systemd unit (`actions.runner.*.service`, `active (running)`, "Listening for Jobs").
- [ ] **Reboot test**: pending. Need to schedule a `Restart-Computer` and confirm everything (WSL, docker, runner, Caddy) comes back up without an interactive login.
## Operator runbook
`docs/deployment/non-prod.md` and `deploy/windows/README.md` are the source of truth. The runbook now reflects:
- Windows host bootstrap via `deploy/windows/` (replacing the old "enable mirrored networking" instructions).
- Self-hosted runner registration via `actions/runner` `svc.sh install`.
- Operational cheat sheet using `gh workflow run` and direct SSH (instead of the abandoned `az vm run-command`).
- Trimmed environment-variable list to match what the workflow actually consumes.
Made with [Cursor](https://cursor.com)
created_at
2026-04-28 19:59:53+00:00
updated_at
2026-04-29 12:57:16+00:00
closed_at
2026-04-29 12:57:15+00:00
merged_at
2026-04-29 12:57:15+00:00
author_id
3656386
author_login
dboone31
repo_name
moreland-connect/oatey-rep-portal-monorepo
repo_full_name
—
base_ref
—
head_ref
—
additions
—
deletions
—
changed_files
—
_dlt_meta
—
|
|||||||
| linear | 9b742b12-a5d8-49a6-8eb0-f363b37a523a | - | 1.00 | exact | Active |
|
|
|
Source Record
bronze_linear_users
id
9b742b12-a5d8-49a6-8eb0-f363b37a523a
name
david.boone@morelandconnect.com
display_name
david.boone
email
david.boone@morelandconnect.com
active
—
created_at
—
_dlt_meta
—
|
|||||||
| toggl | 12405071 | - | 1.00 | exact | Active |
|
|