Skip to content

PR26.5: source-install payload completeness#532

Merged
itcmsgr merged 2 commits intomainfrom
feat/pr26.5-source-install-payload-completeness
Apr 30, 2026
Merged

PR26.5: source-install payload completeness#532
itcmsgr merged 2 commits intomainfrom
feat/pr26.5-source-install-payload-completeness

Conversation

@itcmsgr
Copy link
Copy Markdown
Owner

@itcmsgr itcmsgr commented Apr 30, 2026

Summary

Closes the dns2 install-evidence finding (2026-04-30) where --mode=install --source reached StateDegraded with three named assertion failures:

Assertion Why it failed Now closed by
systemd_execstart_paths_ok 8 unit ExecStart paths referenced unstaged files new shell categories + retired-unit removal
systemd_payload_inventory_ok 6 nftban-owned paths referenced but not in inventory extended defaultInventoryPaths
panel_survival_ok LoadPanelConfig("directadmin") couldn't find conf.d new panels staging category

PR26.5 makes the source-install path complete: every shipped systemd unit's ExecStart resolves to a staged file, and every panel's canonical conf.d is staged for panel_loader.LoadPanelConfig.

Scope (operator-locked)

  • ✅ source-install payload completeness only
  • ❌ no takeover-preservation work (PR26.6)
  • ❌ no CSF binary handling (PR26.6)
  • ❌ no cPanel/Plesk adapter (PR26.7+)
  • ❌ no shell decommission, parser rewrite, restore changes, firewall mutation, destructive dns2 retry

What landed

internal/installer/payload/payload.go::buildEntries

  • New shell-payload categories:
    • cli/lib/nftban/exporters/*.sh/usr/lib/nftban/exporters/
    • cli/lib/nftban/cron/*.sh/usr/lib/nftban/cron/
    • scripts/*.sh/usr/lib/nftban/scripts/
    • install/helpers/*.sh/usr/lib/nftban/helpers/ (joins existing cli/lib/nftban/helpers/)
  • New panels category — 8 single-file entries (directadmin, cpanel, plesk, cyberpanel, cwp, interworx, vesta, generic) → /etc/nftban/conf.d/panels/<name>/main.conf (policyConfigNoReplace).

install/systemd/ (retired unit files removed)

  • nftban-api.service, nftban-ui.service, nftban-ui-auth.service, nftban-ui-auth.socket — all reference binaries the project no longer builds (retired v1.100.1b.A GOTH PR-D4 stage 1, already removed by packaging on RPM/DEB upgrade). Source-install path now matches.

internal/installer/validate/assertions.go::defaultInventoryPaths

  • Extended with the 5 shell-payload destinations referenced by remaining units, so systemd_payload_inventory_ok doesn't false-flag legitimate staged shell payload as "unknown".

Tests (3 new in payload_test.go)

  • TestStageAll_AllUnitNftbanOwnedExecStartPathsStaged_PR26_5 — walks every install/systemd/*.service, parses ExecStart, asserts every nftban-owned destination is in mock.WrittenFiles. The dns2 reproducer.
  • TestStageAll_AllPanelConfDStaged_PR26_5 — asserts every shipped panel's main.conf reaches /etc/nftban/conf.d/panels/<name>/main.conf.
  • TestStageAll_PR26_5_NewShellCategoriesStaged — regression guard pinning the four destinations that failed on dns2 (exporter, cron, scripts, helpers).

Lab proof (lab2, Ubuntu 24.04, go1.22.2)

Branch base: bfe6eac9 (origin/main, post-PR26.4).

go vet ./internal/installer/payload/... ./internal/installer/validate/... ./cmd/nftban-installer/...
  → clean

go test -v ./internal/installer/payload/...
  → all PR26.5 tests PASS; full file PASS

go test ./...
  → 66 packages PASS, 0 FAIL

Staging output during the integration tests:

payload: category=binaries     wrote=4   skipped=0  failed=0
payload: category=cli-bin      wrote=8   skipped=0  failed=0
payload: category=configs      wrote=29  skipped=0  failed=0
payload: category=data         wrote=5   skipped=0  failed=0
payload: category=docs         wrote=1   skipped=1  failed=0
payload: category=logrotate    wrote=2   skipped=0  failed=0
payload: category=panels       wrote=8   skipped=0  failed=0   ← NEW
payload: category=polkit       wrote=3   skipped=0  failed=0
payload: category=shell        wrote=210 skipped=0  failed=0   ← grew (exporters/cron/scripts/install.helpers)
payload: category=systemd      wrote=50  skipped=0  failed=0   ← shrunk by 4 retired units
payload: category=templates    wrote=0   skipped=1  failed=0
payload: category=version      wrote=1   skipped=0  failed=0

Reproducer

The TestStageAll_AllUnitNftbanOwnedExecStartPathsStaged_PR26_5 test would have caught the dns2 finding. Pre-PR26.5, that test fails with:

ExecStart destination not staged: /usr/lib/nftban/exporters/nftban_unified_exporter.sh  (referenced by: nftban-unified-exporter.service)
ExecStart destination not staged: /usr/lib/nftban/cron/maintenance.sh  (referenced by: nftban-maintenance.service)
ExecStart destination not staged: /usr/lib/nftban/scripts/nftban-soak-check.sh  (referenced by: nftban-soak.service)
ExecStart destination not staged: /usr/lib/nftban/helpers/firewall-init-with-delay.sh  (referenced by: nftban-firewall-init.service)
ExecStart destination not staged: /usr/sbin/nftban-api  (referenced by: nftban-api.service)
ExecStart destination not staged: /usr/libexec/nftban-ui-auth  (referenced by: nftban-ui-auth.service)
ExecStart destination not staged: /usr/sbin/nftban-ui  (referenced by: nftban-ui.service)

Post-PR26.5, all green.

Test plan

  • go vet clean on lab2
  • go test ./internal/installer/payload/... green on lab2 (3 new PR26.5 tests + full file)
  • go test ./... green on lab2 (66 packages, 0 fail)
  • CI green
  • Auditor on-PR review

Followups (NOT in this PR)

  • PR26.6 — preserve non-nftban authority assets during takeover (TAKEOVER-PRESERVES-NON-NFTBAN-AUTHORITY-001) — covers nft safety tables, external firewall binaries, config trees, recovery paths
  • PR26.7 — cPanel adapter via panel_loader (uses the panels staging now in place)
  • PR26.8 — Plesk adapter
  • Hygiene — services.EnablePanel legacy "non-fatal" warning alignment (PANEL-ENABLE-LEGACY-WARNING-001)

🤖 Generated with Claude Code

Closes the dns2 evidence finding (2026-04-30) where the source-install
path reached StateDegraded with three named assertion failures:
  systemd_execstart_paths_ok    (8 missing destinations)
  systemd_payload_inventory_ok  (6 unknown nftban-owned references)
  panel_survival_ok             (LoadPanelConfig directadmin not found)

Per the operator-locked PR26.5 scope (`project_pr26_5_locked.md`):
  - source-install payload completeness ONLY
  - no takeover/CSF binary handling (PR26.6)
  - no cPanel/Plesk adapters (PR26.7+)

internal/installer/payload/payload.go (buildEntries):
  - new shell-payload entries:
      cli/lib/nftban/exporters/*.sh -> /usr/lib/nftban/exporters/
      cli/lib/nftban/cron/*.sh      -> /usr/lib/nftban/cron/
      scripts/*.sh                  -> /usr/lib/nftban/scripts/
      install/helpers/*.sh          -> /usr/lib/nftban/helpers/  (joins
        the existing cli/lib/nftban/helpers/ source — both flatten into
        the same destination)
  - new `panels` category staging the canonical panel conf.d files:
      etc/nftban/conf.d/panels/<name>/main.conf -> /etc/nftban/conf.d/panels/<name>/main.conf
      for all 8 first-class panels (directadmin, cpanel, plesk,
      cyberpanel, cwp, interworx, vesta, generic).
      policyConfigNoReplace: operator edits preserved on upgrade.

install/systemd/ (retired unit files removed):
  - nftban-api.service, nftban-ui.service, nftban-ui-auth.service,
    nftban-ui-auth.socket — all reference Go binaries the project
    no longer builds. Operator-confirmed retired in v1.100.1b.A
    (GOTH PR-D4 stage 1) and already removed by packaging on RPM/DEB
    upgrade. Source-install path now matches that contract: not
    shipped, not in source tree, not staged.

internal/installer/validate/assertions.go (defaultInventoryPaths):
  - extended with shell-payload destinations referenced by remaining
    units, so systemd_payload_inventory_ok no longer false-flags
    legitimate staged shell payload as "unknown".

internal/installer/payload/payload_test.go (3 new tests):
  - TestStageAll_AllUnitNftbanOwnedExecStartPathsStaged_PR26_5:
      walks every install/systemd/*.service, parses every
      ExecStart/Pre/Post path, asserts every nftban-owned
      destination is in mock.WrittenFiles after StageAll.
  - TestStageAll_AllPanelConfDStaged_PR26_5:
      asserts every shipped panel's main.conf is staged at
      /etc/nftban/conf.d/panels/<name>/main.conf.
  - TestStageAll_PR26_5_NewShellCategoriesStaged:
      regression guard pinning the four specific destinations
      that failed on dns2 (exporter, cron, scripts, helpers).

Lab proof (lab2, Ubuntu 24.04, go1.22.2):
  go vet payload/... validate/... cmd/nftban-installer/...   clean
  go test -v ./internal/installer/payload/...                3 new tests PASS, full file PASS
  go test ./...                                              66 packages, 0 FAIL

Hard exclusions preserved: no takeover-side cleanup, no CSF binary
handling, no cPanel/Plesk adapters, no shell decommission, no
parser rewrite, no restore changes, no firewall mutation, no
destructive dns2 retry from this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Auditor flagged: TestStageAll_AllUnitNftbanOwnedExecStartPathsStaged_PR26_5
fails on CI because the runner ships a clean source checkout with no
prebuilt binaries. lab2 happened to have prebuilt binaries from prior
builds, so the test passed there. The test's intent is correct (assert
every shipped unit's nftban-owned ExecStart path is staged), but it
needs the binary source files to exist for the staging step to succeed.

Fix: per auditor option 1, add a `seedStubBuiltBinaries` helper that
populates `bin/<name>` with a stub byte-string in the test mock before
StageAll. Production payload.go is unchanged.

Helper applied to all 3 PR26.5 tests for environment consistency:
  TestStageAll_AllUnitNftbanOwnedExecStartPathsStaged_PR26_5  (the failing one)
  TestStageAll_AllPanelConfDStaged_PR26_5
  TestStageAll_PR26_5_NewShellCategoriesStaged

Lab proof — confirmed reproducer:
  rm -rf /root/nftban-src/bin && go test -count=1 -v -run PR26_5 ./...
  → all 3 PR26.5 tests PASS (was: 1 FAIL pre-fix on empty bin/)

  go test ./...
  → 66 packages PASS, 0 FAIL

Production code: unchanged. The fix is fixture-only — no payload.go
edits, no validate/ edits, no buildEntries change.

Hard exclusions still preserved: no takeover preservation, no CSF
binary handling, no cPanel/Plesk adapters, no shell decommission, no
parser rewrite, no restore changes, no firewall mutation, no
destructive dns2 retry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@itcmsgr
Copy link
Copy Markdown
Owner Author

itcmsgr commented Apr 30, 2026

Auditor option 1 applied — 7062c202

Exact test fixture fix

Added seedStubBuiltBinaries(t, mock, repoRoot) helper in internal/installer/payload/payload_test.go. Populates bin/{nftban-core,nftband,nftban-validate,nftban-installer} with stub byte-strings in the mock so the staging copy-or-skip succeeds on CI runners that lack prebuilt binaries.

Helper called from all 3 PR26.5 tests for environment consistency.

Production code: UNCHANGED

The fix is fixture-only. Diff: internal/installer/payload/payload_test.go +31 lines. No edits to payload.go, assertions.go, buildEntries, defaultInventoryPaths, or any production-side path.

Lab proof — explicit CI-condition reproducer

On lab2 with bin/ deleted (simulates CI runner state):

rm -rf /root/nftban-src/bin
go vet ./internal/installer/payload/...                                 → clean
go test -count=1 -v -run "PR26_5" ./internal/installer/payload/...      → all 3 PASS
go test -count=1 ./...                                                  → 66 packages PASS, 0 FAIL

Pre-fix on the same bin/-deleted state: TestStageAll_AllUnitNftbanOwnedExecStartPathsStaged_PR26_5 failed on /usr/lib/nftban/bin/nftban-core and /usr/lib/nftban/bin/nftband — same failure CI showed. Post-fix: all 3 tests pass.

Test discipline preserved

  • Test 1 (the failing one) still asserts every nftban-owned ExecStart from every shipped unit is in mock.WrittenFiles
  • /usr/lib/nftban/bin/* paths are NOT filtered out — they remain in the assertion set
  • The seed adds source files; the staging contract still has to copy them
  • The dns2 reproducer power is preserved: pre-PR26.5 staging table still fails this test (the missing exporter/cron/scripts/helpers entries were the actual bug, not the binary stubs)

Hard exclusions still preserved

  • No takeover preservation work
  • No CSF binary handling
  • No cPanel/Plesk adapters
  • No shell decommission
  • No parser rewrite
  • No restore changes
  • No firewall mutation
  • No destructive dns2 retry

Status

  • ✅ Fix pushed
  • 🔄 Awaiting CI on 7062c202
  • 🔄 Requesting auditor final GO when CI is green

🤖 Generated with Claude Code

@itcmsgr itcmsgr merged commit 1510e36 into main Apr 30, 2026
54 checks passed
@itcmsgr itcmsgr deleted the feat/pr26.5-source-install-payload-completeness branch April 30, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant