uninstall

package
v1.131.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 25, 2026 License: MPL-2.0 Imports: 14 Imported by: 0

Documentation

Overview

============================================================================= NFTBan v1.100 PR-23 — Uninstall Mutation Phase 1 (Authority Release Core) ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-uninstall-apply" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-20" meta:description="Authority release core — PR-23 uninstall mutation orchestrator" meta:inventory.files="internal/installer/uninstall/apply.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="nftband.service" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

PR-23 authorized scope (frozen 2026-04-20):

Allowed mutation:
  - releasing nftban kernel authority (flush + delete ip/ip6 nftban tables)
  - controlled teardown of nftban-owned service (stop + disable + mask)
  - emergency SSH safety injection before mutation, removal after success

NOT allowed (non-goals):
  - external firewall restoration (PR-24)
  - filesystem artifact deletion / purge semantics (PR-25)
  - .conf.local touch — read or write
  - cross-system cleanup (logrotate, timers beyond mask, user/group)
  - any package-manager transactions

This file is the ONLY mutation path in the uninstall package. Every other file in internal/installer/uninstall/ (authority.go, prior.go, plan.go) remains strictly read-only — the G3-UN-NO-MUTATION CI gate scopes its structural audit to exclude this file explicitly.

Caller contract:

  • Operator passed --confirm-mutation on the CLI (flags.go validates)
  • Caller classified authority via Classify() and verified one of:
  • Classify().State == AuthorityNFTBan
  • Classify().State == AuthorityAmbiguous AND Classify().Ambiguity == AmbiguityOrphanNFTBan Any other pre-state must refuse BEFORE calling Apply.

=============================================================================

============================================================================= NFTBan v1.100.4 — Uninstall Artifact Removal (PR-25-equivalent, surgical) ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-uninstall-artifacts" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-05-02" meta:description="Filesystem-cleanup counterpart of payload.StageAll for source-install uninstall" meta:inventory.files="internal/installer/uninstall/artifacts.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="nftban*.service, nftban*.timer, nftban*.socket, nftband.service" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

UPSTREAM-UNINSTALL-INCOMPLETE-001 — v1.100.4 release blocker fix.

PR-23 introduced uninstall.Apply for kernel + service authority release. PR-25 was originally slotted for filesystem artifact removal but was repurposed for restore-execution; the gap left every source-install uninstall leaving the entire payload on disk (110 paths / 49 unit files / 11 timers cross-distro, evidence in VANILLA_MATRIX Round 1 RE-RUN 20260501T214954Z). The phantom mask symlink that the existing apply.go step-8 mask leaves at /etc/systemd/system/nftband.service then fails reinstall with "Unit file is masked".

This file is the surgical fix:

  • RemoveArtifacts walks payload.Destinations() — the single source of truth shared with install — and deletes every staged path PER MODE.
  • It unmasks nftband.service before unit-file rm so the mask symlink does not survive uninstall (paired with services.StartDaemon's defensive ServiceUnmask call for installs onto unclean state).
  • It strips the immutable bit on protected dirs before rm so chattr +i guards do not silently turn rm into a no-op.
  • It does daemon-reload + reset-failed at the end so systemd's view of the world matches disk.

SAFETY GUARANTEE (G3-UN-NO-MUTATION exception): the only paths this function may rm are

(a) paths derived from payload.Destinations(distro), or
(b) the explicit uninstall-owned runtime/state list:
    /var/lib/nftban, /var/log/nftban

No globbing, no broad rm-rf, no walking arbitrary directories. The destination catalog is bounded by buildEntries; the runtime/state list is bounded by this file. Every other path is unreachable from this code.

=============================================================================

============================================================================= NFTBan v1.100 PR-22 — Uninstall Authority Classifier (Read-Only) ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-uninstall-authority" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-19" meta:description="4-state authority classifier for uninstall planning" meta:inventory.files="internal/installer/uninstall/authority.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

PR-22 scope lock: READ-ONLY classification. This file does not mutate kernel, service, or filesystem state. No nft add/flush/delete. No systemctl stop/disable. Strictly detection.

=============================================================================

============================================================================= NFTBan v1.100 PR-22 — Uninstall Plan Struct + Renderer ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-uninstall-plan" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-19" meta:description="Release-plan struct + contract-language renderer" meta:inventory.files="internal/installer/uninstall/plan.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="none" =============================================================================

Per PR-22 contract §"Plan output — contract-language rendering": the rendered plan reads like a release contract preview. Every field references the frozen V1100 contract row it implements. No debug dumps, no implementation-language leakage.

PR-22 scope lock: plan rendering is pure in/pure out. No mutation, no I/O beyond Render's io.Writer. The Plan struct is the output of BuildPlan; consumers render it once.

=============================================================================

============================================================================= NFTBan v1.100 PR-P2-1 — Prior-Authority Record Detector (Read-Only) ============================================================================= SPDX-License-Identifier: MPL-2.0 meta:name="installer-uninstall-prior" meta:type="lib" meta:owner="Antonios Voulvoulis <contact@nftban.com>" meta:created_date="2026-04-19" meta:description="5-state prior-authority record detection for uninstall planning" meta:inventory.files="internal/installer/uninstall/prior.go" meta:inventory.binaries="" meta:inventory.env_vars="" meta:inventory.config_files="" meta:inventory.systemd_units="" meta:inventory.network="" meta:inventory.privileges="root" =============================================================================

PR-22 scope lock (preserved): READ-ONLY. Probes whether a prior-authority artifact exists and whether its contents are usable for a future --restore-prior-authority decision. Never writes, never deletes.

PR-P2-1 strengthening: PR-24 restore-enforcement cannot trust under-defined records. This file hardens the schema and parsing semantics so "usable" has a stricter meaning:

NoRecord              — no artifact on disk
RecordMalformed       — artifact exists but JSON does not parse
RecordIncomplete      — JSON parses but required fields are missing,
                        unknown, or semantically unusable
RecordUsableActive    — all requirements met AND the prior firewall
                        was active at install time
RecordUsableInactive  — all requirements met AND the prior firewall
                        was explicitly recorded as NOT active at
                        install time

Backward-safety rule (PR-P2-1): older records from before this tightening that lack recorded_at / installer_version / active_at_install are reclassified as RecordIncomplete by design. This is intentional safety tightening so PR-24 restore logic never trusts under-defined evidence.

=============================================================================

Index

Constants

View Source
const PlanSchemaVersion = "1.100.0"

PlanSchemaVersion is the current plan wire contract.

View Source
const PriorAuthorityPath = "/var/lib/nftban/state/prior_authority.json"

PriorAuthorityPath is the canonical on-disk location for the prior-authority record. Kept as a package constant so PR-23's install-side writer and any future reader agree on one location.

View Source
const PriorRecordSchemaVersion = "1.100.0"

PriorRecordSchemaVersion is the currently-expected on-disk contract. A record with a different schema_version is RecordIncomplete.

Variables

This section is empty.

Functions

This section is empty.

Types

type AmbiguityKind

type AmbiguityKind string

AmbiguityKind sub-classifies AuthorityAmbiguous so consumers that must decide whether ambiguity is recoverable (proceed with caution) or blocking (refuse and ask operator to resolve) can do so without re-deriving the host signal — the single source of truth for that decision lives here, at the classifier layer.

PR-23 introduced this split because Apply (authority release) has legitimate cleanup work for orphan nftban artifacts even when the classifier cannot confirm pure AuthorityNFTBan. Blanket-refusing Ambiguous would leave operators stuck with no sanctioned cleanup path for half-installed hosts.

const (
	// AmbiguityNone is the sentinel for non-ambiguous classifications
	// (AuthorityNFTBan / AuthorityExternal / AuthorityNone). Ambiguity
	// MUST be AmbiguityNone for any State != AuthorityAmbiguous.
	AmbiguityNone AmbiguityKind = ""

	// AmbiguityOrphanNFTBan means the host has nftban kernel / service
	// artifacts that are NOT all present together (partial state), AND
	// no external firewall is observable. Recoverable: Apply may
	// proceed via the emergency-SSH-injected release path to clean up
	// the orphan artifacts. PR-22A first added this distinction; PR-23
	// operationalises it.
	AmbiguityOrphanNFTBan AmbiguityKind = "orphan_nftban"

	// AmbiguityConflictExternal means the host has either (a) both
	// nftban AND an external firewall observable, or (b) multiple
	// external firewalls active simultaneously. Blocking: Apply MUST
	// refuse until the operator resolves authority ownership. Silent
	// takeover from this state is exactly the regression class the
	// audits have been hardening against.
	AmbiguityConflictExternal AmbiguityKind = "conflict_external"
)

type ApplyConfig

type ApplyConfig struct {
	// SSHPort is the port the emergency SSH safety table accepts on.
	// Must be > 0 and a valid TCP port.
	SSHPort int

	// Mode is the §4.4 artifact-removal policy. Default ModeRemove.
	// Added in v1.100.4 (UPSTREAM-UNINSTALL-INCOMPLETE-001) to wire
	// payload-symmetric artifact removal into the existing release
	// sequence.
	Mode Mode

	// Distro is passed through to RemoveArtifacts so the destination
	// catalog matches install-time staging (currently only polkit
	// branch differs by distro family).
	Distro *detect.DistroInfo
}

ApplyConfig is the input to Apply. Intentionally minimal — Apply is authority release core and should not grow consumer-specific knobs.

type ApplyResult

type ApplyResult struct {
	// State is the terminal installer state the dispatcher should
	// persist via sf.Transition. Exactly one of:
	//   StateUninstallReleased     — full success
	//   StateUninstallFailedRelease — kernel mutation started but
	//                                 did not complete / validate
	//   StateDegraded              — kernel released but service
	//                                 teardown incomplete (authority
	//                                 released, state persists)
	//   StateFailedNoFirewall      — emergency SSH inject failed;
	//                                 no mutation was attempted
	State state.InstallState
	// Reason is the human-readable rationale for State. Placed in the
	// state file's FailureReason on failure, logged on success.
	Reason string
	// EmergencyInjected reports whether the emergency SSH table was
	// inserted during this run. True means the table was put in place;
	// for StateUninstallReleased it has been cleanly removed, for
	// failure states it MAY still be present as a safety net (leaving
	// it up is the deliberate failure-mode behaviour — an
	// over-permissive SSH rule is safer than a potential lockout).
	EmergencyInjected bool
	// Steps is an ordered audit trail of each mutation step attempted.
	// Used by the dispatcher for real-host evidence output + test
	// assertions.
	Steps []StepResult
}

ApplyResult is the output of Apply. Consumed by the dispatcher (cmd/nftban-installer/uninstall_apply.go) to transition the state file and set the process exit code.

func Apply

func Apply(exec executor.Executor, cfg *ApplyConfig, log *logging.Logger) *ApplyResult

Apply runs the v1.100.4 authority release mutation sequence.

The sequence is 11 explicit steps, executed in order:

  1. Inject emergency SSH safety table (inet nftban_install_emergency)
  2. Stop nftband.service (prevent it from fighting kernel mutation)
  3. Flush ip nftban table
  4. Flush ip6 nftban table (if present)
  5. Delete ip nftban table
  6. Delete ip6 nftban table (if present)
  7. Disable nftband.service
  8. Remove staged payload artifacts per cfg.Mode (v1.100.4 — closes UPSTREAM-UNINSTALL-INCOMPLETE-001). Walks payload.Destinations and rm -rf's installer-owned paths; mode gates operator-owned territory. ServiceUnmask("nftband.service") happens inside this step before unit-file rm.
  9. Mask nftband.service ONLY if its unit file still exists (skipped when step 8 removed it — masking an absent unit creates a phantom /etc/systemd/system/nftband.service -> /dev/null symlink that fails the next reinstall with "Unit file is masked")
  10. Validate end-state: - no ip nftban / ip6 nftban tables - nftband.service not active - emergency SSH table STILL PRESENT (step 11 removes it)
  11. Remove emergency SSH table (warn-only on failure — leaving an over-permissive SSH rule in place is safer than lockout)

Failure mapping:

Step 1 fails    → StateFailedNoFirewall (no kernel mutation; safe to retry)
Step 2 fail     → log warn, continue (stop-of-already-stopped is OK)
Steps 3-6 fail  → StateUninstallFailedRelease (kernel partial; emergency up)
Steps 7+9 fail  → StateDegraded (kernel released; service lingers)
Step 8 fail     → log warn, continue (best-effort artifact removal;
                  end-state residue is the operator-visible signal)
Step 10 fail    → StateUninstallFailedRelease (end-state mismatch)
Step 11 fail    → log warn; State stays StateUninstallReleased (over-permissive safer than lockout)
All pass        → StateUninstallReleased

type ClassifyResult

type ClassifyResult struct {
	State    CurrentAuthority `json:"state"`
	External string           `json:"external,omitempty"` // ufw / firewalld / iptables / csf / ""
	// Ambiguity sub-classifies AuthorityAmbiguous so consumers can
	// decide recoverable-vs-blocking without re-deriving host signals.
	// AmbiguityNone for all non-Ambiguous states (invariant).
	Ambiguity AmbiguityKind `json:"ambiguity,omitempty"`
	Notes     []string      `json:"notes,omitempty"` // human-readable rationale for the classification
}

ClassifyResult is the full read-only observation returned by Classify. Plan renderers consume this rather than re-probing.

func Classify

func Classify(exec executor.Executor, log *logging.Logger) *ClassifyResult

Classify inspects the current host state and returns the 4-state classification plus detection notes. It is READ-ONLY and idempotent.

Detection inputs (all read-only):

  • exec.NftTableExists("ip", "nftban") — is nftban authoritative
  • exec.ServiceActive("nftband.service") — is the daemon up
  • extfw.Detect(exec, log) — unified external-firewall detection (see internal/installer/extfw — single source of truth for UFW / firewalld / iptables / CSF probing across install, update, and uninstall lifecycle paths; PR-P2-2)

The classifier does NOT: invoke nft add/flush/delete, run systemctl stop/disable, read any config file beyond the FileExists probe, or write anything.

type CurrentAuthority

type CurrentAuthority string

CurrentAuthority is one of four mutually exclusive states returned by the PR-22 classifier. The three conceptual axes are:

  • does nftban own authority right now (ip nftban table + active daemon)
  • is an external firewall observable (UFW / firewalld / iptables / CSF)
  • is the detection result conclusive

per the frozen V1100 contract §4.3 and PR-22 contract §"Authority classification".

const (
	// AuthorityNFTBan means nftban is authoritative on this host right now.
	AuthorityNFTBan CurrentAuthority = "nftban"

	// AuthorityExternal means an external firewall appears authoritative.
	// Uninstall of nftban in this state is cheap (nothing to remove on
	// the kernel side) but the classifier records which external firewall
	// for the restore-plan renderer downstream.
	AuthorityExternal CurrentAuthority = "external"

	// AuthorityNone means no authoritative firewall is detectable.
	// Uninstall in this state is a no-op on kernel authority.
	AuthorityNone CurrentAuthority = "none"

	// AuthorityAmbiguous means both nftban and external appear present,
	// OR the detection is inconclusive. Classifier returns ambiguity
	// rather than guessing — the plan renderer surfaces the state and
	// the operator decides.
	AuthorityAmbiguous CurrentAuthority = "ambiguous"
)

type IncompleteReason

type IncompleteReason string

IncompleteReason is a typed enum describing exactly why a record was classified as RecordIncomplete. Machine-consumable so downstream tooling does not have to substring-match human notes.

const (
	IncompleteReasonNone                    IncompleteReason = ""
	IncompleteReasonUnreadable              IncompleteReason = "unreadable"
	IncompleteReasonSchemaMismatch          IncompleteReason = "schema_mismatch"
	IncompleteReasonMissingFirewallType     IncompleteReason = "missing_firewall_type"
	IncompleteReasonUnknownFirewallType     IncompleteReason = "unknown_firewall_type"
	IncompleteReasonMissingRecordedAt       IncompleteReason = "missing_recorded_at"
	IncompleteReasonMissingInstallerVersion IncompleteReason = "missing_installer_version"
	IncompleteReasonMissingActiveAtInstall  IncompleteReason = "missing_active_at_install"
)

type Mode

type Mode string

Mode encodes the operator's requested uninstall mode. Corresponds directly to the V1100 §4.4 purge/remove table columns.

const (
	ModeRemove        Mode = "remove"          // §4.4 column 1
	ModePurge         Mode = "purge"           // §4.4 column 2 (default purge — preserves operator .conf.local)
	ModePurgeForceDOC Mode = "purge+force-doc" // §4.4 column 3 — purge + --force-delete-operator-config
)

type Plan

type Plan struct {
	// SchemaVersion locks the on-wire contract for tooling consumers.
	SchemaVersion string `json:"schema_version"`

	// RequestedMode is one of ModeRemove / ModePurge / ModePurgeForceDOC.
	RequestedMode Mode `json:"requested_mode"`

	// ArtifactPolicy is a one-line description of what the §4.4 row
	// authorises for this mode. Present for operator readability; the
	// authoritative source is the frozen contract table.
	ArtifactPolicy string `json:"artifact_policy"`

	// CurrentAuthority is the 4-state classifier output (nftban /
	// external / none / ambiguous).
	CurrentAuthority CurrentAuthority `json:"current_authority"`

	// DetectedExternal names the external firewall observed, or ""
	// when none. Populated regardless of RestoreRequested so the
	// operator sees the ambient reality.
	DetectedExternal string `json:"detected_external,omitempty"`

	// TargetAuthority names the authority state the plan would produce
	// if a later PR actually executed mutation. Per Q2=C (frozen), the
	// default is AuthorityNone and the restore path is opt-in.
	TargetAuthority CurrentAuthority `json:"target_authority"`

	// RestoreRequested reflects the presence of --restore-prior-authority
	// on the command line.
	RestoreRequested bool `json:"restore_requested"`

	// RestoreAuthorized is true only when RestoreRequested AND
	// PriorState.IsUsable() returns true (both UsableActive and
	// UsableInactive qualify — active/inactive is a separate semantic
	// dimension surfaced via PriorActiveAtInstall). PR-24 enforces
	// this gate.
	RestoreAuthorized bool `json:"restore_authorized"`

	// PriorState is the 5-state probe result for the prior-authority
	// record (PR-P2-1 hardened). Populated regardless of RestoreRequested
	// so the operator always sees the underlying ambiguity.
	PriorState PriorRecordState `json:"prior_state"`

	// PriorIncompleteReason is populated when PriorState ==
	// RecordIncomplete and gives a machine-consumable reason code so
	// downstream tooling doesn't have to substring-match human notes.
	// Empty string when the state is not Incomplete.
	PriorIncompleteReason IncompleteReason `json:"prior_incomplete_reason,omitempty"`

	// PriorActiveAtInstall surfaces the active-at-install-time
	// distinction when the record is usable. Three values:
	//
	//   *true  — the prior firewall was actively holding authority at
	//            install time; restore = re-enable a firewall that was
	//            running
	//   *false — the prior firewall was present but NOT active at install
	//            time; restore = enable a firewall that was NOT running
	//            (a different operation; operator should see this)
	//   nil    — unknown (usable state not reached)
	//
	// PR-24 uses this to decide whether restoration needs a second
	// confirmation prompt.
	PriorActiveAtInstall *bool `json:"prior_active_at_install,omitempty"`

	// Warnings is the operator-visible list of concerns that do NOT
	// abort the plan but should be surfaced. Example: "restore flag
	// set but no prior-authority record on disk".
	Warnings []string `json:"warnings,omitempty"`

	// PhasesThatWouldMutate lists, in contract language, which later
	// phases would perform mutation if this PR's scope were expanded.
	// PR-22 ships NONE of these — the list is informational.
	PhasesThatWouldMutate []string `json:"phases_that_would_mutate,omitempty"`

	// ScopeBoundary is the scope-lock disclaimer, carried as a
	// first-class field so JSON consumers see the same "no mutation
	// performed" guarantee the human text render carries. Addresses
	// the audit concern about text/JSON renderer drift: if the field
	// were only in Render(), machine tooling that consumed JSON could
	// miss the fact that --mode=uninstall did nothing.
	ScopeBoundary string `json:"scope_boundary"`

	// NoMutationPerformed is a machine-consumable flag that mirrors
	// ScopeBoundary. For PR-22 it is always true by construction —
	// no code path in the orchestrator performs mutation. Later PRs
	// that add mutation must explicitly flip this to false when
	// mutation actually occurs.
	NoMutationPerformed bool `json:"no_mutation_performed"`
}

Plan is the complete release-plan preview computed by BuildPlan and rendered for the operator. Every field corresponds to a decision frozen in V1100_LIFECYCLE_COMPLETION_CONTRACT.md §13.

func BuildPlan

func BuildPlan(mode Mode, auth *ClassifyResult, prior *ProbeResult, restoreRequested bool) *Plan

BuildPlan assembles a Plan from the read-only classifier + probe results + operator flags. Pure function — all inputs, no I/O.

restoreRequested should be set from the presence of the --restore-prior-authority flag at call time.

func (*Plan) JSON

func (p *Plan) JSON() ([]byte, error)

JSON returns the plan as pretty-printed JSON for machine consumers.

func (*Plan) Render

func (p *Plan) Render(w io.Writer)

Render prints the plan as a contract-language preview. Output is stable and reviewable by humans; machine consumers should use JSON.

The scope-boundary block is MANDATORY per PR-22 contract.

type PriorRecord

type PriorRecord struct {
	SchemaVersion    string     `json:"schema_version"`
	FirewallType     string     `json:"firewall_type"`
	RecordedAt       *time.Time `json:"recorded_at,omitempty"`
	InstallerVersion string     `json:"installer_version,omitempty"`
	ActiveAtInstall  *bool      `json:"active_at_install,omitempty"`
}

PriorRecord is the schema for the recorded-at-install-time prior authority snapshot. PR-22 consumes this to classify usability; it does NOT construct, write, or mutate this struct.

PR-P2-1 required fields for PriorRecordUsable* classification:

SchemaVersion     — freezes on-disk contract; reader must match
FirewallType      — "ufw" / "firewalld" / "iptables" / "csf"
RecordedAt        — timestamp when the record was written; enables
                    freshness judgement in future tooling
InstallerVersion  — version of the installer that wrote the record;
                    enables per-version quirk handling
ActiveAtInstall   — explicit tri-state pointer. nil = field absent
                    (record is incomplete); &true = prior firewall
                    was active; &false = prior firewall was explicitly
                    recorded as NOT active

json.Unmarshal tolerates unknown fields, so future additive fields do not break older readers. PR-P2-1 does NOT add signature/checksum infrastructure — that remains out of scope.

type PriorRecordState

type PriorRecordState string

PriorRecordState classifies the usability of the prior-authority artifact for PR-24 restoration-path enforcement.

const (
	PriorNoRecord             PriorRecordState = "no_record"
	PriorRecordMalformed      PriorRecordState = "record_malformed"
	PriorRecordIncomplete     PriorRecordState = "record_incomplete"
	PriorRecordUsableActive   PriorRecordState = "record_usable_active"
	PriorRecordUsableInactive PriorRecordState = "record_usable_inactive"
)

func (PriorRecordState) IsUsable

func (s PriorRecordState) IsUsable() bool

IsUsable reports whether a record-state represents a record that PR-24 may consider authorizing restoration from. The active/inactive distinction is exposed separately so the plan can surface the semantic difference; both are usable-at-this-layer.

type ProbeResult

type ProbeResult struct {
	State            PriorRecordState `json:"state"`
	Record           *PriorRecord     `json:"record,omitempty"`
	IncompleteReason IncompleteReason `json:"incomplete_reason,omitempty"`
	Notes            []string         `json:"notes,omitempty"`
}

ProbeResult is the full classification + parsed record + typed incomplete reason. Plan renderers consume this rather than re-probing.

func Probe

func Probe(exec executor.Executor, log *logging.Logger) *ProbeResult

Probe classifies the on-disk prior-authority record. READ-ONLY.

Decision order (first match wins; fall-through means "acceptable so far"):

  1. File absent → NoRecord
  2. File present but unreadable → Incomplete (Unreadable)
  3. File readable but bad JSON → Malformed
  4. JSON parses but schema mismatch → Incomplete (SchemaMismatch)
  5. Required fields missing/invalid → Incomplete (specific reason)
  6. All required fields present AND ActiveAtInstall=&true → UsableActive ActiveAtInstall=&false → UsableInactive

Read-only at every step: exec.FileExists + exec.ReadFile + json.Unmarshal + field check. No writes, no deletes, no mutation.

type RemovalResult

type RemovalResult struct {
	// Removed counts paths the function asked the executor to delete
	// (rm -rf invocations or systemd unit-file removals via rm).
	Removed int
	// Preserved counts paths that were within reach but skipped per
	// mode policy (e.g. /etc/nftban under ModeRemove, *.conf.local
	// under ModePurge).
	Preserved int
	// Failed counts paths whose delete invocation returned a non-zero
	// exit code. Non-fatal — uninstall treats best-effort as the
	// contract; CI residue check is the end-state assertion.
	Failed int
	// UnitFileRemoved reports whether nftband.service's unit file was
	// removed by this call. Apply uses this to decide whether to skip
	// the legacy mask step (mask of an absent unit recreates a phantom
	// mask symlink that then fails the next reinstall).
	UnitFileRemoved bool
	// Steps is an ordered audit trail recorded for evidence/test.
	Steps []StepResult
}

RemovalResult records the outcome of a RemoveArtifacts run. Mirrors the StepResult shape used by Apply so the caller can splice these into ApplyResult.Steps.

func RemoveArtifacts

func RemoveArtifacts(exec executor.Executor, mode Mode, distro *detect.DistroInfo, log *logging.Logger) *RemovalResult

RemoveArtifacts is the inverse of payload.StageAll for source-install uninstall plus the nftband.service unmask-symmetry fix.

Algorithm (steps map 1:1 to RemovalResult.Steps for evidence):

a. Disable + stop every nftban*.timer destination
b. Disable + stop every nftban*.service destination EXCEPT nftband.service
   (apply.go owns the nftband stop/disable in step 7)
c. ServiceUnmask("nftband.service") soft-fail — symmetric counterpart
   of services.StartDaemon's ServiceUnmask, lets unit-file rm proceed
   without a leftover /etc/systemd/system/<unit> -> /dev/null tombstone
d. chattr -R -i on protectedDirs that mode authorises (best-effort)
e. rm -rf each destination path that mode authorises, deepest-first
f. systemctl daemon-reload + systemctl reset-failed to clear systemd's
   stale unit-file view (the residue-counts.txt's leftover_units=49
   signature was systemd cache, not on-disk state, after the fix)

Every path this function deletes is bounded by SAFETY GUARANTEE in the file header. exec is the typed interface so MockExecutor records the command sequence for tests.

type StepResult

type StepResult struct {
	Name    string
	Success bool
	Detail  string
}

StepResult records the outcome of one Apply step.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL