# SPDX-License-Identifier: GPL-3.0-or-later """Command-line entry point. enodia-sentinel run # daemon loop (default; used by systemd) enodia-sentinel check # run every detector once, print alerts, exit enodia-sentinel baseline # (re)build listener/SUID baselines and exit """ from __future__ import annotations import argparse import signal import sys from . import __version__, detectors from .config import Config from .daemon import Sentinel from .system import SystemState, scan_suid_binaries def _cmd_run(cfg: Config) -> int: sentinel = Sentinel(cfg) signal.signal(signal.SIGTERM, sentinel.stop) signal.signal(signal.SIGINT, sentinel.stop) sentinel.run() return 0 def _cmd_baseline(cfg: Config) -> int: sentinel = Sentinel(cfg) sentinel.build_baselines() print(f"Baselines written under {cfg.log_dir}") return 0 def _cmd_check(cfg: Config) -> int: # One-shot: arm everything immediately, force the SUID scan. sentinel = Sentinel(cfg) sentinel.start_time = 0.0 # past the grace window sentinel.load_baselines() if not sentinel.listener_baseline: sentinel.build_baselines() alerts = sentinel.sweep(force_suid=True) if not alerts: print("No alerts.") return 0 for a in sorted(alerts, key=lambda x: -x.severity): print(f"[{a.severity}] {a.signature:<14} {a.detail}") return 0 def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( prog="enodia-sentinel", description="Host intrusion-detection daemon.", ) parser.add_argument("--version", action="version", version=f"enodia-sentinel {__version__}") parser.add_argument("-c", "--config", help="path to TOML config") sub = parser.add_subparsers(dest="cmd") sub.add_parser("run", help="run the daemon loop (default)") sub.add_parser("check", help="run detectors once and print alerts") sub.add_parser("baseline", help="rebuild listener/SUID baselines") sub.add_parser("list-detectors", help="list available detectors") sub.add_parser("web", help="serve the read-only dashboard") args = parser.parse_args(argv) cfg = Config.load(args.config) if args.cmd == "list-detectors": for det in detectors.REGISTRY: mark = "on " if cfg.enabled(det.name) else "off" print(f" [{mark}] {det.name}") return 0 if args.cmd == "baseline": return _cmd_baseline(cfg) if args.cmd == "check": return _cmd_check(cfg) if args.cmd == "web": from .web import serve serve(cfg) return 0 return _cmd_run(cfg) if __name__ == "__main__": sys.exit(main())