Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save DeadBranches/473b32bdc332ffcc40b483f191d26f7f to your computer and use it in GitHub Desktop.

Select an option

Save DeadBranches/473b32bdc332ffcc40b483f191d26f7f to your computer and use it in GitHub Desktop.
SearXNG web search setup for OpenClaw

SearXNG Web Search Setup for OpenClaw

Run these steps on the OpenClaw host to give the bot free, unlimited web search (no API key needed).

1. Create SearXNG config

mkdir -p /opt/searxng
SECRET=$(openssl rand -hex 32)
cat > /opt/searxng/settings.yml << EOF
use_default_settings: true

server:
  secret_key: "$SECRET"
  bind_address: "0.0.0.0"
  port: 8080
  limiter: false

search:
  safe_search: 0
  formats:
    - html
    - json

outgoing:
  request_timeout: 5.0
EOF

2. Run SearXNG container

docker run -d \
  --name searxng \
  --restart unless-stopped \
  --network bridge \
  -p 127.0.0.1:8888:8080 \
  -v /opt/searxng/settings.yml:/etc/searxng/settings.yml:ro \
  --memory=256m \
  --cpus=0.5 \
  searxng/searxng:latest

Verify it works:

sleep 3 && curl -s 'http://127.0.0.1:8888/search?q=test&format=json' | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['results'][0]['title'])"

3. Install websearch scripts

Host version (uses localhost:8888)

cat > /usr/local/bin/websearch << 'SCRIPT'
#!/bin/bash
set -euo pipefail
if [ $# -eq 0 ]; then echo "Usage: websearch <query> [--count N]"; exit 1; fi
COUNT=5; QUERY=""
while [ $# -gt 0 ]; do
  case "$1" in --count) COUNT="$2"; shift 2 ;; *) QUERY="${QUERY:+$QUERY }$1"; shift ;; esac
done
[ -z "$QUERY" ] && echo "Error: No query" && exit 1
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
RESULT=$(curl -s --max-time 15 "http://127.0.0.1:8888/search?q=${ENCODED}&format=json&categories=general")
[ -z "$RESULT" ] && echo "Error: No response" && exit 1
python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
count = int(sys.argv[1])
for i, r in enumerate(data.get('results', [])[:count], 1):
    title = r.get('title', 'No title')
    url = r.get('url', '')
    content = r.get('content', 'No description').replace('\n', ' ')
    if len(content) > 200: content = content[:200] + '...'
    print(f'[{i}] {title}\n    {url}\n    {content}\n')
suggestions = data.get('suggestions', [])[:3]
if suggestions: print('Related: ' + ', '.join(suggestions))
" "$COUNT" <<< "$RESULT"
SCRIPT
chmod +x /usr/local/bin/websearch

Sandbox version (auto-discovers SearXNG on Docker bridge)

Important: Uses Python for JSON parsing, NOT jq. Host jq is compiled against glibc 2.38 but the OpenClaw sandbox runs Debian Bookworm with older glibc — bind-mounted jq will crash with GLIBC_2.38 not found.

cat > /usr/local/bin/websearch-sandbox << 'SCRIPT'
#!/bin/bash
set -euo pipefail
if [ $# -eq 0 ]; then echo "Usage: websearch <query> [--count N]"; exit 1; fi
COUNT=5; QUERY=""
while [ $# -gt 0 ]; do
  case "$1" in --count) COUNT="$2"; shift 2 ;; *) QUERY="${QUERY:+$QUERY }$1"; shift ;; esac
done
[ -z "$QUERY" ] && echo "Error: No query" && exit 1
SEARX_URL=""
for host in "172.17.0.2:8080" "172.17.0.3:8080" "172.17.0.4:8080" "172.17.0.5:8080"; do
  if curl -s --max-time 2 "http://$host/" >/dev/null 2>&1; then SEARX_URL="http://$host"; break; fi
done
[ -z "$SEARX_URL" ] && echo "Error: Cannot reach SearXNG" && exit 1
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
RESULT=$(curl -s --max-time 15 "${SEARX_URL}/search?q=${ENCODED}&format=json&categories=general")
[ -z "$RESULT" ] && echo "Error: No response" && exit 1
python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
count = int(sys.argv[1])
for i, r in enumerate(data.get('results', [])[:count], 1):
    title = r.get('title', 'No title')
    url = r.get('url', '')
    content = r.get('content', 'No description').replace('\n', ' ')
    if len(content) > 200: content = content[:200] + '...'
    print(f'[{i}] {title}\n    {url}\n    {content}\n')
suggestions = data.get('suggestions', [])[:3]
if suggestions: print('Related: ' + ', '.join(suggestions))
" "$COUNT" <<< "$RESULT"
SCRIPT
chmod +x /usr/local/bin/websearch-sandbox

4. Add bind mount to openclaw.json

Add this string to the agents.defaults.sandbox.docker.binds array:

"/usr/local/bin/websearch-sandbox:/usr/local/bin/websearch:ro"

5. Restart and verify (MANDATORY)

Bind mounts are set at container creation time. Old sandbox containers will NOT pick up new mounts.

# Remove old sandbox containers
docker ps -q --filter 'name=openclaw-sbx' | xargs -r docker rm -f

# Restart OpenClaw
systemctl restart openclaw

# Send a test message via Telegram to trigger sandbox creation
# Then wait for the new container:
docker ps --filter 'name=openclaw-sbx'

# Verify INSIDE the new sandbox (not from host!)
docker exec <container-name> which websearch
docker exec <container-name> websearch "test query" --count 2

# Only declare success after the above returns actual search results

Gotchas

  • Do NOT use jq in sandbox scripts — host jq has glibc mismatch with sandbox
  • Container IPs shift on restart — sandbox script auto-tries 172.17.0.2-5
  • Bind mounts are set at container creation — old containers must be removed and recreated
  • Test from inside the sandbox, not from the host — network access differs
  • SearXNG aggregates Google, DuckDuckGo, Brave, Bing, Wikipedia — no API keys, no rate limits, no reCAPTCHA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment