Skip to content

Commit c71bd2c

Browse files
alsechemistclaude
andcommitted
feat(wolf-search): 6-f.2 packaging half — wolf-search.deb, five .debs, CI installs + health-checks it (closes 6-f.2)
debian/wolf-search.* mirroring wolf-database: the .deb ships only Wolf-owned artifacts (wrapper + settings/uwsgi templates + dedicated unit); postinst = the exact 6-f.2 host recipe pinned to 747cec4c (searxng user, clone at the pin, searx-pyenv + official pip sequence, per-host secret install-once root:searxng 640, ini to apps-available never apps-enabled). Requires network at install — ratified ADR 0032 trade-off; air-gapped installs skip it (Recommends in the wolf meta-package, not Depends). Upgrades incremental; remove keeps /etc/searxng until purge; searxng user never removed. CI: smoke-deb + release.yml expect five .debs; smoke-deb-install installs wolf-search, asserts every postinst effect, then starts the service and runs `wolf-search health` end-to-end (timeout 15→25); smoke-systemd verifies the 4th unit template. Docs: RELEASING, docs/17, ONBOARDING topology + sideload, roadmap 6-f.2 ✅, ADR 0032 addendum + Appendix A → the real postinst. Next: 6-f.3 — web_search/web_fetch/bounded web_crawl tools + A6 security + docs-first + citations, gated on WEB_SEARCH_ENABLED. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent a16a342 commit c71bd2c

19 files changed

Lines changed: 518 additions & 57 deletions

