STAGE 03 / ONLINE
Homelab P3
Runner P2–3
~64 credits
Services.
Things start running. Plex, Gitea, Langfuse, SearXNG come up on the homelab. The runner's worker drains the queue end-to-end. Streaming chat appears in the browser at control.davidcockson.com.
Goal
Two services live: homelab stack & runner web/worker.
SearXNG on Tailscale must be reachable from Contabo by the end of this stage — that is the gate into Stage 04.
Exit criteria
- All Prod-Services containers healthy (Plex, Gitea, Syncthing, Langfuse stack, nginx, SearXNG)
- Grafana datasource on Dev-Learning VM
- SearXNG reachable from Contabo via Tailscale IP
- Job submitted to
_queue/appears inrunner-outputs/ - Discord pings on success/failure; trace visible in Langfuse
- Streaming chat works in browser; SQLite history persists
Job lifecycle, end-to-end
Files & commands
Prod-Services compose
- Plex
- Gitea + PostgreSQL
- Syncthing (LAN only)
- Portainer · Uptime Kuma
- nginx reverse proxy
- SearXNG · Tailscale-reachable ★
- Langfuse · web + worker + ClickHouse + MinIO + PG
Dev-Learning + Ansible roles
- Prometheus
- Grafana with Prometheus datasource
roles/prod-servicesdeployroles/dev-learningdeploy- Uptime Kuma monitors all services
- Basic Grafana homelab dashboard
- ★ Verify SearXNG from Contabo
worker/poller.py· atomic ops · 2s mod-guard · startup re-queueworker/executor.py· frontmatter dispatch · catches all exceptionsjob_types/text.py· single provider call → output filejob_types/chain.py· sequential, context accumulatesjob_types/research.py· stub for Stage 04 MCP- Discord notifications · success + failure (with reason)
@observeon every provider calltests/test_worker/*· atomic ops · routing · happy + failure
web/app.py· FastAPI factory + lifespanroutes/health.py· Ollama, Davas, Qdrant, Langfuseroutes/chat.py· SSE stream · model picker · historyroutes/queue.py· submit · list active · list recent- SQLite chat history · single table
- HTMX UI · chat panel + queue panel · Davas indicator
tests/test_web/*
## Homelab — Phase 3 Compose Prod-Services and Dev-Learning stacks per spec Phase 3. SearXNG must publish on its Tailscale IP. Add an Ansible task that curls SearXNG from Contabo to verify reachability. ## Runner — Phase 2 (Worker) Build worker per spec. Poller uses shutil.move only. Add the 2-second modification guard for Syncthing. On startup, scan _active/ and re-queue. Each job_type has its own pytest file. ## Runner — Phase 3 (Web + Chat) Build the FastAPI factory + 3 routes per spec. SSE streaming for chat — research the asyncio + httpx chunk pattern before writing. HTMX UI in services/web/static — keep CSS minimal, design later.
What to watch
CRITICAL
Tailscale IP gate
SearXNG must answer on Tailscale before Stage 04 starts. Test with curl from Contabo.
QUIRK
Syncthing partials
Files mid-sync can read truncated. The 2s modification guard exists for this — do not skip.
WATCH
SSE in async
No sync calls inside the streaming generator. httpx.AsyncClient + aiter_lines, not requests.