Documentation
¶
Overview ¶
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-disk" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Reads /var/log filesystem capacity + non-root-available bytes via syscall.Statfs (Linux). This is the ONE side-effecting input; it is deliberately separate from the pure Calculate() so the calculator stays deterministic. Mirrors the installer preflight statfs convention (Bavail for non-root available, Blocks for capacity, safeconv for the block-size conversion)." meta:depends="syscall,github.com/itcmsgr/nftban/internal/safeconv" meta:inventory.files="internal/logretention/disk.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-generate" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Phase-5 generated-policy transaction (Model B). Treats the logrotate files as DERIVED STATE, never operator config. Acquires an exclusive generation lock; renders all candidates into the target filesystem; validates the COMBINED effective policy with `logrotate -d --state <tmp>` (debug=no rotation; temp state=real state untouched; explicit file args=active policy never co-loaded); writes a DURABLE activation journal (candidate+backup paths, prev+candidate hashes) BEFORE touching any target; then a two-file ATOMIC activation with in-process ROLLBACK on error; writes the generated-state record only after a fully successful activation; preserves the previous valid policy on any failure; removes temporaries; records prior+active SHA-256 hashes. Z1: a crash between the per-file renames is NOT left to the next regeneration — Recover() (recover.go) deterministically rolls the set forward or back to a uniform state at every consumer (generate, status, maintenance-before-logrotate)." meta:depends="crypto/sha256,encoding/json,os,os/exec,syscall" meta:inventory.files="internal/logretention/generate.go" meta:inventory.env_vars="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.binaries="logrotate" meta:inventory.config_files="install/config/nftban.logrotate,install/config/nftban-suricata.logrotate" meta:inventory.privileges="root (writes /etc/logrotate.d + /var/lib/nftban/generated)"
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-inventory" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Canonical NFTBan log-family inventory for the retention calculator + generated-policy renderer. Mirrors the shipped baseline install/config/nftban.logrotate + nftban-suricata.logrotate (grouping, cadence, rotation mechanism). BaseSizeBytes/BaseRotate are the ceilings the calculator will not exceed; the calculator scales the effective size/rotate down to fit the capacity budget. A parity test (inventory_parity_test.go) asserts this table stays in sync with the shipped templates and logs.go LogInventory()." meta:depends="none (stdlib)" meta:inventory.files="internal/logretention/inventory.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.config_files="install/config/nftban.logrotate,install/config/nftban-suricata.logrotate" meta:inventory.privileges="none"
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-loadconf" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Reads the operator override authority /etc/nftban/conf.d/logs.conf (shell KEY=\"value\" convention) into the Overrides model. An absent file means fully automatic (safe default). Only uncommented keys are treated as overrides. Malformed numeric values and out-of-contract values FAIL (via Overrides.Validate) — they are never silently ignored. The generated logrotate file is NOT read as config; this file is the sole operator authority." meta:depends="os,strconv" meta:inventory.files="internal/logretention/loadconf.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="etc/nftban/conf.d/logs.conf" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"
============================================================================= NFTBan Gate B — Log-retention capacity budget calculator ============================================================================= SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-calculator" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Deterministic, side-effect-free calculator that derives a bounded log-retention policy from /var/log filesystem CAPACITY (the stable basis) + detected profile + validated operator overrides. Produces per-family effective size/rotate/retention with the invariants UNBOUNDED_STANZAS=0 and THEORETICAL_MAX<=BUDGET, preserving a minimum forensic floor. Available free space is surfaced as headroom/health only, never as the permanent budget basis. Calculate() mutates nothing and never invokes logrotate." meta:input="DiskFacts (capacity+avail), Profile, validated Overrides, LogFamily inventory" meta:output="EffectivePolicy (budget, per-family caps, theoretical max, fit verdict, source+version)" meta:depends="none (stdlib)" meta:inventory.files="internal/logretention/logretention.go,internal/logretention/disk.go,internal/logretention/render.go,internal/logretention/generate.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="install/config/nftban.logrotate,install/config/nftban-suricata.logrotate" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none" =============================================================================
SCOPE (Gate B / v1.222.0): the calculator + generated-policy contract. As of Phase 6-7 this package IS imported by the nftban-core CLI (nftban logs retention status), reads operator overrides from conf.d/logs.conf (LoadOverrides), and the LOG_RETENTION_* keys are declared in config-schema.json. It is NOT imported by the nftband daemon. Runtime PACKAGING wiring of the generator (install/%post + timer + config-change) is pending (audit R2); until then the shipped static install/config/nftban.logrotate is the active authority.
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-readiness" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="DELTA-L1/L2/L3 post-generation policy-readiness invariant. After install-time generation + fallback handling, an install may report COMMITTED ONLY if a VALID ACTIVE policy exists: the generated set (every applicable target's live hash matches the generated-state transaction => READY_GENERATED) OR the APPROVED BOUNDED fallback (the active main policy is BYTE-IDENTICAL to the shipped template and bounded, with no generated-state claim => READY_FALLBACK). DELTA-L2: READY_FALLBACK is NOT syntax-only — it requires sha256(active)==sha256(shipped template) (the fallback is a verbatim template copy) AND unbounded_stanzas==0, so an arbitrary/unbounded/hand-edited valid policy can never masquerade as the fallback. DELTA-L3: READY_GENERATED covers the WHOLE generated set — when the state records a suricata policy (applicability = authoritative state key), the live /etc/logrotate.d/nftban-suricata must exist, validate, and hash-match state; a missing/drifted/stale suricata policy => NOT_READY. Single authority: the CLI, the installer assertion, and the acceptance collector all consume one Readiness() result. Pure + deterministic given an injected Validator." meta:depends="crypto/sha256,encoding/hex,encoding/json,os,strings" meta:inventory.files="internal/logretention/readiness.go" meta:inventory.env_vars="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.binaries="logrotate" meta:inventory.config_files="/etc/logrotate.d/nftban,/etc/logrotate.d/nftban-suricata,/etc/nftban/templates/nftban.logrotate,/var/lib/nftban/generated/logrotate/nftban-effective.state.json" meta:inventory.privileges="reads the generated policies + state + template (root in the install path)"
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-recover" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Z1 crash-consistency recovery for the two-phase logrotate-policy activation. A crash between the main and suricata renames would leave the on-disk policy set SPLIT (one file new, one old). Recover() reads the durable activation journal and DETERMINISTICALLY drives the set to a uniform all-new (roll-forward, completing the already-validated intent) or all-old (roll-back, when candidates are no longer available) state — never eventual-consistency-via-next-regeneration. Idempotent and safe to call before every consumer: generate, status (must not report ACTIVE_MATCH over a pending journal), and the maintenance step that triggers logrotate. Because every file mutation is an atomic rename, each target is only ever fully-old or fully-new (no torn content), so the current on-disk hash classifies it unambiguously against the journal." meta:depends="encoding/json,os,path/filepath" meta:inventory.files="internal/logretention/recover.go" meta:inventory.env_vars="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.binaries="" meta:inventory.config_files="" meta:inventory.privileges="root to repair (renames under /etc/logrotate.d); read-only detection needs only read on the staging dir"
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024-2026 Antonios Voulvoulis <contact@nftban.com> meta:name="logretention-render" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-07-19" meta:description="Deterministically renders an EffectivePolicy + family inventory into logrotate stanza text, per file (main | suricata), reproducing the corrected shipped mechanism (copytruncate vs rename+create, delaycompress only where a writer holds the rotated inode, su/create/olddir/USR2). No timestamps or nondeterminism — byte-identical for identical inputs, which the DEB≡RPM parity requirement depends on." meta:depends="none (stdlib)" meta:inventory.files="internal/logretention/render.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none"
Index ¶
- Constants
- func DefaultValidator(candidatePaths []string) (string, error)
- func FilesRendered(fams []LogFamily) []string
- func FitVerdictFor(theoreticalMax, availBytes uint64) string
- func PendingActivation(mainPath string) bool
- func RenderFile(policy EffectivePolicy, fams []LogFamily, file string) string
- type DiskFacts
- type EffectivePolicy
- type FamilyPolicy
- type GenerateOptions
- type GeneratedState
- type LogFamily
- type Overrides
- type Profile
- type ReadinessOptions
- type ReadinessResult
- type ReadinessVerdict
- type RecoverResult
- type SelfBoundedLog
- type Validator
- type VolumeClass
Constants ¶
const ( ClassEnforcementAudit = "ENFORCEMENT_AUDIT" ClassSecurityEvent = "SECURITY_EVENT" ClassModuleHighVolume = "MODULE_HIGH_VOLUME" ClassLifecycle = "LIFECYCLE_FORENSICS" ClassOperational = "OPERATIONAL" ClassDebug = "DEBUG" )
Semantic classes (R11). The most-sensitive class of a family's member logs drives its treatment; it is surfaced in `nftban logs retention status` so the operator can see which retention families are enforcement/security records vs ordinary operational/debug logs.
Byte-size units.
const ( DefaultMaxPercent = 15 // default NFTBan log budget = 15% of /var/log capacity DefaultMinDays = 7 // minimum forensic retention floor per family (days) DefaultMaxDays = 90 // retention ceiling per family (days) )
Calculator defaults. The budget derives PRIMARILY from filesystem capacity; available space is health/headroom only (owner directive: free space is volatile and must not be the permanent policy basis).
const ( FitFits = "FITS" FitTight = "TIGHT" FitOverHeadroom = "OVER_HEADROOM" FitUnknown = "UNKNOWN" )
Fit verdicts (health signal derived from available space, not policy basis).
const ( CapAchievable = "ACHIEVABLE" CapRaisedForFloor = "CAP_RAISED_FOR_FLOOR" CapFloorOverOpCap = "FLOOR_EXCEEDS_OPERATOR_CAP" CapInsufficientHead = "INSUFFICIENT_HEADROOM" CapFloorOverCapacity = "FLOOR_EXCEEDS_CAPACITY" )
Capacity verdicts (structural achievability, distinct from the headroom FitVerdict).
const ( RecoverNone = "none" // no journal present — nothing to do RecoverAlreadyWhole = "already_whole" // journal present but the set is already uniform RecoverRolledFwd = "rolled_forward" RecoverRolledBack = "rolled_back" RecoverDiscardedStale = "discarded_stale" // legacy/corrupt journal — safe to drop (see note) )
Recovery action strings (stable; asserted by tests and surfaced in status).
const DefaultConfPath = "/etc/nftban/conf.d/logs.conf"
DefaultConfPath is the operator override authority for log retention.
const DefaultLogDir = "/var/log"
DefaultLogDir is the filesystem whose capacity backs the retention budget.
const GeneratorVersion = "1"
GeneratorVersion is the version of the generation transaction/state writer, distinct from PolicyVersion (the rendered-policy + state schema version).
const PolicyVersion = "1"
PolicyVersion is the schema version of the generated policy + state record. Bump when the generated logrotate structure or the state schema changes.
Variables ¶
This section is empty.
Functions ¶
func DefaultValidator ¶
DefaultValidator runs `logrotate -d --state <temp> <candidate>...`:
- `-d` (debug): parse + simulate only, no file is rotated.
- `--state <temp>`: a throwaway state file, so /var/lib/logrotate/logrotate.status is never read or written — the real logrotate state is untouched.
- explicit candidate paths as the config args: logrotate reads ONLY those files and does not pull in /etc/logrotate.d/* (no `include` in our candidates), so the active policy is never co-loaded. Passing all candidates together validates the combined effective policy.
Missing logrotate returns an error → the caller fails safe and keeps the previous policy.
func FilesRendered ¶
FilesRendered returns the sorted set of distinct File values in the inventory (e.g. ["main","suricata"]).
func FitVerdictFor ¶
FitVerdictFor recomputes the headroom fit verdict from a LIVE available-space reading. The CLI uses this so `status` reflects the CURRENT filesystem, not the (possibly stale) value frozen into the generated-state record.
func PendingActivation ¶
PendingActivation reports, read-only, whether an interrupted activation is still pending (a journal exists). Callers that cannot write (non-root status) use this to refuse to report ACTIVE_MATCH over an unrepaired split.
func RenderFile ¶
func RenderFile(policy EffectivePolicy, fams []LogFamily, file string) string
RenderFile renders all stanzas belonging to the given file ("main" or "suricata") as logrotate text, using the calculated per-family policy. Output is deterministic for identical (policy, fams) inputs.
Types ¶
type DiskFacts ¶
type DiskFacts struct {
Path string
TotalBytes uint64 // filesystem capacity — the STABLE budget basis
AvailBytes uint64 // non-root available — surfaced as headroom/health ONLY
}
DiskFacts is the /var/log filesystem capacity snapshot. It is gathered by DetectDiskFacts (which reads the filesystem) and passed to Calculate as a pure input, so the calculator itself stays deterministic and side-effect free.
func DetectDiskFacts ¶
DetectDiskFacts returns the capacity and non-root-available bytes of the filesystem containing path. It reads the filesystem (side effect) and is therefore kept out of Calculate, which takes DiskFacts as a pure input.
TotalBytes uses Blocks (fundamental block count) — the stable capacity basis. AvailBytes uses Bavail (blocks available to non-root) — surfaced only as headroom/health, never as the permanent budget basis.
type EffectivePolicy ¶
type EffectivePolicy struct {
BudgetBytes uint64 // effective budget (may be raised to the min-achievable floor)
TheoreticalMaxBytes uint64 // UNCOMPRESSED worst-case retained bytes (compression not modeled — conservative upper bound, not expected disk use)
UnboundedCount int
FitVerdict string // headroom vs live available space (FITS/TIGHT/OVER_HEADROOM) — volatile
Families []FamilyPolicy
Profile Profile
Disk DiskFacts
PolicySource string // "automatic" | "operator-override"
PolicyVersion string
// R7 capacity/achievability (structural, vs total capacity + operator cap):
RequestedBudgetBytes uint64 // capacity*pct capped by MaxBytes, BEFORE the floor raise
MinimumAchievableBytes uint64 // sum of per-family forensic floors (generations x minSize) + fixed worst-case
CapRaisedForFloor bool // the forensic floor forced the budget above the requested budget
OperatorCapExceeded bool // MaxBytes set but the min-achievable floor exceeds it
Achievable bool // false => the minimum policy does not fit the filesystem
CapacityVerdict string // ACHIEVABLE | CAP_RAISED_FOR_FLOOR | FLOOR_EXCEEDS_OPERATOR_CAP | INSUFFICIENT_HEADROOM | FLOOR_EXCEEDS_CAPACITY
// R6 forensic-floor honesty: the hard floor is expressed in GENERATIONS +
// BYTES per family (rotate count x size cap). Elapsed retention DAYS are a
// target/best-case only — under size-triggered rotation a busy log rotates
// before its cadence, so real elapsed days can be shorter while the
// generation/byte floor still holds.
ForensicFloorKind string // "generations_and_bytes"
}
EffectivePolicy is the calculator's full output.
func Calculate ¶
func Calculate(disk DiskFacts, prof Profile, o Overrides, fams []LogFamily) (EffectivePolicy, error)
Calculate derives the effective, bounded retention policy. It is a pure function of its inputs: it reads no files, touches no filesystem, and never invokes logrotate. Invariants guaranteed on success:
- UnboundedCount == 0 (every family has a size cap)
- TheoreticalMaxBytes <= BudgetBytes
- each family retains at least its forensic floor (>= min(FloorDays,MinDays))
- a valid operator override wins; an invalid one was already rejected by Validate
type FamilyPolicy ¶
type FamilyPolicy struct {
Key string `json:"key"`
RotateCount int `json:"rotate_count"` // retained generations (hard floor in generations)
SizeCapBytes uint64 `json:"size_cap_bytes"`
PathCount int `json:"path_count"` // files this family governs; worst case scales with it
RetentionDays int `json:"target_retention_days"` // best-case elapsed days; size pressure may reduce actual days
WorstCaseBytes uint64 `json:"worst_case_bytes"` // RotateCount * SizeCapBytes * len(Paths), uncompressed
ForensicFloorDays int `json:"forensic_floor_target_days"` // TARGET floor (NOT guaranteed under size pressure)
CeilingDays int `json:"ceiling_days"`
SizeTriggered bool `json:"size_triggered"` // a size cap exists -> may rotate before cadence
// R11 semantic classification (surfaced by the CLI so enforcement/security
// records are distinguishable from ordinary operational/debug logs).
SemanticClass string `json:"semantic_class"`
AuthorityRole string `json:"authority_role"` // "authoritative" | "projection"
WriterStrategy string `json:"writer_strategy"` // "copytruncate" | "rename+create"
ReaderStrategy string `json:"reader_strategy"` // "same-inode" | "path-following"
}
FamilyPolicy is the calculated effective policy for one log family. The HARD forensic floor is RotateCount generations x SizeCapBytes = WorstCaseBytes; the day figures are TARGETS (best-case at/under the size cap), NOT guaranteed elapsed retention when size-triggered rotation fires before the cadence.
type GenerateOptions ¶
type GenerateOptions struct {
LogDir string // filesystem whose capacity backs the budget (default /var/log)
MainPath string // e.g. /etc/logrotate.d/nftban (required)
SuricataPath string // e.g. /etc/logrotate.d/nftban-suricata ("" to skip)
StatePath string // generated-state JSON path
LockPath string // exclusive generation lock ("" -> derived next to StatePath/MainPath)
Families []LogFamily
Overrides Overrides
Disk DiskFacts // if zero-value, DetectDiskFacts(LogDir) is used
Profile Profile // if empty Name, ClassifyProfile is used
PanelPresent bool // input to profile classification when Profile is auto
SourceVersion string // NFTBan version/commit stamped into the state record
Reason string // "install" | "config-change" | "timer" | "manual"
Now time.Time // injected clock (zero -> time.Now().UTC())
Validator Validator // injectable (nil -> DefaultValidator)
}
GenerateOptions parameterizes the transaction. Target paths are absolute; the temp/rename happens in each target's own directory so each rename is atomic on the same filesystem.
type GeneratedState ¶
type GeneratedState struct {
PolicyVersion string `json:"policy_version"`
GeneratorVersion string `json:"generator_version"`
GeneratedAt string `json:"generated_at"`
SourceVersion string `json:"source_version"`
Reason string `json:"generation_reason"`
Profile Profile `json:"profile"`
Disk DiskFacts `json:"filesystem"`
Overrides Overrides `json:"operator_overrides"`
PolicySource string `json:"policy_source"`
BudgetBytes uint64 `json:"budget_bytes"`
RequestedBudgetBytes uint64 `json:"requested_budget_bytes"`
MinimumAchievableBytes uint64 `json:"minimum_achievable_bytes"`
TheoreticalMaxBytes uint64 `json:"theoretical_max_bytes_uncompressed"`
UnboundedCount int `json:"unbounded_stanzas"`
FitVerdict string `json:"fit_verdict"`
CapacityVerdict string `json:"capacity_verdict"`
Achievable bool `json:"achievable"`
CapRaisedForFloor bool `json:"cap_raised_for_floor"`
OperatorCapExceeded bool `json:"operator_cap_exceeded"`
ForensicFloorKind string `json:"forensic_floor_kind"`
Families []FamilyPolicy `json:"families"` // each carries ForensicFloorDays + CeilingDays
ValidationCmd string `json:"validation_cmd"`
ValidationOK bool `json:"validation_ok"`
ActivePolicyHashes map[string]string `json:"active_policy_hashes"`
PreviousPolicyHashes map[string]string `json:"previous_policy_hashes"`
// Unchanged is a RETURN-ONLY signal (never persisted): Generate set it when it
// detected the effective policy was already active and skipped all writes.
Unchanged bool `json:"-"`
}
GeneratedState is the evidence record written after a successful activation.
func Generate ¶
func Generate(opts GenerateOptions) (GeneratedState, error)
Generate runs the full transaction and returns the resulting state. On any error it guarantees the previously-active policy files are left intact (both files — no split-brain) and no state record is written.
type LogFamily ¶
type LogFamily struct {
Key string
Paths []string
File string // "main" | "suricata"
Cadence string // "daily" | "weekly" | "monthly"
Volume VolumeClass
Weight int // weighted share of the distributable size budget
FloorDays int // minimum retention (forensic floor) for this family
CeilingDays int // per-family safety ceiling on retention (0 = derive from volume)
BaseRotate int // reference rotate (shipped-baseline value)
BaseSizeBytes uint64 // ceiling size cap (shipped-baseline value)
Fixed bool // keep base policy, do not budget-scale (report documents)
// rotation mechanism (reproduced verbatim by the renderer)
Copytruncate bool
Delaycompress bool
Create string // e.g. "0640 nftban nftban" ("" = omit)
Su string // e.g. "suricata nftban" ("" = omit)
Olddir string // e.g. "/var/log/nftban/reports/archive" ("" = omit)
CreateOlddir string // e.g. "0750 nftban nftban" ("" = omit)
UseMaxsize bool // emit `maxsize` instead of `size` (report documents)
PostrotateUSR2 bool // emit the Suricata USR2 postrotate reopen block
// R11 (firewall-logs vs general-logs authority — Stream J minimum).
SemanticClass string // ENFORCEMENT_AUDIT | SECURITY_EVENT | MODULE_HIGH_VOLUME | LIFECYCLE_FORENSICS | OPERATIONAL | DEBUG
Primary bool // true = authoritative record; false = a projection/derived surface
}
LogFamily describes one logrotate stanza's identity, budget metadata, and rotation mechanism. The mechanism fields mirror the corrected shipped templates so the renderer reproduces them faithfully.
func DefaultFamilies ¶
func DefaultFamilies() []LogFamily
DefaultFamilies returns the canonical family inventory mirroring the corrected shipped baseline templates (Gate B Phase 1).
type Overrides ¶
type Overrides struct {
Mode string // "" or "auto" (default) | "manual"
MaxPercent int // cap budget at this %% of capacity (0 = default)
MaxBytes uint64 // absolute budget cap (0 = unset)
MinDays int // per-family retention floor (0 = default)
MaxDays int // per-family retention ceiling (0 = default)
Profile string // "" or "auto" | "small" | "standard" | "high-volume"
}
Overrides is the operator override model. In Phases 4-5 it is supplied directly; conf.d/logs.conf wiring is deferred. Zero value == fully automatic.
func LoadOverrides ¶
LoadOverrides reads logs.conf into an Overrides. An absent file yields the zero value (fully automatic). Present-but-uncommented keys become overrides. The result is validated; invalid values return an error (no silent fallback).
type Profile ¶
Profile is the retention profile classification. It is derived from disk capacity (primary) and may be nudged by control-panel presence; an operator override can force it.
func ClassifyProfile ¶
ClassifyProfile derives the retention profile from capacity (primary basis) and an operator override. Panel presence nudges the classification upward because control-panel hosts run more web/mail log producers.
type ReadinessOptions ¶
type ReadinessOptions struct {
MainPath string // active generated main policy (required)
SuricataPath string // active generated suricata policy ("" if not installed)
StatePath string // generated-state record (may be absent -> fallback)
TemplatePath string // shipped bounded fallback template (identity source for READY_FALLBACK)
Validator Validator // nil -> DefaultValidator (real logrotate -d)
}
ReadinessOptions parameterizes the check (paths + injectable validator).
type ReadinessResult ¶
type ReadinessResult struct {
Verdict ReadinessVerdict `json:"verdict"`
PolicySource string `json:"policy_source"` // "generated" | "fallback" | "none"
PolicyPath string `json:"policy_path"`
FileMode string `json:"file_mode"`
FileBytes int64 `json:"file_bytes"`
ValidationResult string `json:"validation_result"` // "valid" | error text | "not_run"
// main policy
ActiveMainHash string `json:"active_main_hash"`
ExpectedMainHash string `json:"expected_main_hash,omitempty"` // from state (generated) or template (fallback)
MainHashMatch bool `json:"main_hash_match"`
// DELTA-L2: fallback identity + boundedness
FallbackIdentityMatch bool `json:"fallback_identity_match"`
FallbackTemplateHash string `json:"fallback_template_hash,omitempty"`
UnboundedStanzas int `json:"unbounded_stanzas"`
// DELTA-L3: suricata generated policy (part of the generated set)
SuricataApplicable bool `json:"suricata_applicable"`
ActiveSuricataHash string `json:"active_suricata_hash,omitempty"`
ExpSuricataHash string `json:"expected_suricata_hash,omitempty"`
SuricataHashMatch bool `json:"suricata_hash_match"`
JournalRecovered string `json:"journal_recovered,omitempty"`
Reason string `json:"reason,omitempty"`
Remediation string `json:"remediation,omitempty"`
SelfHealPending bool `json:"self_heal_pending"` // true for READY_FALLBACK
}
ReadinessResult is the structured, durable outcome; every consumer (CLI, installer assertion, acceptance collector) reads these fields so no consumer re-implements a weaker check.
func Readiness ¶
func Readiness(opts ReadinessOptions) ReadinessResult
Readiness computes the post-generation policy-readiness verdict — the single authority for the CLI, the installer assertion, and the acceptance collector.
func (ReadinessResult) Ready ¶
func (r ReadinessResult) Ready() bool
Ready reports whether the verdict allows a COMMITTED install (generated or a verified bounded fallback). Only NOT_READY blocks COMMITTED.
type ReadinessVerdict ¶
type ReadinessVerdict string
ReadinessVerdict is the install-readiness classification of the active policy.
const ( ReadyGenerated ReadinessVerdict = "READY_GENERATED" // whole generated set matches state ReadyFallback ReadinessVerdict = "READY_FALLBACK" // active main == approved bounded template NotReady ReadinessVerdict = "NOT_READY" // no valid active policy — install must be DEGRADED )
type RecoverResult ¶
type RecoverResult struct {
JournalFound bool // an activation journal existed
Action string // one of the Recover* constants
Targets []string // targets named by the journal
FromState string // "uniform_new" | "uniform_old" | "split" (as found)
}
RecoverResult reports what Recover observed and did.
func Recover ¶
func Recover(mainPath string) (RecoverResult, error)
Recover completes or unwinds an interrupted activation so the on-disk policy set is uniform. It is a no-op (JournalFound=false) when no journal exists. Requires write access to the target directory + staging dir to repair.
type SelfBoundedLog ¶
type SelfBoundedLog struct {
Path string // may contain <run_id> for per-run records
SemanticClass string
BoundedBy string // the mechanism that bounds it (not logrotate)
}
SelfBoundedLog is a log NOT rotated by logrotate: it is bounded by its own writer/pruner. Z6 classifies these too, so the semantic-class coverage is complete across ALL nftban logs, not only the logrotate-managed families — the retention MECHANISM differs (prune vs rotate) but the SIEM/forensic class is still declared.
func SelfBoundedForensicLogs ¶
func SelfBoundedForensicLogs() []SelfBoundedLog
SelfBoundedForensicLogs returns the self-bounded (non-logrotate) nftban logs with their semantic class. The per-update-run record under /var/log/nftban/update-runs/<run_id>/ — run.jsonl (machine JSONL stream), human.log (operator slice), installer.log (per-run installer copy) — is LIFECYCLE_FORENSICS, bounded by _forensic_prune (keeps a fixed number of recent runs) rather than logrotate. Classifying them here closes the Z6 gap where these logs had no semantic class.
type Validator ¶
Validator validates one or more candidate logrotate files TOGETHER without executing any rotation and without touching the real logrotate state or the active policy. It returns the exact command string (for the state record) and an error if any candidate is invalid or logrotate is unavailable.
type VolumeClass ¶
type VolumeClass int
VolumeClass groups families by expected write rate; it drives budget weighting and the default retention window.
const ( VolumeLow VolumeClass = iota VolumeMedium VolumeHigh )
func (VolumeClass) String ¶
func (v VolumeClass) String() string