.github/workflows/ci.yml

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -467,9 +467,10 @@ jobs:
467467
# syntactic + presence-of-fail-loud-branches validation. Catches
468468
# regressions in unit-file syntax, shim shell logic, and the
469469
# install.sh CLI surface.
470-
# Phase 5.9-e: build all four wolf-*.deb on a clean ubuntu-latest
471-
# runner. Catches build-time issues across the per-component
472-
# debian/ artifacts authored in 5.9-a/b/c/d that the per-slice
470+
# Phase 5.9-e: build all five wolf-*.deb on a clean ubuntu-latest
471+
# runner (wolf-search joined in slice 6-f.2, ADR 0032). Catches
472+
# build-time issues across the per-component debian/ artifacts
473+
# authored in 5.9-a/b/c/d + 6-f.2 that the per-slice
473474
# static checks (bash -n, syntax-only validation) can't reach.
474475
# Runs natively on the runner (vs Docker-in-Docker) — faster, no
475476
# nested-container quirks.
@@ -511,7 +512,7 @@ jobs:
511512
python3 python3-pip python3-venv python3-build \
512513
nodejs npm \
513514
ca-certificates
514-
- name: Build all four .debs
515+
- name: Build all five .debs
515516
run: dpkg-buildpackage -b -us -uc
516517
- name: Stage .debs inside the workspace for upload
517518
# dpkg-buildpackage drops .deb files in the parent of the
@@ -522,8 +523,8 @@ jobs:
522523
mv ../wolf*.deb packaging/build/debs/
523524
ls -la packaging/build/debs/
524525
count=$(ls packaging/build/debs/wolf*.deb 2>/dev/null | wc -l)
525-
if [ "$count" -lt 4 ]; then
526-
echo "FAIL: expected 4 .debs, got $count"
526+
if [ "$count" -lt 5 ]; then
527+
echo "FAIL: expected 5 .debs, got $count"
527528
exit 1
528529
fi
529530
echo "OK: $count .debs produced"
@@ -632,7 +633,9 @@ jobs:
632633
# NodeSource). add-apt-repository ppa:deadsnakes can hang on a slow
633634
# launchpad/keyserver — with no timeout it wedges at GitHub's 6-hour
634635
# default. Cap it so an infra hang fails fast in minutes, not hours.
635-
timeout-minutes: 15
636+
# 25 (was 15): wolf-search's postinst clones SearXNG + pip-installs
637+
# its deps over the network (~3-5 min on a runner) — slice 6-f.2.
638+
timeout-minutes: 25
636639
steps:
637640
- uses: actions/checkout@v6
638641
- name: Download .debs from smoke-deb
@@ -683,16 +686,21 @@ jobs:
683686
sudo apt-get install -y python3.13 python3.13-venv nodejs
684687
python3.13 --version
685688
node --version
686-
- name: Install all four wolf-*.debs
689+
- name: Install all five wolf-*.debs
687690
# apt install resolves the inter-package deps automatically
688691
# (wolf-server depends on wolf-database, the meta wolf
689-
# depends on all three components, etc.).
692+
# depends on all three components, etc.). wolf-search's
693+
# Depends (uwsgi, git, libxslt-dev, ...) come from stock
694+
# noble; its postinst clones SearXNG at the Wolf-verified
695+
# pin + builds the venv — real network install, by design
696+
# (ADR 0032: the postinst IS the official install recipe).
690697
run: |
691698
ls -la debs/
692699
sudo apt install -y \
693700
./debs/wolf-database_*.deb \
694701
./debs/wolf-server_*.deb \
695702
./debs/wolf-dashboard_*.deb \
703+
./debs/wolf-search_*.deb \
696704
./debs/wolf_*.deb
697705
- name: Verify service users + group created by postinst
698706
run: |
@@ -752,11 +760,12 @@ jobs:
752760
/usr/bin/wolf-cert --help 2>&1 | head -5
753761
echo "OK: shims executable + CLIs respond"
754762
- name: Verify systemd units are loaded
755-
# Don't start them (needs operator-driven init); just
756-
# confirm systemd recognises the units so they could be
757-
# started.
763+
# Don't start the three core components (needs operator-driven
764+
# init); just confirm systemd recognises the units so they
765+
# could be started. wolf-search gets a REAL start below — it
766+
# is the one component whose postinst leaves it fully runnable.
758767
run: |
759-
for unit in wolf-database wolf-server wolf-dashboard; do
768+
for unit in wolf-database wolf-server wolf-dashboard wolf-search; do
760769
systemctl status "$unit" --no-pager 2>&1 | head -3 || true
761770
# `is-enabled` returns "disabled" for fresh installs
762771
# (postinst doesn't auto-enable; operator does that
@@ -765,12 +774,59 @@ jobs:
765774
systemctl cat "$unit" >/dev/null 2>&1 \
766775
|| { echo "FAIL: systemctl can't load $unit unit"; exit 1; }
767776
done
768-
echo "OK: all 3 systemd units loaded + parseable"
777+
echo "OK: all 4 systemd units loaded + parseable"
778+
- name: Verify wolf-search postinst effects (slice 6-f.2)
779+
# The postinst = the official SearXNG recipe pinned to the
780+
# Wolf-verified commit. Verify each of its jobs actually
781+
# happened on this clean runner.
782+
run: |
783+
getent passwd searxng || { echo "FAIL: searxng user missing"; exit 1; }
784+
pin="747cec4c2304fc2e06ceb4a46dbe25bfb769db1e"
785+
head=$(sudo -u searxng git -C /usr/local/searxng/searxng-src rev-parse HEAD)
786+
[ "$head" = "$pin" ] \
787+
|| { echo "FAIL: searxng-src at $head, expected pin $pin"; exit 1; }
788+
test -x /usr/local/searxng/searx-pyenv/bin/python \
789+
|| { echo "FAIL: searx-pyenv venv missing"; exit 1; }
790+
perms=$(stat -c "%a %U %G" /etc/searxng/settings.yml)
791+
[ "$perms" = "640 root searxng" ] \
792+
|| { echo "FAIL: settings.yml is '$perms', expected '640 root searxng'"; exit 1; }
793+
if sudo grep -q "ultrasecretkey" /etc/searxng/settings.yml; then
794+
echo "FAIL: secret_key placeholder not substituted"; exit 1
795+
fi
796+
test -f /etc/uwsgi/apps-available/searxng.ini \
797+
|| { echo "FAIL: uwsgi ini missing"; exit 1; }
798+
if test -L /etc/uwsgi/apps-enabled/searxng.ini; then
799+
echo "FAIL: ini must NOT be in apps-enabled (double-run)"; exit 1
800+
fi
801+
test -x /usr/bin/wolf-search || { echo "FAIL: wolf-search shim missing"; exit 1; }
802+
/usr/bin/wolf-search help | grep -q "health" \
803+
|| { echo "FAIL: wolf-search help doesn't list health"; exit 1; }
804+
echo "OK: searxng user + pinned checkout + venv + settings(640) + ini + shim"
805+
- name: Start wolf-search + end-to-end health check
806+
# Unlike the three core components, wolf-search needs no
807+
# operator-provisioned env — postinst leaves it runnable.
808+
# `wolf-search health` probes the exact JSON endpoint
809+
# wolf-server's SearxngProvider consumes; SearXNG answers
810+
# HTTP 200 regardless of upstream-engine luck, so this is
811+
# stable on a datacenter runner.
812+
run: |
813+
sudo systemctl start wolf-search
814+
for i in $(seq 1 12); do
815+
if /usr/bin/wolf-search health; then
816+
echo "OK: wolf-search healthy end-to-end (loopback 1307, JSON API 200)"
817+
exit 0
818+
fi
819+
sleep 5
820+
done
821+
echo "FAIL: wolf-search not healthy after 60s"
822+
sudo journalctl -u wolf-search --no-pager | tail -40
823+
exit 1
769824
- name: Smoke summary
770825
run: |
771826
echo "=== smoke-deb-install: PASS ==="
772827
echo "wolf-* packages installed cleanly on ubuntu-latest"
773828
echo "users / group / FHS dirs / venvs / shims / systemd units all verified"
829+
echo "wolf-search: postinst recipe verified + service started + health OK"
774830
775831
smoke-systemd:
776832
name: systemd + shims smoke (Phase 5.8-d)

