The GUI frontends landed with README/CLI/operations docs but no packaging coverage. Add a PACKAGING.md section with the command/script/requirement table for both frontends, note why stdlib tkinter is still an optdepend rather than a hard dependency, and record that packaging must not grant the GUIs privileges beyond the operator's own account. Add the matching PKGBUILD optdepends (`tk`, `pyside6`), plus `python-pystray`/`python-pillow` for the tray applet, which were never listed when it shipped. Also add `gui/` to the CLAUDE.md architecture notes and state snapshot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JX86xeoBJVBb16qHkDf53K
10 KiB
Enodia Sentinel Packaging
Applies to package version: 0.7.0
Docs version: 0.7
Schema generation: v1
Sentinel is a stdlib-only Python package installed as a plain source directory plus launcher wrapper. The service and package are designed around Linux hosts with systemd.
Arch Linux
Arch is the primary packaged target.
Build from the repository root:
packaging/build-package.sh
Install the generated package:
sudo pacman -U enodia-sentinel-*.pkg.tar.zst
sudo systemctl daemon-reload
sudo systemctl enable --now enodia-sentinel.service
The Arch package installs:
| Path | Purpose |
|---|---|
/usr/bin/enodia-sentinel |
Launcher wrapper for the Python package. |
/usr/bin/sentinel-redteam |
Safe local red-team harness. |
/usr/lib/enodia-sentinel/enodia_sentinel/ |
Stdlib-only Python package. |
/etc/enodia-sentinel.toml |
Operator configuration, preserved as a pacman backup file. |
/usr/lib/systemd/system/enodia-sentinel.service |
Hardened daemon unit. |
/usr/lib/systemd/system/enodia-sentinel-web.service |
Read-only dashboard unit. |
/usr/share/libalpm/hooks/enodia-sentinel-fim.hook |
Post-transaction FIM baseline refresh hook. |
/usr/lib/tmpfiles.d/enodia-sentinel.conf |
Runtime log directory definition. |
/usr/share/doc/enodia-sentinel/ |
Versioned operator docs and examples. |
/var/log/enodia-sentinel/ |
Alert snapshots, events, incidents, response plans, and audit logs. |
The pacman hook calls /usr/bin/enodia-sentinel fim-update after package
transactions so legitimate package changes refresh the FIM baseline. It does
not silence unexplained non-package drift.
Optional Arch packages:
python-bpfccfor eBPF exec/syscall telemetry.bpftracefor short-lived exec capture inside forensic snapshots.libnotifyfor local desktop notifications.libarchivefor signed-cache-package.MTREEverification helpers.
The default service is hardened for polling mode. To enable eBPF, copy the packaged drop-in example and reload systemd:
sudo install -Dm644 /usr/share/doc/enodia-sentinel/examples/enodia-sentinel-ebpf.conf \
/etc/systemd/system/enodia-sentinel.service.d/ebpf.conf
sudo systemctl daemon-reload
sudo systemctl restart enodia-sentinel.service
Go Migration Sidecar (opt-in)
The Go agent has a separate validation service and is never installed by the
default make install target or the Arch package. Build and install it
explicitly from a checkout with Go 1.25 or newer:
make build-go
sudo make install-go PREFIX=/usr/local
sudo make enable-go
The static enodia-sentinel-go binary emits enodia.event.v1 JSONL to the
system journal. Its service owns /var/lib/enodia-sentinel-go for baseline
state and a bounded retained event stream. events.jsonl rotates to one
events.jsonl.1 segment before crossing 64 MiB; both are private to root, and
non-status records are synchronized before emission succeeds. The service
requests the embedded native exec and syscall probes and fails open to polling
when the kernel refuses a probe. It does not write the production Python
daemon's /var/log/enodia-sentinel state or replace
enodia-sentinel.service. Systemd readiness is emitted only after baseline
initialization, event-log and snapshot-store preflight, and both requested probe
load attempts complete.
Alert events are additionally retained as private
alert-YYYYMMDD-HHMMSS.{json,log} pairs in /var/lib/enodia-sentinel-go.
Same-second events are merged into one snapshot, required
enodia.alert.snapshot.v1 fields are preserved, and the shared
max_snapshots / max_snapshot_age_days settings bound retention. The current
sidecar also writes a private atomic incidents.json with the required
enodia.incident.v1 fields, process-lineage/time-window grouping, and additive
correlation evidence. Hot-path Go enrichment is bounded and no-I/O: process and
parent context, lineage, remote classification, and candidate paths come from
already-captured records. A bounded asynchronous worker subsequently hashes
regular executables no larger than 8 MiB and records file metadata; a full queue
skips those optional fields without delaying an alert. Python remains
authoritative for package ownership, richer integrity metadata, notifications,
and management consumers.
Each snapshot text/JSON revision is SHA-256 hashed into a synchronized private
hash-chain.jsonl using the stable enodia.hash_chain.v1 record schema.
Each successful sweep atomically refreshes
/var/lib/enodia-sentinel-go/heartbeat as a Unix timestamp with mode 0600;
the retained value becomes stale when the sidecar stops or wedges.
enodia-sentinel-go --health reports that state as JSON and exits nonzero when
it is unavailable or older than the configured heartbeat_max_age.
Inspect the retained event stream without depending on journald:
sudo tail -n 50 /var/lib/enodia-sentinel-go/events.jsonl
sudo enodia-sentinel-go --events-tail 50
The native reader includes events.jsonl.1, preserves chronological order, and
fails nonzero if either retained segment contains malformed JSON.
Inspect the sidecar's isolated incident records and timeline without starting the service or changing Python state:
sudo enodia-sentinel-go --incidents-list
sudo enodia-sentinel-go --incident-show <incident-id>
Inspect or remove the validation service with:
sudo make status-go
sudo make disable-go
sudo make uninstall-go
Uninstalling preserves the shared configuration and sidecar state for forensic review or a later reinstall.
Debian and Ubuntu
There is no native .deb package yet. Use the source install path:
sudo apt install python3 iproute2 procps
sudo make install PREFIX=/usr/local
sudo systemctl daemon-reload
sudo systemctl enable --now enodia-sentinel.service
Notes:
- The installed launcher does not require pip or a virtualenv.
fim-check --packagesuses the available package-manager backend; Debian package verification support should be validated for the target host before relying on it operationally.- The source install path includes the pacman hook file for parity with Arch source installs. On non-Arch systems it is inert unless pacman/libalpm is installed; Debian installs should manage FIM baseline refresh through operator procedure or a future dpkg trigger package.
RPM-family Linux
There is no native RPM package yet. Use the same source install path:
sudo dnf install python3 iproute procps-ng
sudo make install PREFIX=/usr/local
sudo systemctl daemon-reload
sudo systemctl enable --now enodia-sentinel.service
Notes:
- Native RPM packaging should eventually install systemd units under the distro
unit path, preserve
/etc/enodia-sentinel.toml, and create a package-manager trigger equivalent to the Arch FIM refresh hook. - Validate package verification behavior on the target distro before enabling signed-package mismatch alerting.
Desktop Tray Applet (optional)
The [tray] optional extra (pip install 'enodia-sentinel[tray]') pulls in
pystray and Pillow and provides the enodia-sentinel-tray console script.
The extra is never required by the daemon; core runtime dependencies stay
empty.
packaging/enodia-sentinel-tray.desktop is an opt-in autostart entry. It is
not installed to a system autostart path by default — desktop users copy it to
~/.config/autostart/ themselves.
Desktop GUI Frontends (optional)
Two windowed dashboards ship alongside the tray applet. Both are optional frontends over the same local state the TUI and web console read; neither is required by the daemon, and core runtime dependencies stay empty.
| Frontend | Command | Console script | Requirement |
|---|---|---|---|
| tkinter | enodia-sentinel gui |
enodia-sentinel-gui |
stdlib tkinter (distro tk / python-tk / python-tkinter) |
| Qt6 | enodia-sentinel gui-qt |
enodia-sentinel-gui-qt |
[qt] extra (PySide6) |
Notes:
tkinteris stdlib but is packaged separately on most distros, so it is anoptdepends(Archtk), never a hard dependency.- The
[qt]extra (pip install 'enodia-sentinel[qt]') pulls inPySide6only for the Qt frontend; the Arch package listspyside6as an optdepend. - Both GUIs are read-only over detection state and display response plans for
review only — packaging must not grant them privileges beyond what the
operator's own account has. Daemon start/stop/restart goes through
systemctland prompts for polkit authorization as usual. - Only
gui/app.py(tkinter) andgui/qt_app.py(Qt) import GUI libraries;gui/model.pystays import-clean so headless builders can run the test suite without a display server orPySide6installed.
Terminal TUI and Completion
The terminal dashboard is stdlib-only and available through
enodia-sentinel tui in source/wrapper installs. Python packaging metadata also
declares enodia-sentinel-tui = enodia_sentinel.tui:main for pip-style script
generation.
Shell completion has no runtime dependency: operators generate scripts with
enodia-sentinel completion bash or enodia-sentinel completion zsh and install
them in their normal shell completion path.
Package Hardening Checklist
- Package version matches
pyproject.tomlandenodia_sentinel.__version__. - Config is preserved across upgrades.
- Log directory is created with mode
0750. - Service units remain hardened and read-mostly by default.
- eBPF privilege relaxation is shipped only as an opt-in drop-in example.
- Package-manager hooks use absolute paths and do not depend on ambient
PATH. - Versioned operator docs are installed with the package.
Release Artifacts
Release artifacts are built with:
make release-artifacts
The release target writes to dist/ by default:
| Artifact | Purpose |
|---|---|
enodia-sentinel-<version>.tar.gz |
Versioned source release. |
SHA256SUMS |
SHA-256 checksums for the source tarball and release manifest. |
RELEASE_MANIFEST.json |
Project/version/git metadata for the generated release. |
SHA256SUMS.asc |
Optional detached GPG signature over SHA256SUMS. |
The script refuses a dirty git worktree by default so a release maps to a known
commit. For local packaging smoke tests only, set RELEASE_ALLOW_DIRTY=1.
To sign checksum artifacts:
RELEASE_SIGN=1 make release-artifacts
To select a specific GPG key:
RELEASE_SIGN=1 RELEASE_SIGNING_KEY=<key-id-or-fingerprint> make release-artifacts
Verification flow for operators:
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS