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.
66 lines
2.6 KiB
Bash
66 lines
2.6 KiB
Bash
#!/usr/bin/env bats
|
|
# Seam: SearXNG limiter / trusted-group configuration.
|
|
# The trusted group lives in shared/searxng/limiter.toml (NOT in settings.yml).
|
|
# It must be valid TOML, trust the homelab LAN, and be copied into place by
|
|
# deploy.sh so the deployed /etc/searxng/limiter.toml matches the repo.
|
|
|
|
load helpers
|
|
|
|
@test "shared/searxng/limiter.toml exists" {
|
|
[ -f "$REPO_ROOT/shared/searxng/limiter.toml" ]
|
|
}
|
|
|
|
@test "limiter.toml is valid TOML" {
|
|
python3 - "$REPO_ROOT/shared/searxng/limiter.toml" <<'PYEOF'
|
|
import sys, tomllib
|
|
with open(sys.argv[1], "rb") as fh:
|
|
tomllib.load(fh)
|
|
PYEOF
|
|
}
|
|
|
|
@test "limiter.toml trusts the homelab LAN in pass_ip" {
|
|
python3 - "$REPO_ROOT/shared/searxng/limiter.toml" <<'PYEOF'
|
|
import sys, tomllib
|
|
with open(sys.argv[1], "rb") as fh:
|
|
data = tomllib.load(fh)
|
|
ip_lists = data.get("botdetection", {}).get("ip_lists", {})
|
|
pass_ip = ip_lists.get("pass_ip", [])
|
|
assert "192.168.0.0/24" in pass_ip, f"LAN missing from pass_ip; got {pass_ip}"
|
|
PYEOF
|
|
}
|
|
|
|
@test "limiter.toml trusts the LAN as a proxy for X-Forwarded-For" {
|
|
python3 - "$REPO_ROOT/shared/searxng/limiter.toml" <<'PYEOF'
|
|
import sys, tomllib
|
|
with open(sys.argv[1], "rb") as fh:
|
|
data = tomllib.load(fh)
|
|
bd = data.get("botdetection", {})
|
|
proxies = bd.get("trusted_proxies", [])
|
|
assert "192.168.0.0/24" in proxies, f"LAN missing from trusted_proxies; got {proxies}"
|
|
PYEOF
|
|
}
|
|
|
|
@test "limiter.toml pass_ip covers local + podman sources (loopback + slirp/bridge)" {
|
|
# Rootless podman strips the client IP on published-port hits: the container
|
|
# sees a slirp/bridge source (10.88.x, 10.89.x), never the real client. And
|
|
# the JSON rate limit (API_MAX=4/hour, hardcoded) 429s any non-passed IP.
|
|
# So local subagents must present a pass_ip source. Loopback + the host's
|
|
# private podman ranges are trusted; they are reachable only from the host.
|
|
python3 - "$REPO_ROOT/shared/searxng/limiter.toml" <<'PYEOF'
|
|
import sys, tomllib
|
|
with open(sys.argv[1], "rb") as fh:
|
|
data = tomllib.load(fh)
|
|
pass_ip = data.get("botdetection", {}).get("ip_lists", {}).get("pass_ip", [])
|
|
need = ["127.0.0.1/32", "::1", "192.168.0.0/24", "10.88.0.0/16", "10.89.0.0/16"]
|
|
missing = [n for n in need if n not in pass_ip]
|
|
assert not missing, f"pass_ip missing {missing}; got {pass_ip}"
|
|
PYEOF
|
|
}
|
|
|
|
@test "deploy.sh copies limiter.toml into /opt/searxng/config" {
|
|
grep -q "shared/searxng/limiter.toml" "$REPO_ROOT/scripts/deploy.sh" \
|
|
|| { echo "deploy.sh does not copy shared/searxng/limiter.toml"; return 1; }
|
|
grep -q "/opt/searxng/config/limiter.toml" "$REPO_ROOT/scripts/deploy.sh" \
|
|
|| { echo "deploy.sh does not place limiter.toml at /opt/searxng/config"; return 1; }
|
|
}
|