open-swe/deploy/seahaven/DEPLOYMENT.md
Adam Moussa cf57ba7aa5 feat(deploy): add Sea Haven self-hosted deployment capture
Captures the stock-LangGraph deployment of this fork at Sea Haven:
- systemd/open-swe.service: langgraph dev (:2024) + store seed ExecStartPost
- seed_store.sh: re-seeds team_settings + user_mappings (in-memory store
  resets on restart); env-parameterized, no secrets
- nginx/openswe.conf: dashboard SPA + scoped /dashboard/api proxy (security
  boundary; agent API not exposed)
- aegra/: deferred self-hosted-runtime alternative (not active on stock)
- DEPLOYMENT.md: full runbook (models, build, ingress, OAuth callback)

Secrets and internal infra identifiers are intentionally excluded (public
fork); real values live in private IT docs.
2026-06-25 17:28:34 -04:00

99 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Sea Haven — Open SWE self-hosted deployment
How this fork is deployed at Sea Haven. The runtime is the **stock LangGraph dev
server** (not the Aegra path — see [Aegra](#aegra-deferred)). Internal addresses,
ARNs, and account IDs are shown as `<PLACEHOLDERS>`; the real values live in the
private IT docs (Confluence "AWS Architecture Map") — **do not commit them to this
public fork.**
## Topology
```
GitHub / Slack ──▶ hooks.seahavenind.com ──┐
│ (public ALB :443, host+path rule)
Browser ─────────▶ openswe.seahavenind.com ─┤
▼
AWS ALB ──(Site-to-Site VPN)──▶ on-prem VM
├─ nginx :80 (dashboard SPA + /dashboard/api proxy)
└─ langgraph dev :2024 (3+ graphs + FastAPI webapp)
└─▶ LangSmith cloud sandbox (build/git/PR)
```
- The VM is **internet-closed**; all inbound rides the existing ALB over the VPN.
- **Webhooks** (`hooks.seahavenind.com`) → ALB listener rule scoped to `/webhooks/*`
only → VM `:2024`. The unauthenticated LangGraph API (`/threads`, `/runs`,
`/assistants`, `/store`) is never path-forwarded.
- **Dashboard** (`openswe.seahavenind.com`) → ALB → VM `:80` (nginx). nginx is the
security boundary: it serves the static SPA and proxies **only** `/dashboard/api/`
to `:2024`; the agent API is not reachable through it.
## VM components
| Component | What |
|---|---|
| `langgraph dev` | systemd `open-swe.service` — `--host 0.0.0.0 --port 2024 --no-browser --no-reload`. In-memory runtime. |
| Store seeding | `seed_store.sh` as `ExecStartPost` (re-seeds team settings + user mappings, which the in-memory store loses on restart). |
| nginx | `nginx/openswe.conf` — SPA from `/var/www/openswe`, proxy `/dashboard/api/` → `:2024`. |
| Postgres | present (was for the Aegra path); unused by the stock in-memory runtime. |
| swap | 8 GB swapfile — **required**: the dashboard (`ui/`) Nitro build OOMs on an 8 GB box without it. |
### Models
Model selection is **store-driven**, not env. `LLM_MODEL_ID` is effectively dead for
runtime selection; the `team_settings/default` store doc wins (then per-user profile,
then per-thread). Defaults seeded by `seed_store.sh`:
- builder: `anthropic:claude-opus-4-8` (effort `high`)
- reviewer (cross-family): `openai:gpt-5.5` (effort `high`) — `openai:gpt-4.1` is **not**
in this fork's `SUPPORTED_MODELS` (`agent/dashboard/options.py`); a raw value is
silently rewritten to gpt-5.5. Add it to `SUPPORTED_MODELS` first if you need 4.1.
- `analyzer` graph is hardcoded to the code default and ignores team settings.
## Build & deploy the dashboard (`ui/`)
`ui/` is a **TanStack Start + Nitro** app (build with `bun`, not plain Vite):
```bash
cd ui
export PATH="$HOME/.bun/bin:$PATH"
export NODE_OPTIONS=--max-old-space-size=6144 # + the 8 GB swapfile, or the build OOMs
bun install
bun run build # -> .output/public (static SPA, _shell.html)
sudo cp -r .output/public/. /var/www/openswe/ # served by nginx
```
Served as a static SPA (per `ui/vercel.json`); the Nitro `.output/server` is unused.
## Install / wire-up checklist
1. App config in `.env` (gitignored — never commit): LLM keys, GitHub App creds,
`LANGSMITH_API_KEY*` + `DEFAULT_SANDBOX_SNAPSHOT_ID` (`SANDBOX_TYPE=langsmith` — the
only sandbox provider with working in-sandbox git/gh auth), `LANGGRAPH_URL=http://127.0.0.1:2024`,
dashboard vars (`DASHBOARD_JWT_SECRET`, `CONFIGURED_ADMINS`, `DASHBOARD_*_URL=https://openswe.seahavenind.com`).
2. `systemd/open-swe.service` → `/etc/systemd/system/`, `seed_store.sh` on the VM with
`OPENSWE_*` env exported (owner login/email, default repo, model ids).
3. `nginx/openswe.conf` → `/etc/nginx/sites-available/openswe`, symlink into
`sites-enabled`, remove the default site, `nginx -t && systemctl reload nginx`.
4. AWS (real IDs in Confluence): IP target groups → `<VM_LAN_IP>:2024` and `:80`;
ALB SG **egress** rules to those ports (the ALB SG is allow-listed — health checks
time out without them); `:443` listener rules for the two hostnames; Route53 ALIAS
records → ALB. Webhook rule must stay path-scoped to `/webhooks/*`.
5. GitHub App: webhook URL `https://hooks.seahavenind.com/webhooks/github`; subscribe to
the events the install guide lists (Issue comment, PR review×2, check_run/suite,
workflow_run, status) — add **Issues** too if you want issue-title/body triggers.
6. **GitHub App OAuth callback (manual, UI-only — not API-settable):**
`https://openswe.seahavenind.com/dashboard/api/auth/callback` — without it, dashboard
login fails with a `redirect_uri` mismatch.
## Triggering
Start a task by mentioning **`@openswe`** in a GitHub issue comment (the documented
intake — the `open-swe` *label* path needs the `Issues` event subscription). The
commenter must have a `user_mappings` entry or the run is skipped.
## Aegra (deferred)
`aegra/aegra.json` + `aegra/aegra_entry.py` are the self-hosted-runtime alternative
(Apache-2.0, avoids the LangGraph-Platform Elastic license). Not active on the stock
deployment. To use: place both at the repo root, run `aegra serve` (:2026), and point
`LANGGRAPH_URL` at `:2026`. Aegra gives a Postgres-backed durable store/checkpointer,
which removes the need for `seed_store.sh` and survives restarts (paused HITL
interrupts persist).