b6924de728
nevermore-claude handed hrafn to infra-ops for uptime ownership. Intake: verified health independently, brought it into the inventory, and fixed one security defect found during the check. - canonicalize stacks/hrafn/ (compose.yaml + .env.example + README) - list hrafn in the ana-docker running-stacks table - README records the load-bearing bits: shm_size 1gb is required for Chromium, playwright and the base image are version-coupled, SSRF denies private targets by default, and restart:unless-stopped does NOT act on healthcheck failure Security fix applied on the host, not in this tree: /opt/docker/compose/ hrafn/.env was 0644 with a live 57-char bearer token in it, readable by every local account (verified by reading it as `nobody`). Tightened to 0600; owner unchanged so the deploy path still works, confirmed via `docker compose config` as lkraven. Container untouched and still healthy. Known gaps recorded rather than silently accepted: no CI deploy, and the image builds from an rsync'd working tree living in the compose directory, so the running image has no commit provenance.
82 lines
3.3 KiB
Markdown
82 lines
3.3 KiB
Markdown
# hrafn
|
|
|
|
Browser-fetch service — a genuine Chromium kept behind a REST API so consumers
|
|
never have to embed a browser themselves. It exists because bot-gated sites
|
|
(Reddit first) 403 every non-browser client; hrafn is the one place in the fleet
|
|
that keeps a real browser warm.
|
|
|
|
**Deploys to:**
|
|
- **ana-docker** (10.250.50.70) — internal-only, no published host port.
|
|
Consumers reach it at `http://hrafn:8080` on `traefik-net`.
|
|
|
|
Built and handed to infra-ops for uptime ownership by nevermore-claude,
|
|
2026-08-22. Upstream source: `vh/hrafn` (private gitea repo).
|
|
|
|
## Reaching it
|
|
|
|
There is **no host port** — this is deliberate (contract § Deployment). Only
|
|
containers on `traefik-net` can talk to it:
|
|
|
|
```bash
|
|
# from another container on traefik-net
|
|
curl -H "Authorization: Bearer $HRAFN_TOKEN" \
|
|
-X POST http://hrafn:8080/v1/fetch -d '{"url":"https://example.com"}'
|
|
|
|
# from the host, for diagnostics only
|
|
ssh infra-ops@10.250.50.70 \
|
|
'sudo docker run --rm --network traefik-net curlimages/curl:latest \
|
|
-s -o /dev/null -w "%{http_code}\n" http://hrafn:8080/readyz'
|
|
```
|
|
|
|
`/readyz` and the other health endpoints are unauthenticated; `POST /v1/fetch`
|
|
requires the bearer token.
|
|
|
|
## Credentials
|
|
|
|
`HRAFN_TOKEN` lives in `/opt/docker/compose/hrafn/.env` on the host and is
|
|
vaulted at `ana-docker/hrafn/bearer-token` (secrets-broker). The `.env` is
|
|
**0600** — it was 0644 on handoff, which exposed a live bearer token to every
|
|
local account on the box; tightened 2026-08-22. Keep it 0600.
|
|
|
|
## Operational notes
|
|
|
|
- **`shm_size: 1gb` is load-bearing.** Chromium crashes on some pages with
|
|
Docker's 64 MB default. Don't trim it to save memory.
|
|
- **Playwright and the base image are version-coupled.** Base is
|
|
`mcr.microsoft.com/playwright/python:v1.62.0-noble` with playwright pinned to
|
|
`1.62.0`. Bump both together or the browser binary and the client library
|
|
disagree.
|
|
- **SSRF is denied by default** — private and reserved targets are refused
|
|
unless listed in `HRAFN_PRIVATE_ALLOWLIST`, which is empty. Leave it empty
|
|
unless there is a specific internal target and a reason.
|
|
- **`restart: unless-stopped` does not act on the healthcheck.** Docker restarts
|
|
on *exit*, not on *unhealthy*. A wedged-but-running Chromium will sit there
|
|
marked unhealthy indefinitely. Beszel on this host reports container status,
|
|
so that is the alerting path.
|
|
|
|
## Deploy
|
|
|
|
**No CI deploy is wired.** As of the handoff the image builds from a working
|
|
tree rsync'd into the compose directory, so `/opt/docker/compose/hrafn/`
|
|
contains the full source (`src/`, `tests/`, `pyproject.toml`, …) rather than
|
|
just a compose file. That is a deviation from the house convention and means
|
|
the running image has no commit provenance.
|
|
|
|
Current update path:
|
|
|
|
```bash
|
|
ssh infra-ops@10.250.50.70
|
|
cd /opt/docker/compose/hrafn
|
|
# rsync the updated working tree in, then:
|
|
sudo docker compose build && sudo docker compose up -d
|
|
sudo docker compose ps # expect (healthy)
|
|
```
|
|
|
|
## Known gaps
|
|
|
|
| gap | impact | status |
|
|
|---|---|---|
|
|
| No CI deploy | manual rsync + build; no provenance for what is running | offered by nevermore-claude, not yet built |
|
|
| Source tree in the compose dir | deviates from `/opt/docker/compose/<stack>/compose.yaml` convention | needs a host-clone or registry-image deploy shape |
|
|
| No homepage entry | internal-only service, so arguably correct | intentional for now |
|