Device= in [Container] is a [Volume]-group key; the container device key is AddDevice= (maps to podman --device). Update the /dev/net/tun device line and the test seam.
102 lines
4.3 KiB
Bash
102 lines
4.3 KiB
Bash
#!/usr/bin/env bats
|
|
# Seam: SearXNG stack boot-persistence via Quadlet.
|
|
#
|
|
# The gluetun + searxng + valkey trio runs under systemd as quadlet units so it
|
|
# survives a reboot (the podman-compose deployment did not). The crux: searxng
|
|
# must reuse gluetun's network namespace (Network=gluetun.container) so its
|
|
# outbound search traffic exits the WireGuard tunnel, while still reaching valkey
|
|
# by name on the shared searxng network.
|
|
|
|
load helpers
|
|
|
|
QDIR="$REPO_ROOT/dankclaw/searxng/quadlet"
|
|
|
|
@test "all four quadlet files exist" {
|
|
[ -f "$QDIR/searxng.network" ]
|
|
[ -f "$QDIR/gluetun.container" ]
|
|
[ -f "$QDIR/searxng.container" ]
|
|
[ -f "$QDIR/searxng-valkey.container" ]
|
|
}
|
|
|
|
@test "searxng reuses gluetun's network namespace (exits the tunnel)" {
|
|
grep -qE '^Network=gluetun\.container' "$QDIR/searxng.container" \
|
|
|| { echo "searxng.container must set Network=gluetun.container"; return 1; }
|
|
}
|
|
|
|
@test "searxng publishes no port (the port lives on gluetun)" {
|
|
if grep -qE '^PublishPort=' "$QDIR/searxng.container"; then
|
|
echo "searxng.container must not set PublishPort (it shares gluetun's netns)"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
@test "gluetun publishes SearXNG on 8081:8080" {
|
|
grep -qE '^PublishPort=8081:8080' "$QDIR/gluetun.container" \
|
|
|| { echo "gluetun.container must publish 8081:8080"; return 1; }
|
|
}
|
|
|
|
@test "gluetun has NET_ADMIN and the /dev/net/tun device" {
|
|
grep -qE '^AddCapability=NET_ADMIN' "$QDIR/gluetun.container" \
|
|
|| { echo "gluetun.container must set AddCapability=NET_ADMIN"; return 1; }
|
|
grep -qE '^AddDevice=/dev/net/tun' "$QDIR/gluetun.container" \
|
|
|| { echo "gluetun.container must set AddDevice=/dev/net/tun"; return 1; }
|
|
}
|
|
|
|
@test "valkey joins searxng.network and is reachable by the name valkey" {
|
|
grep -qE '^Network=searxng\.network' "$QDIR/searxng-valkey.container" \
|
|
|| { echo "searxng-valkey.container must set Network=searxng.network"; return 1; }
|
|
grep -qE '^NetworkAlias=valkey' "$QDIR/searxng-valkey.container" \
|
|
|| { echo "searxng-valkey.container must set NetworkAlias=valkey"; return 1; }
|
|
}
|
|
|
|
@test "gluetun joins searxng.network so searxng (in its netns) can reach valkey" {
|
|
grep -qE '^Network=searxng\.network' "$QDIR/gluetun.container" \
|
|
|| { echo "gluetun.container must set Network=searxng.network"; return 1; }
|
|
}
|
|
|
|
@test "searxng.network pins a subnet that limiter.toml trusts as a proxy" {
|
|
# The pinned subnet must appear in limiter.toml trusted_proxies so the value
|
|
# stays stable across reboots (rootless podman otherwise reassigns the CIDR).
|
|
local subnet
|
|
subnet=$(grep -oE '^Subnet=[0-9./:]+' "$QDIR/searxng.network" | head -1 | cut -d= -f2)
|
|
[ -n "$subnet" ] || { echo "searxng.network must pin a Subnet="; return 1; }
|
|
grep -qF "'$subnet'" "$REPO_ROOT/shared/searxng/limiter.toml" \
|
|
|| { echo "pinned subnet $subnet missing from limiter.toml trusted_proxies"; return 1; }
|
|
}
|
|
|
|
@test "every container unit feeds \${VAR} expansion via [Service] EnvironmentFile" {
|
|
# An EnvironmentFile in [Container] only lands in the ignored [X-Container]
|
|
# section and does not feed ${VAR} expansion. It must be in [Service].
|
|
local f
|
|
for f in gluetun searxng searxng-valkey; do
|
|
# The file must contain a [Service] section that sets EnvironmentFile=.
|
|
awk '/^\[Service\]/{s=1;next} /^\[/{s=0} s&&/EnvironmentFile=/{found=1} END{exit found?0:1}' \
|
|
"$QDIR/$f.container" \
|
|
|| { echo "$f.container: [Service] must set EnvironmentFile="; return 1; }
|
|
done
|
|
return 0
|
|
}
|
|
|
|
@test "every container unit is installed for boot (WantedBy=default.target)" {
|
|
local f
|
|
for f in gluetun searxng searxng-valkey; do
|
|
grep -qE '^WantedBy=default\.target' "$QDIR/$f.container" \
|
|
|| { echo "$f.container: [Install] must set WantedBy=default.target"; return 1; }
|
|
done
|
|
return 0
|
|
}
|
|
|
|
@test "no secret material in the quadlet files" {
|
|
# The units reference \${VAR} placeholders, never literal keys. A WireGuard
|
|
# key is a 44-char base64 string; none may appear in the tracked files.
|
|
local f
|
|
for f in "$QDIR"/*.container "$QDIR"/*.network; do
|
|
if grep -qE '[A-Za-z0-9+/]{44}=' "$f"; then
|
|
echo "$f: contains a secret-like value"
|
|
return 1
|
|
fi
|
|
done
|
|
has_no_leaked_secret "$QDIR/gluetun.container"
|
|
}
|