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.
57 lines
3.2 KiB
Bash
57 lines
3.2 KiB
Bash
# hrafn — fleet browser-fetch service. Copy to `.env` and set HRAFN_TOKEN.
|
|
# This file is the canonical config surface (contract § Configuration).
|
|
|
|
# ── auth (required) ──────────────────────────────────────────────────
|
|
# Shared bearer token required on POST /v1/fetch. Provision via the
|
|
# vault (secrets-broker); rotation = rotate the vault entry + recycle.
|
|
HRAFN_TOKEN=CHANGE_ME
|
|
|
|
# ── network ──────────────────────────────────────────────────────────
|
|
HRAFN_BIND=0.0.0.0
|
|
HRAFN_PORT=8080
|
|
|
|
# ── fetch limits ─────────────────────────────────────────────────────
|
|
# Max post-content-decoding body bytes of the main navigation response;
|
|
# oversize is rejected (413), never truncated. Both raw and dom modes.
|
|
HRAFN_MAX_BODY_BYTES=10485760
|
|
# Whole-request budget default + hard cap (seconds). timeout_s INCLUDES
|
|
# politeness-queue wait. A request may not exceed the cap.
|
|
HRAFN_DEFAULT_TIMEOUT_S=30
|
|
HRAFN_MAX_TIMEOUT_S=120
|
|
# Default dom-mode wait state: load | domcontentloaded | networkidle.
|
|
HRAFN_DEFAULT_WAIT_UNTIL=domcontentloaded
|
|
|
|
# ── concurrency + browser lifecycle ──────────────────────────────────
|
|
# Global in-flight cap (acquired on leaving the politeness queue).
|
|
HRAFN_CONCURRENCY=4
|
|
# Recycle the browser after N requests or M seconds of age.
|
|
HRAFN_BROWSER_MAX_REQUESTS=200
|
|
HRAFN_BROWSER_MAX_AGE_S=1800
|
|
|
|
# ── politeness gate (per registrable domain) ─────────────────────────
|
|
# Minimum seconds between fetch STARTS per domain (0 = no throttle).
|
|
HRAFN_DOMAIN_MIN_INTERVAL_S=5
|
|
# Per-domain interval overrides: comma-separated domain=seconds pairs.
|
|
# HRAFN_DOMAIN_OVERRIDES=reddit.com=10, example.com=2
|
|
HRAFN_DOMAIN_OVERRIDES=
|
|
# Waiting-request queue depth per domain (excludes the in-flight fetch;
|
|
# 0 = no queue). Overflow → 429 queue_full.
|
|
HRAFN_QUEUE_DEPTH=4
|
|
# Upstream Retry-After (on 429/503) embargoes the domain; clamped to
|
|
# this many seconds so a hostile Retry-After can't lock a domain for a day.
|
|
HRAFN_MAX_EMBARGO_S=300
|
|
|
|
# ── SSRF ─────────────────────────────────────────────────────────────
|
|
# Private/reserved targets are DENIED by default. To permit specific
|
|
# internal targets, list IPs/CIDRs (comma-separated). Empty = deny all
|
|
# private ranges (the safe default). Write entries in IPv4 form for IPv4
|
|
# targets: resolved IPv4-mapped IPv6 addresses are canonicalised to IPv4
|
|
# before matching, so an IPv6-mapped CIDR (::ffff:10.0.0.0/120) will not
|
|
# match them.
|
|
# HRAFN_PRIVATE_ALLOWLIST=10.0.0.0/8, 192.168.1.5
|
|
HRAFN_PRIVATE_ALLOWLIST=
|
|
|
|
# ── live smoke (dev only) ────────────────────────────────────────────
|
|
# Set to 1 to enable the opt-in Reddit RSS smoke test. Off in CI.
|
|
# HRAFN_LIVE_SMOKE=1
|