PR26.1: validate installed systemd payload before StateCommitted#528
Merged
PR26.1: validate installed systemd payload before StateCommitted#528
Conversation
PR26.1 — generic install-validation hardening. Adds four new
post-install assertions consumed by the existing AllPassed gate so
StateCommitted is blocked on any failure (no phases.go change):
SYSTEMD-EXECSTART-001 every nftban unit's ExecStart/Pre/Post
local executable path exists on disk
SYSTEMD-TIMER-PAIR-001 every nftban-*.timer has its target
service installed (explicit Unit= or
implicit basename.service)
PAYLOAD-INVENTORY-001 nftban-owned paths referenced by
nftban units belong to the staged
payload inventory
FAILED-UNIT-POSTINSTALL-001
no nftban-* unit is in failed state;
fails closed if systemctl enumeration
cannot complete
Generic by design — no panel logic, no DirectAdmin specifics, no
firewall-runtime mutation. Pure validator + host adapter; one
gather call feeds all four assertions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds generic install validation for:
No panel logic. No DirectAdmin logic. No authority classifier. No restore changes. No firewall mutation.
Invariants
SYSTEMD-EXECSTART-001ExecStart/ExecStartPre/ExecStartPostlocal executable path must exist on disk after payload staging.SYSTEMD-TIMER-PAIR-001nftban-*.timermust activate an installed nftban service unit. ImplicitUnit=(basename.service) is inferred when the directive is absent.PAYLOAD-INVENTORY-001/bin/,/usr/bin/, etc.) are exempt.FAILED-UNIT-POSTINSTALL-001systemctlis unavailable or the failed-unit enumeration query errors.STATECOMMITTED-STRICT-001RunAssertionsresults; the existingAllPassedgate atcmd/nftban-installer/phases.go:353,378blocksStateCommittedautomatically — nophases.gochange required.Architecture
internal/installer/validate/systemd_payload.go— pure validator (no I/O), parser, types.internal/installer/validate/systemd_payload_gather.go— host adapter; reads unit dirs viaos.ReadDirand queriessystemctl list-units --state=failedthrough the executor.internal/installer/validate/assertions.go— four new assertions sharing one gather call so the unit dirs andsystemctlare not walked four times.Read-only. No
nft*, noService{Start,Stop,Mask,Unmask,Enable,Disable}, noWriteFileAtomic, noRename, noRemove, noos.Write*.Bug coverage
The dns2 (2026-04-29) regressions that motivated this PR are now caught at install time:
nftban-unified-exporter.serviceExecStart/usr/lib/nftban/exporters/nftban_unified_exporter.shmissing on disk → SYSTEMD-EXECSTART-001 + PAYLOAD-INVENTORY-001 fire.nftban-metrics-exporter.timerorphaned (paired service absent) → SYSTEMD-TIMER-PAIR-001 fires.nftban-core-geoip.service(status=2) /nftban-unified-exporter.service(203/EXEC) /nftban-maintenance.service(activating-loop) → FAILED-UNIT-POSTINSTALL-001 fires.Three independent assertions catch the dns2 exporter bug.
AllPassedreturns false on the first; install drops toStateDegradedinstead of false-passingStateCommitted.Tests
41 tests in
./internal/installer/validate/..., all green on lab4. Coverage:MissingExecStart_FilesystemAbsent(dns2 regression shape, structural name)TimerOrphan_NoServicePair(dns2 regression shape, structural name)Unit=inference/bin/sh -cand/usr/bin/envshell-wrapper variants — embedded nftban paths detected/bin/sh,/usr/bin/sh,/bin/bash,/usr/bin/bash,/bin/env,/usr/bin/env,/bin/systemctl,/usr/bin/systemctl,/usr/bin/journalctl(9 sub-tests)/bin/sh -c '/usr/lib/nftban/.../missing.sh'still fails/bin/sh -c 'echo ok'passesIsNftbanUnitnaming matrix: 19 cases coveringnftban/nftband×service/timer/socket/target/path/mountplus negatives-,+,!, combos) strippedFailedUnitQueryError_FailsClosed— fail-closed whensystemctlunavailable or query errorsLab proof (lab4, RHEL/cPanel, go1.25.8)
Branch base:
7c9b409d(origin/main).TMPDIR=/root/build-tmpwas required on lab4 — both/tmpand/var/tmpare noexec under cPanel's/usr/tmpDSKmount.Test plan
go test ./internal/installer/validate/...green on lab4go vet ./internal/installer/validate/... ./cmd/nftban-installer/...clean on lab4go test ./...green on lab4 (64 packages, 0 fail)🤖 Generated with Claude Code