Properties
category: reference
tags:
  - robot-wtf
  - v1
  - deployment
last_updated: 2026-03-15

V1-3: Gunicorn Configuration, Systemd Units, and Deployment

Branch: feat/v1-3-gunicorn-systemd in robot.wtf repo Commit: 5eb51c6

Deliverables

Entry Points (app/)

File Service Port Server Description
wsgi.py Otterwiki 8000 Gunicorn Wraps Flask app in TenantResolver + ManagementMiddleware. Stub when otterwiki not installed.
api_server.py Platform API 8002 Gunicorn ManagementMiddleware + /api/internal/check-slug for Caddy on-demand TLS + static file serving.
auth_server.py Auth 8003 Gunicorn ATProto OAuth stubs (501). Serves client-metadata.json, AS metadata, JWKS.
mcp_entry.py MCP Sidecar 8001 Uvicorn FastMCP stub (ASGI). Serves /.well-known/oauth-protected-resource.

Shared Configuration

  • app/gunicorn.conf.py — Bind from GUNICORN_BIND env var, workers = min(2*CPU+1, 4), timeout 30s, access log to stdout.

Systemd Units (ansible/roles/deploy/files/)

All services run as robot user, WorkingDirectory=/srv/app/src.

  • robot-otterwiki.service — Gunicorn, port 8000, MULTI_TENANT=true
  • robot-api.service — Gunicorn, port 8002
  • robot-auth.service — Gunicorn, port 8003
  • robot-mcp.service — Uvicorn, port 8001

Ansible Deploy Role

ansible/roles/deploy/ with tasks + handlers:

  • Syncs app/ to /srv/app/src/app/ via rsync
  • Installs requirements.txt into /srv/app/venv
  • Copies systemd units, enables and starts all four services
  • Handlers restart services on code change
  • TODO: otterwiki pip install from fork (placeholder comments)

Requirements Added

  • flask>=3.0.0 — needed by api_server, auth_server, wsgi stub
  • uvicorn>=0.29.0 — needed by MCP sidecar

Environment Variables

Variable Used By Default
MULTI_TENANT wsgi.py (unset = disabled)
ROBOT_DB_PATH wsgi, api /srv/data/robot.db
SIGNING_KEY_PATH wsgi, api, auth /srv/data/signing_key.pem
PLATFORM_DOMAIN all robot.wtf
WIKI_BASE wsgi /srv/data/wikis
GUNICORN_BIND gunicorn.conf 0.0.0.0:8000
ROBOT_STATIC_DIR api /srv/static
MCP_PORT mcp 8001

Testing

  • All 55 existing tests pass (no regressions)
  • auth_server: stub routes return 501, metadata routes return 200 with valid JSON
  • mcp_entry: ASGI stub serves oauth-protected-resource (200) and default (501)
  • wsgi.py: loads stub Flask app when otterwiki not installed
  • api_server: check-slug returns 404 for unknown domains/slugs
  • Systemd units validated: correct User, WorkingDirectory, ExecStart, ports
  • Ansible YAML validated: 7 tasks, 2 handlers