.github/workflows/release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ name: Release
88
# git push origin v0.X.Y
99
#
1010
# What happens:
11-
# 1. Build the four wolf-*.deb files (same as smoke-deb).
11+
# 1. Build the five wolf-*.deb files (same as smoke-deb).
1212
# 2. Sign each .deb with the Wolf maintainers' GPG key.
1313
# 3. Produce SHA256SUMS + sign that too.
1414
# 4. Create a GitHub Release at https://github.com/M-s-Tech4TIME/
@@ -80,16 +80,16 @@ jobs:
8080
nodejs npm \
8181
ca-certificates
8282
83-
- name: Build all four .debs
83+
- name: Build all five .debs
8484
run: dpkg-buildpackage -b -us -uc
8585

8686
- name: Stage .debs inside the workspace
8787
run: |
8888
mkdir -p packaging/build/debs
8989
mv ../wolf*.deb packaging/build/debs/
9090
count=$(ls packaging/build/debs/wolf*.deb 2>/dev/null | wc -l)
91-
if [ "$count" -lt 4 ]; then
92-
echo "FAIL: expected 4 .debs, got $count"
91+
if [ "$count" -lt 5 ]; then
92+
echo "FAIL: expected 5 .debs, got $count"
9393
exit 1
9494
fi
9595
echo "OK: ${count} .debs produced for ${{ steps.version.outputs.version }}"

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,18 +254,19 @@ smoke-systemd: ## End-to-end systemd + shim smoke (Phase 5.8-d)
254254
echo " OK: all three installed user units are clean"; \
255255
\
256256
echo "--- 3/5: systemd-analyze passes on system-level unit templates ---"; \
257-
echo " (filtering expected \"/usr/bin/wolf-* is not executable\"; that lands with the .deb)"; \
257+
echo " (filtering expected \"... is not executable\"; binaries land with the .debs)"; \
258258
for u in deploy/systemd/system/wolf-database.service \
259259
deploy/systemd/system/wolf-server.service \
260-
deploy/systemd/system/wolf-dashboard.service; do \
260+
deploy/systemd/system/wolf-dashboard.service \
261+
deploy/systemd/system/wolf-search.service; do \
261262
out=$$(systemd-analyze verify --man=no "$$u" 2>&1 | \
262263
grep -v "is not executable" || true); \
263264
if [ -n "$$out" ]; then \
264265
echo "FAIL: $$u has unexpected issues:"; \
265266
echo "$$out"; exit 1; \
266267
fi; \
267268
done; \
268-
echo " OK: all three system unit templates have clean directives"; \
269+
echo " OK: all four system unit templates have clean directives"; \
269270
\
270271
echo "--- 4/5: every shim fails loud with exit 2 when its venv is missing ---"; \
271272
for shim in deploy/bin/wolf-cert deploy/bin/wolf-database \

