Documentation
¶
Overview ¶
Package agent is the daemon loop: tail → parse → evaluate → decide → enforce, pending verdicts ticked, everything the other packages provide wired into one running defence.
A live tail joins the present: a starting agent reads from the end of each file, stamps each line with the moment it read it — the same instant — and never resumes a position saved before it stopped. Reading a log's past is a deliberate act, either replay_on_start at boot or a dry run from the command line, and it judges by window *and* ban duration: a crossing counts only if the sanction it earns would still be in force.
One deliberate simplification remains: pending verdicts are not flushed on shutdown. Firing them early could ban what exculpatory evidence would have cleared, so a restart re-detects from fresh evidence instead.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler writes the agent's log. It is a slog.Handler so that every package can keep logging the way it does.
func NewHandler ¶
NewHandler writes to w at level and above.
type Options ¶
type Options struct {
ConfigDir string
StatePath string // the SQLite state file; required, Run refuses an empty one
Applier nft.Applier // nil = the real nft binary
PollInterval time.Duration // default 200ms
RescanEvery time.Duration // how often log_input patterns are re-read; default rescanEvery
Logger *slog.Logger // nil = slog.Default()
// Output is what Logger writes to, when the caller built it over
// one. The agent adds log_file to it after reading the
// configuration, and keeps it open across rotation. Nil is fine:
// the log then goes wherever Logger already sends it.
Output *Output
}
Options configure a run. ConfigDir is required; zero values elsewhere take defaults.
type Output ¶
type Output struct {
// contains filtered or unexported fields
}
Output is where the log goes. It starts as one writer — stderr, which systemd collects — and can gain a file once the configuration has been read, which is after the logger already exists.
It also survives log rotation on its own. The file is identified by (device, inode) like every other file this program follows, so when logrotate renames it away the next check notices the path no longer names the same file and reopens it. That is why no logrotate configuration ships with this: there is nothing to tell it.
func (*Output) AddFile ¶
AddFile starts also writing to path, creating it if it is not there. An error is returned rather than logged — the caller is the only one who can say it in the log that still works.
func (*Output) CheckRotation ¶
CheckRotation reopens the file if the path no longer names the one that is open — which is what logrotate leaves behind when it renames or removes it. Called on the agent's own slow cadence; a stat is cheap and a log written into a deleted inode is invisible.