#!/usr/bin/env bash # Is SearXNG actually searching? # # scripts/searxng-health.sh check the fleet instance # scripts/searxng-health.sh --url http://host:9996 # # ⚠ THIS EXISTS BECAUSE /healthz CANNOT ANSWER THE QUESTION. On 2026-09-03 the # old ana-docker instance was found returning ZERO results for every query, for # an unknown number of weeks, while its container reported `healthy` for 7 days # straight and its dashboard card was green. /healthz proves the web app # answers; it says nothing about whether a single engine works. # # SearXNG rots quietly: engine scrapers break as upstream sites change markup, # and the project ships near-daily releases to keep up. An instance pinned to # `:latest` that nobody re-pulls is frozen at whatever `latest` meant on the day # it was created — that one was 4.5 months behind. # # So this asserts the property, not the mechanism: RESULTS > 0. set -euo pipefail URL="http://10.100.50.40:9996" [[ "${1:-}" == "--url" ]] && { URL="${2:?--url needs a value}"; shift 2; } fail=0 say() { printf '%s\n' "$*"; } say "── ${URL}" # 1. reachable at all? An unreachable instance is an OUTAGE, not "no results". if ! ver=$(curl -s -m 15 "$URL/config" | python3 -c 'import sys,json;print(json.load(sys.stdin)["version"])' 2>/dev/null); then say " ✗ unreachable — this is an outage, not an empty index" exit 1 fi say " version: $ver" # 2. is that version current? `:latest` is only latest at pull time. if latest=$(curl -s -m 20 "https://hub.docker.com/v2/repositories/searxng/searxng/tags/?page_size=1&ordering=last_updated" \ | python3 -c 'import sys,json;print(json.load(sys.stdin)["results"][0]["last_updated"][:10])' 2>/dev/null); then say " registry :latest last pushed: $latest (running build predates any later push)" fi # 3. THE CHECK THAT MATTERS. Three unrelated queries, because one query # returning nothing can legitimately mean nothing matched; three cannot. for q in "proxmox backup" "python asyncio" "linux kernel"; do out=$(curl -s -m 45 --get --data-urlencode "q=$q" --data "format=json" "$URL/search" 2>/dev/null) || out="" n=$(printf '%s' "$out" | python3 -c 'import sys,json;print(len(json.load(sys.stdin).get("results") or []))' 2>/dev/null || echo 0) errs=$(printf '%s' "$out" | python3 -c 'import sys,json;print(",".join(e[0] for e in (json.load(sys.stdin).get("unresponsive_engines") or [])) or "-")' 2>/dev/null || echo "?") if [[ "$n" -gt 0 ]]; then say " ✓ '$q' -> $n results (failed engines: $errs)" else say " ✗ '$q' -> ZERO results (failed engines: $errs)" fail=1 fi done if (( fail )); then say "" say " ✗ SearXNG answers HTTP but finds nothing. Almost always staleness:" say " ssh infra-ops@nh3-docker 'cd /opt/docker/compose/searxng && \\" say " sudo docker compose pull && sudo docker compose up -d'" exit 1 fi say " ✓ searching"