# 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