ONBOARDING.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,7 @@ threat model.
736736
│ wolf-server host │
737737
│ (internal "brain" VLAN)│
738738
│ - wolf-server.deb │
739+
│ - wolf-search.deb** │
739740
└───────────┬────────────┘
740741
│ Wazuh API
741742
@@ -745,6 +746,11 @@ threat model.
745746
746747
* wolf-database can live on either host. Most operators
747748
co-locate it with wolf-server (less network surface).
749+
** wolf-search (SearXNG web research, ADR 0032) is
750+
wolf-server's sidecar: ALWAYS co-located with wolf-server,
751+
loopback-only (127.0.0.1:1307) — in every topology. Optional:
752+
skip it (it's a Recommends) for air-gapped installs or
753+
hosted search backends.
748754
```
749755

750756
**Three-host topology (brain + edge + DB):**
@@ -770,9 +776,12 @@ wolf-database first (it owns the schema), then wolf-server
770776

771777
```bash
772778
# 1. Add the Wolf APT repository (URL pending; tracked as
773-
# docs/17 gap 2). Until then, sideload the .deb files:
779+
# docs/17 gap 2). Until then, sideload the .deb files.
780+
# wolf-search is optional-but-default (web research; its
781+
# postinst needs network to github.com + PyPI):
774782
sudo apt install ./wolf-database_0.1.0_amd64.deb \
775-
./wolf-server_0.1.0_amd64.deb
783+
./wolf-server_0.1.0_amd64.deb \
784+
./wolf-search_0.1.0_all.deb
776785

777786
# 2. Initialize the cluster (one-shot):
778787
sudo -u wolf-database \

RELEASING.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ A "release" is:
1212

1313
1. A git tag of the form `vMAJOR.MINOR.PATCH` (semver, see
1414
[SUPPORT.md](SUPPORT.md)).
15-
2. Four signed `.deb` files attached as assets to a GitHub
15+
2. Five signed `.deb` files attached as assets to a GitHub
1616
Release with that tag's name:
1717
- `wolf-database_X.Y.Z_amd64.deb`
1818
- `wolf-server_X.Y.Z_amd64.deb`
1919
- `wolf-dashboard_X.Y.Z_amd64.deb`
20+
- `wolf-search_X.Y.Z_all.deb` (SearXNG sidecar, ADR 0032)
2021
- `wolf_X.Y.Z_all.deb` (meta-package)
2122
3. Detached signatures (`*.asc`) alongside each `.deb`.
2223
4. A signed `SHA256SUMS` (+ `SHA256SUMS.asc`) covering all
23-
four `.deb` files.
24+
five `.deb` files.
2425
5. Release notes pulled from `docs/CHANGELOG.md` (or a
2526
fallback if no matching section exists).
2627

@@ -108,7 +109,7 @@ Pushing the tag triggers `.github/workflows/release.yml`,
108109
which:
109110

110111
1. Verifies the tag version matches `debian/changelog`.
111-
2. Builds all four `.deb` files via `dpkg-buildpackage`.
112+
2. Builds all five `.deb` files via `dpkg-buildpackage`.
112113
3. Imports the Wolf signing key from `GPG_PRIVATE_KEY`
113114
secret + signs each `.deb` (detached `.asc` signature).
114115
4. Generates `SHA256SUMS` + signs that too.
@@ -133,8 +134,8 @@ v0.X.Y` to the release being live.
133134
Run this once the release workflow completes:
134135

135136
```bash
136-
# 1. Confirm the release exists with all 9 expected assets
137-
# (4 .debs + 4 .asc + SHA256SUMS + SHA256SUMS.asc = 10).
137+
# 1. Confirm the release exists with all 12 expected assets
138+
# (5 .debs + 5 .asc + SHA256SUMS + SHA256SUMS.asc = 12).
138139
gh release view v0.X.Y
139140

140141
# 2. Download + verify locally as an operator would.

debian/control

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,57 @@ Description: Wolf edge component (Next.js)
7373
Built from Next.js 16 with output: "standalone" so production
7474
doesn't need npm at runtime — just node.
7575

76+
Package: wolf-search
77+
Architecture: all
78+
Depends:
79+
${misc:Depends},
80+
python3-dev,
81+
python3-babel,
82+
python3-venv,
83+
python-is-python3,
84+
uwsgi,
85+
uwsgi-plugin-python3,
86+
git,
87+
build-essential,
88+
libxslt-dev,
89+
zlib1g-dev,
90+
libffi-dev,
91+
libssl-dev,
92+
openssl,
93+
curl,
94+
ca-certificates,
95+
Description: Wolf bundled-SearXNG component (loopback metasearch)
96+
wolf-search wraps a SearXNG metasearch instance installed per the
97+
official SearXNG Ubuntu/Debian step-by-step docs, pinned to a
98+
Wolf-verified upstream commit. It is wolf-server's sidecar for the
99+
web-research tools (ADR 0032): loopback-only on 127.0.0.1:1307,
100+
called by nothing but wolf-server, no accounts, no API keys, no
101+
per-query cost.
102+
.
103+
The Depends list is the official SearXNG install's apt list —
104+
postinst executes that exact recipe (git clone at the pinned
105+
commit + dedicated venv under /usr/local/searxng), so installing
106+
this package REQUIRES network access to github.com and PyPI.
107+
Air-gapped installs skip wolf-search (it is a Recommends of the
108+
wolf meta-package, not a Depends) and use a hosted search backend
109+
or none.
110+
76111
Package: wolf
77112
Architecture: all
78113
Depends:
79114
${misc:Depends},
80115
wolf-database (= ${binary:Version}),
81116
wolf-server (= ${binary:Version}),
82117
wolf-dashboard (= ${binary:Version}),
118+
Recommends:
119+
wolf-search (= ${binary:Version}),
83120
Description: Wolf — agentic AI for Wazuh (all-in-one meta-package)
84-
Installs all three Wolf components on a single host. Convenience
85-
meta-package for the common all-in-one deployment; distributed
86-
deployments install the individual component packages on each
87-
host instead.
121+
Installs all three core Wolf components on a single host, plus
122+
wolf-search (SearXNG web research) as a Recommends — installed by
123+
default, skippable via --no-install-recommends for air-gapped or
124+
hosted-search-backend deployments. Convenience meta-package for
125+
the common all-in-one deployment; distributed deployments install
126+
the individual component packages on each host instead.
88127
.
89128
After installation, see /usr/share/doc/wolf/README for the
90129
one-command bring-up: wolf-database init + wolf-cert init +

debian/wolf-search.dirs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Empty directories the package ships. Mirrors wolf-database.dirs.
2+
#
3+
# /usr/local/searxng + /etc/searxng are NOT listed here — the
4+
# postinst is the canonical owner of those paths (it executes the
5+
# official SearXNG install recipe, which creates them with the
6+
# right ownership); having debhelper also create them would be a
7+
# coordination footgun.
8+
9+
usr/lib/wolf-search

debian/wolf-search.install

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# debian/wolf-search.install — what ends up inside wolf-search.deb.
2+
#
3+
# Format: <source path relative to repo> <install dir under debian/wolf-search/>
4+
#
5+
# The .deb itself carries only the Wolf-owned artifacts: the CLI
6+
# wrapper + the two config templates + the systemd unit (shipped via
7+
# debian/wolf-search.service). SearXNG itself is NOT in the .deb —
8+
# the postinst installs it per the official recipe (git clone pinned
9+
# to the Wolf-verified commit + venv under /usr/local/searxng),
10+
# exactly as the 6-f.2 host standup did by hand.
11+
12+
# The operator CLI (health/status/version/logs/service ops). Same
13+
# script the manual install drops; the .deb ships it directly.
14+
deploy/bin/wolf-search usr/bin/
15+
16+
# Config templates. postinst installs each to its /etc destination
17+
# ONLY if absent (operator edits survive upgrades):
18+
# settings.yml -> /etc/searxng/settings.yml (+ per-host
19+
# secret_key via openssl, root:searxng 640)
20+
# searxng-uwsgi.ini -> /etc/uwsgi/apps-available/searxng.ini
21+
deploy/searxng/settings.yml usr/lib/wolf-search/
22+
deploy/searxng/searxng-uwsgi.ini usr/lib/wolf-search/

0 commit comments

Comments
 (0)