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
283 lines
11 KiB
Markdown
283 lines
11 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
packaging/build-package.sh
|
|
```
|
|
|
|
Install the generated package:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
sudo enodia-sentinel-go --incidents-list
|
|
sudo enodia-sentinel-go --incident-show <incident-id>
|
|
```
|
|
|
|
Inspect or remove the validation service with:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
RELEASE_SIGN=1 make release-artifacts
|
|
```
|
|
|
|
To select a specific GPG key:
|
|
|
|
```bash
|
|
RELEASE_SIGN=1 RELEASE_SIGNING_KEY=<key-id-or-fingerprint> make release-artifacts
|
|
```
|
|
|
|
Verification flow for operators:
|
|
|
|
```bash
|
|
gpg --verify SHA256SUMS.asc SHA256SUMS
|
|
sha256sum -c SHA256SUMS
|
|
```
|