The stock SearXNG image ships a native kagi engine; enabling it needs an
API key that must never enter git, and SearXNG does not expand env vars
in settings.yml (the live secret_key literally was '${SEARXNG_SECRET}'
until now — the app tolerated the placeholder string silently).
- settings.yml: kagi engine enabled (weight 2, kg shortcut, paging off so
exactly one API credit per aggregated query, timeout 8.0 vs 5.0 scrapers
so a slow paid query is not a wasted credit); api_key stays ${KAGI_API_KEY}.
- new scripts/searxng-render-settings.py: deploy-time renderer, allowlisted
to KAGI_API_KEY/SEARXNG_SECRET/SEARXNG_BASE_URL, fails the deploy loudly
on an unresolvable placeholder, idempotent on redeploys.
- deploy-remote.sh copy_searxng_config runs the renderer against
/opt/searxng/.env after copying settings.yml into /opt/searxng/config.
- KAGI_API_KEY placeholder added to dankclaw/.env.example (real key staged
into the host-side /opt/searxng/.env, mode 600, out of band).
- bats: engine wiring, renderer substitution, loud-failure, deployer seam.
- docs: SearXNG.md Kagi section + renderer law; Providers.md carve-out on
the no-credentials policy (paid engine acceptable when bounded + ordered).
Verified live on dankclaw: direct API probe via the gluetun netns (200,
data.search[] shape), then an aggregated query returned 29/29 results
tagged kagi, zero unresponsive engines.
Root cause of the 429s: the ip_limit rate limiter caps JSON (format!=html)
requests at API_MAX=4 per IP per hour, and that constant is hardcoded in
Python (searx/botdetection/ip_limit.py), not settable in limiter.toml. The
only bypass is pass_ip (filter_request returns before ip_limit runs).
Rootless podman strips the client IP on published-port hits, so the container
sees a slirp/bridge source (10.88.x/10.89.x), never the real client - hence
even a LAN-IP curl 429'd on /search. Expand pass_ip to loopback + the host's
private podman/slirp ranges (plus the existing LAN) so local subagents present
a trusted source and skip the rate limit entirely. All these ranges are
reachable only from the host or LAN, never the internet.
The trusted group lives in limiter.toml, not settings.yml. Add
shared/searxng/limiter.toml trusting 192.168.0.0/24 in pass_ip and
trusted_proxies, copy it into /opt/searxng/config on deploy, and correct
the SearXNG doc. New test seam: tests/searxng_limiter.bats.