Documentation
¶
Overview ¶
Package postinstall answers one question a package manager cannot: did THIS transaction actually commit?
WHY THIS EXISTS ¶
`dnf install` and `apt-get install` report success when the package payload is unpacked. They do not report the installer's outcome. On a stock enforcing EL9 host the bundled installer aborts with FAILED_NO_FIREWALL, the firewall never loads, and the package manager still prints "Complete!". Automation that gates on the package manager's exit code reads a firewall-less host as a successful install.
The installer already records the truth — StateFile.Transition persists a terminal state with a UTC timestamp. The gap is on the READING side.
STATE PRESENCE IS NOT STATE FRESHNESS ¶
Three termination classes run BEFORE the StateFile exists (main.go:125): flag/usage errors, --version, and lock contention (os.Exit(75)); a panic is a fourth. Those paths are CORRECT not to write state — nothing was installed. But they leave the previous install_state untouched, so a reader asking only "is it COMMITTED?" gets a yes from an unrelated, earlier transaction.
Version equality does not close this. A retry after lock contention is BY DEFINITION a same-version reinstall, so State==COMMITTED and Version==expected both hold while nothing was installed. Freshness must be correlated with the transaction itself:
StateFile.Timestamp >= the moment this transaction invoked the installer
Both operands are UTC (Transition sets time.Now().UTC()), so no timezone conversion can affect the comparison.
FAIL CLOSED. Missing, unreadable or ambiguous state is never a pass.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Options ¶
type Options struct {
// StateDir is the installer state directory.
StateDir string
// ExpectedVersion is the version the package manager just installed.
ExpectedVersion string
// NotBefore is captured IMMEDIATELY before invoking the installer — not at
// the start of the whole package script. Anything earlier widens the window
// in which a previous transaction's state would look fresh.
NotBefore time.Time
// DryRun marks a dry-run verification; authoritative state is not expected
// to change and must not be reported as a current commit.
DryRun bool
}
Options are the inputs a package script must supply.
type Result ¶
type Result struct {
Verdict Verdict
// PersistedState is what the state file said, verbatim — may be empty.
PersistedState string
// PersistedVersion is the version the state file recorded — may be empty.
PersistedVersion string
// PersistedTimestamp is the state file's UTC timestamp — may be zero.
PersistedTimestamp time.Time
// Detail is a short operator-safe explanation.
Detail string
}
Result carries the verdict plus the persisted facts behind it, so the caller can print the historical state and the current-attempt verdict as SEPARATE machine-readable fields. A historically valid COMMITTED must never be presentable as this transaction's outcome.
func Verify ¶
Verify decides whether the persisted install_state belongs to this transaction.
No tolerance window is applied. Both timestamps originate from time.Now().UTC() on the same host at nanosecond precision, and a tolerance is precisely what would let an old state look fresh.
func (Result) Tokens ¶
Tokens renders the stable machine-readable fields.
PERSISTED state and the CURRENT-ATTEMPT verdict are deliberately separate fields. Printing a bare NFTBAN_INSTALL_STATE=COMMITTED on a path where the state predates the attempt is exactly the misrepresentation this package exists to prevent.
type Verdict ¶
type Verdict string
Verdict is the outcome of a post-install verification.
const ( // CurrentCommitted is the ONLY verdict that means "this transaction committed". CurrentCommitted Verdict = "CURRENT_COMMITTED" // StaleState means the persisted state predates this transaction. The most // important verdict in this package: it is what lock contention, a panic and // any other pre-StateFile termination must produce. StaleState Verdict = "STALE_STATE" // VersionMismatch means the state belongs to a different package version. VersionMismatch Verdict = "VERSION_MISMATCH" // InstallFailed means the installer reached a terminal failure state. InstallFailed Verdict = "INSTALL_FAILED" // InstallDegraded means the installer completed with issues. InstallDegraded Verdict = "INSTALL_DEGRADED" // MissingState means no state file exists. MissingState Verdict = "MISSING_STATE" // InvalidState means the state file exists but cannot be read or understood. InvalidState Verdict = "INVALID_STATE" // DryRunNotApplied means the caller asked about a dry run, which by contract // never mutates authoritative state. DryRunNotApplied Verdict = "DRY_RUN_NOT_APPLIED" // StateReadError means the state file could not be accessed. Distinct from // MissingState: "unreadable" is not "never installed". StateReadError Verdict = "STATE_READ_ERROR" // InvalidInvocation means the verification mode itself was called wrongly. // Emitted with the same token set so callers never special-case it. InvalidInvocation Verdict = "INVALID_INVOCATION" )