enodia-sentinal/docs/PACKAGING.md
Luna 1d1dee7f6e
feat(packaging): add desktop launcher entries for both GUI dashboards
The tray applet shipped with a .desktop file but the GUI dashboards had
none, so desktop users had no menu entry for them.

Unlike the tray entry, these carry no autostart key: a dashboard window
should open when the operator asks for it, not at login. Tests assert the
Exec names match the pyproject console scripts and that neither entry can
regain an autostart key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JX86xeoBJVBb16qHkDf53K
2026-07-22 04:35:37 -07:00

11 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-bpfcc for eBPF exec/syscall telemetry.
  • bpftrace for short-lived exec capture inside forensic snapshots.
  • libnotify for local desktop notifications.
  • libarchive for signed-cache-package .MTREE verification 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 --packages uses 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:

  • tkinter is stdlib but is packaged separately on most distros, so it is an optdepends (Arch tk), never a hard dependency.
  • The [qt] extra (pip install 'enodia-sentinel[qt]') pulls in PySide6 only for the Qt frontend; the Arch package lists pyside6 as 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 systemctl and prompts for polkit authorization as usual.
  • Only gui/app.py (tkinter) and gui/qt_app.py (Qt) import GUI libraries; gui/model.py stays import-clean so headless builders can run the test suite without a display server or PySide6 installed.

packaging/enodia-sentinel-gui.desktop and packaging/enodia-sentinel-gui-qt.desktop are application-menu launchers. Unlike enodia-sentinel-tray.desktop, they carry no autostart key — a dashboard window should open when the operator asks for it, not at login. Desktop packagers may install them to /usr/share/applications/; source installs can copy them to ~/.local/share/applications/.

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.toml and enodia_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