Blame

014939 Claude (MCP) 2026-03-15 04:58:54
[mcp] Document V1-3 deployment deliverables
1
---
2
category: reference
3
tags:
4
- robot-wtf
5
- v1
6
- deployment
7
last_updated: 2026-03-15
8
---
9
10
# V1-3: Gunicorn Configuration, Systemd Units, and Deployment
11
12
**Branch:** `feat/v1-3-gunicorn-systemd` in `robot.wtf` repo
13
**Commit:** `5eb51c6`
14
15
## Deliverables
16
17
### Entry Points (app/)
18
19
| File | Service | Port | Server | Description |
20
|------|---------|------|--------|-------------|
21
| `wsgi.py` | Otterwiki | 8000 | Gunicorn | Wraps Flask app in TenantResolver + ManagementMiddleware. Stub when otterwiki not installed. |
22
| `api_server.py` | Platform API | 8002 | Gunicorn | ManagementMiddleware + `/api/internal/check-slug` for Caddy on-demand TLS + static file serving. |
23
| `auth_server.py` | Auth | 8003 | Gunicorn | ATProto OAuth stubs (501). Serves client-metadata.json, AS metadata, JWKS. |
24
| `mcp_entry.py` | MCP Sidecar | 8001 | Uvicorn | FastMCP stub (ASGI). Serves `/.well-known/oauth-protected-resource`. |
25
26
### Shared Configuration
27
28
- `app/gunicorn.conf.py` — Bind from `GUNICORN_BIND` env var, workers = min(2*CPU+1, 4), timeout 30s, access log to stdout.
29
30
### Systemd Units (ansible/roles/deploy/files/)
31
32
All services run as `robot` user, `WorkingDirectory=/srv/app/src`.
33
34
- `robot-otterwiki.service` — Gunicorn, port 8000, MULTI_TENANT=true
35
- `robot-api.service` — Gunicorn, port 8002
36
- `robot-auth.service` — Gunicorn, port 8003
37
- `robot-mcp.service` — Uvicorn, port 8001
38
39
### Ansible Deploy Role
40
41
`ansible/roles/deploy/` with tasks + handlers:
42
- Syncs `app/` to `/srv/app/src/app/` via rsync
43
- Installs `requirements.txt` into `/srv/app/venv`
44
- Copies systemd units, enables and starts all four services
45
- Handlers restart services on code change
46
- **TODO:** otterwiki pip install from fork (placeholder comments)
47
48
### Requirements Added
49
50
- `flask>=3.0.0` — needed by api_server, auth_server, wsgi stub
51
- `uvicorn>=0.29.0` — needed by MCP sidecar
52
53
## Environment Variables
54
55
| Variable | Used By | Default |
56
|----------|---------|---------|
57
| MULTI_TENANT | wsgi.py | (unset = disabled) |
58
| ROBOT_DB_PATH | wsgi, api | /srv/data/robot.db |
59
| SIGNING_KEY_PATH | wsgi, api, auth | /srv/data/signing_key.pem |
60
| PLATFORM_DOMAIN | all | robot.wtf |
61
| WIKI_BASE | wsgi | /srv/data/wikis |
62
| GUNICORN_BIND | gunicorn.conf | 0.0.0.0:8000 |
63
| ROBOT_STATIC_DIR | api | /srv/static |
64
| MCP_PORT | mcp | 8001 |
65
66
## Testing
67
68
- All 55 existing tests pass (no regressions)
69
- auth_server: stub routes return 501, metadata routes return 200 with valid JSON
70
- mcp_entry: ASGI stub serves oauth-protected-resource (200) and default (501)
71
- wsgi.py: loads stub Flask app when otterwiki not installed
72
- api_server: check-slug returns 404 for unknown domains/slugs
73
- Systemd units validated: correct User, WorkingDirectory, ExecStart, ports
74
- Ansible YAML validated: 7 tasks, 2 handlers