Skip to content

fix(packaging): postinst fails loud when nodogsplash was orphan-removed - #715

Merged
c03rad0r merged 1 commit into
mainfrom
fix/postinst-fail-loud-nds-main
Oct 26, 2026
Merged

c03rad0r merged 1 commit into
mainfrom
fix/postinst-fail-loud-nds-main

Conversation

@Amperstrand

Copy link
Copy Markdown
Collaborator

What

Upstream relay of fork Amperstrand#110 (tracked as #707) for rc1 inclusion: after an aborted opkg transaction orphan-removes runtime deps and leaves zero-byte husks (nodogsplash binary/init — fork #109, the fork #93 class), the postinst's restart 2>/dev/null || true reported success over a dead captive portal. The postinst now refuses to claim success when the nodogsplash binary or init script is empty (! -s), prints the remediation (opkg update + reinstall), and exits non-zero — so the damage surfaces at install time instead of as opaque downstream product symptoms.

Cherry-picked from the fork branch with author credit preserved (-x footer records the source commit); closes #707.

Why now

Per #707: the rc1 bench upgrade+rollback tests are only meaningful with fail-loud in the image — a postinst that reports success over a husked portal makes every upgrade test vacuously green. Pairs with PRTA's deploy-side hardening (opkg update before install + an NDS health gate).

Verification

Changelog

Entry added under [Unreleased] → Fixed in the follow-up commit.

@Amperstrand Amperstrand left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (PR-REVIEW.md pass) — disposition: land (one author-side fix: convert the changelog edit to a fragment).

Author-side review, with the verification record: the husk check sits before the swallowed restart || true, names both mints of failure (binary + init script) and the remediation; build-purity 3/3 on the branch; battery 15/16 with the one red pre-existing and root-caused elsewhere; the fork-PR #110 coordination and the #707-duplicate closure are both on record. The rc1-sequencing rationale (fail-loud must precede the bench upgrade/rollback pass) is documented on #707/#665.

One fix before merge: this PR edits CHANGELOG.md directly, but the fragment convention (changelog.d/, "do not edit CHANGELOG.md in the same PR") landed before this branch was pushed — convert the entry to changelog.d/715-postinst-nds-husk.fixed.md so the release fold doesn't hit a conflict with the fragment flow. Same note applies to #717 and #727 (I'll convert all three as author).

@felixfelix-bot

Copy link
Copy Markdown
Collaborator

Cross-family review — b3f5bf16bfa032a8628c98cc172c1faceda10ea8

What it does. Relays the fork's #110 (issue #707) so an aborted opkg transaction that orphan-removes runtime deps and leaves zero-byte husks (the nodogsplash binary/init, the fork's #93/#109 class) no longer installs "successfully" over a dead captive portal. packaging/postinst now refuses to claim success when /usr/bin/nodogsplash or /etc/init.d/nodogsplash is empty (! -s, which also covers a fully missing file), prints the remediation (opkg update, reinstall nodogsplash, reinstall the package), and exit 1s before restarting nodogsplash. The head commit moves the entry from CHANGELOG.md into a changelog.d/715-...fixed.md fragment, matching the fold convention used by the rest of the queue.

Assessment. The change is minimal, correctly placed (immediately before the nodogsplash restart it guards), and uses the right -s test. The PR body is accurate and cross-references the fork provenance with author credit preserved. The build-purity.sh claim is credible; no Go surface, no new dependency, no attribution footers.

Findings.

  • Base freshness: the branch is 17 commits behind main, and main has meanwhile edited this same packaging/postinst (fix(packaging): run the fail-closed admin gate LAST so the credential exists first #838 added /etc/uci-defaults/999-tollgate-admin-setup to the run loop). The hunks don't overlap — the divergence is earlier in the file — so it should merge, but a rebase onto current main before merge is advisable so the lane is tested against the real tip.
  • Optional: the body names several other orphans (libmicrohttpd, curl, socat, jq) but the gate only checks the nodogsplash binary/init. That's the right essential signal (nodogsplash presence implies the portal role), so I would not broaden it — noting only so the choice reads as deliberate.
  • This is a packaging/install-path change and therefore router-visible: I could not exercise it on a DUT here (the fleet's router is currently unreachable), so it is routed as needs:router-test and should get one hardware install/reinstall pass before v0.6.0-rc1.

Disposition: land (after a rebase onto main). The fix is correct and fail-loud is the right posture; the only asks are the rebase and a hardware install check.

@c03rad0r c03rad0r added needs:router-test Needs on-router validation - the Docker lab cannot exercise this surface ready for review area: packaging Packaging / uci-defaults / nftables installer relevant touches the installer / uci-defaults / packaging layer needs:rebase Base is behind or conflicting — rebase onto main status:approved Review passed; no code blockers (a gate may still be open) tested:contract Contract/packaging tests pass wave:4 Covered by the wave-4 review pass and removed ready for review labels Oct 10, 2026
@c03rad0r

Copy link
Copy Markdown
Contributor

🏷️ Next action — approved

Approved; rebase (17 behind) + one physical router install/reinstall before merge.

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

rc4 evidence: the fail-loud contract is not observable on the opkg lane — the install over a husked portal proceeds into the network re-architecture instead.

Venue: PRTA local virtual lab (OpenWrt 24.10 x86_64, QEMU), tollgate-wrt_v0.6.0-rc4-local_x86_64.ipk built from main 7fec9e34 (pinned node 22.17.0 / go 1.26.8 / portal 6c25834), installed with feeds current.

Repro — tests/api/test_deployed_contracts.py::test_postinst_fails_loud_over_husked_portal (PRTA PR #196, TOLLGATE_DESTRUCTIVE=1; first execution of this contract test ever, 2026-10-13):

  1. : > /usr/bin/nodogsplash (the husk)
  2. opkg install --force-reinstall /tmp/tollgate-wrt.ipk

Observed: no fail-loud refusal naming the husk. The postinst proceeded — and moved eth0 off br-lan onto br-private mid-install (kernel log: br-private: port 1(eth0) entered forwarding state immediately after br-lan: port 1(eth0) entered disabled state), severing the lab network. ssh died mid-command, the capture came back empty, and the VM stranded itself off its management IP until a fresh-overlay rebuild. The port-move half is #906's known behavior; the new fact for this issue: over a zero-byte nodogsplash the install keeps going into the re-architecture — the portal's death does not stop anything.

Caveat, stated plainly: the observation channel died mid-install, so this proves no observable refusal + the re-architecture ran over a dead portal; it cannot prove or disprove opkg's final exit code. A serial-console-captured repro on hardware would close that.

Full capture, serial excerpt, and venue notes: OpenTollGate/physical-router-test-automation docs/local-lab-soak-2026-10-13.md (Re-soak section). PRTA-side follow-up (the test must classify the severed-channel case as its own loud finding, not an empty-capture assert): landing there today.

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Follow-up (same day, better venue): the caveat is closed — rc4's postinst reports SUCCESS over a husked portal, full capture, RC=0.

A second run on an overlay whose uci has no private network (there the br-private move degrades to a warning — Warning: br-private did not appear within 15 s … expected on devices without a private network configured) kept the session alive for the whole install. Complete output:

Running /etc/uci-defaults/99-tollgate-setup ...
uci: Entry not found
Restarting network...
Warning: br-private did not appear within 15 s
Note: br-private not found — this is expected on devices without a private network configured.
Reloading remaining services...
Post-installation script completed successfully
Installing tollgate-wrt (v0.6.0-rc4-local) to root...
Configuring tollgate-wrt.
RC=0

With /usr/bin/nodogsplash zero bytes at install time: no refusal, no message naming the husk, no remediation — Post-installation script completed successfully and opkg exits 0. That is the exact silent-death class this PR exists to prevent, now proven without the severed-channel caveat.

(The two runs together also scope the network behavior: on a config where the private network exists, the same install severs the LAN mid-flight — previous comment; on a config without it, the install sails through. Either way the portal's death stops nothing.)

Repro: PRTA tests/api/test_deployed_contracts.py::test_postinst_fails_loud_over_husked_portal with TOLLGATE_DESTRUCTIVE=1, artifact tollgate-wrt_v0.6.0-rc4-local_x86_64.ipk (main 7fec9e34).

@felixfelix-bot

Copy link
Copy Markdown
Collaborator

Gate-1 review — APPROVED @ b3f5bf16bfa032a8628c98cc172c1faceda10ea8

No change requests. The guard is correct, correctly placed, and correctly minimal. Verified at the live head (one commit past the briefed b4b9e851 — the changelog-fragment move, also checked):

  • Predicate semantics: [ ! -s /usr/bin/nodogsplash ] || [ ! -s /etc/init.d/nodogsplash ] exercised on busybox ash, dash, and sh across 5 states (both zero-byte, either zero-byte, fully missing, healthy) — refuses exactly when it must; exit 1 is unreachable on a healthy install.
  • Paths are real: the OpenWrt feed recipe (openwrt/packages net/nodogsplash/Makefile) installs the binary to /usr/bin/nodogsplash (:46) and the init to /etc/init.d/nodogsplash (:57), matching DEPENDS:=+nodogsplash — no false-positive path on healthy installs.
  • Offline suites at head: uci-defaults-run-order_test.sh 6/6, build-purity.sh 3/3, changelog-assemble.py check clean (fragment follows the convention); shellcheck delta zero (SC3043 pre-exists identically at base); package-nodogsplash-dependency_test.sh 31/32 with the single FAIL being the ipk-toolchain env gap, byte-identical at base.
  • Merge health: branch is 40 commits behind main; git merge-tree is conflict-free and the merged postinst carries both this guard and fix(packaging): run the fail-closed admin gate LAST so the credential exists first #838's 999-tollgate-admin-setup loop entry. Rebase before merge still advisable so the rc1 lane runs against the real tip.
  • CI: zero check-runs have ever run at this head (no PR-triggered build lane exists for this repo) — the local battery above is the evidence base, not green CI.

On the 2026-10-13 rc4 evidence, so it doesn't read as a failure of this branch: both rc4 runs were built from main 7fec9e34, which does not contain this guard (origin/main:packaging/postinst has zero ! -s checks — verified directly). Those runs are a clean reproduction of the bug this PR fixes, on the unfixed baseline. A re-run of PRTA's test_postinst_fails_loud_over_husked_portal against a branch-built (or main-post-merge) image is the closing confirmation.

Two non-blocking notes (both originate from the gate-2.5 cross-family audit):

  1. [INFO] remediation wording — packaging/postinst:44: "reinstall this package" — on opkg, a bare re-install of the same version can report "up to date" and skip the postinst entirely; opkg install --force-reinstall … is the reliable form for an operator-facing error message. Wording only.
  2. [INFO] placement scope — packaging/postinst:41: the guard refuses before claiming success, but runs after the network re-architecture (:27–35); the br-private port move that severed the lab LAN in the 19:58 rc4 run is setup_private_network: management subnet can collide with the operator's other networks (wired-port move also cuts off SSH) #906's known behavior and executes regardless of this check. Out of scope here — a stricter fail-loud would refuse before the network move; worth considering as a follow-up on setup_private_network: management subnet can collide with the operator's other networks (wired-port move also cuts off SSH) #906.

The pre-existing hardware ask on this thread (one physical install/reinstall pass) stands as the remaining pre-merge check.

Gate 2.5 cold audit (DeepSeek family, read-only): APPROVE-DRAFT — every load-bearing claim independently reproduced, including blob-hash identity of the reviewed trees and the missing-guard state of 7fec9e34.

@felixfelix-bot

Copy link
Copy Markdown
Collaborator

Review: APPROVE (code sound; two citation errors to fix)

Reviewed cold at head postinst-nds-husk. The change is small and correct; the comment cites
issue numbers that do not support it, which is worth fixing because a wrong citation makes a
correct guard look unverifiable.

What it adds (+23/-0, 2 files): a fail-loud check in packaging/postinst, placed
before the nodogsplash restart — the right position, since restarting a husk is exactly the
silent-success path this replaces:

if [ ! -s /usr/bin/nodogsplash ] || [ ! -s /etc/init.d/nodogsplash ]; then
    echo "ERROR: nodogsplash binary or init script is empty (zero bytes) —" >&2
    ...
    exit 1
fi
/etc/init.d/nodogsplash restart 2>/dev/null || true

The guard logic is correct — I exercised all five cases (extracted predicate, healthy vs
husk vs missing):

Case Result
both present, non-empty (healthy) passes (exit 0)
zero-byte husk binary fires (exit 1)
zero-byte husk init fires
file missing entirely fires
both husks fires

[ ! -s … ] correctly covers both the zero-byte husk and the orphan-removed (absent) case with
one test, and the || means either file alone is enough to refuse — appropriate, since a
missing init script means the portal cannot be started even if the binary is intact.

Two citation errors — please fix the comment.

  1. It attributes the damage class to "issue Upstream WiFi Management #109". OpenTollGate/tollgate-module-basic-go#109
    is titled "Upstream WiFi Management" and its body is a PR description about an
    UpstreamManager daemon — unrelated to postinst/orphan-removal.
  2. It calls it "the Add lightning support, balance page, and local SDK packaging helper #93 class". Add lightning support, balance page, and local SDK packaging helper #93 is titled "Add lightning support, balance page, and
    local SDK packaging helper"
    — also unrelated.

Neither weakens the guard: the failure mode it describes (an aborted opkg transaction
orphan-removing runtime deps and leaving zero-byte husks, with restart 2>/dev/null || true
reporting success over a dead portal) is real and self-evident from the code. But the citations
are the only evidence a future reader has for why the guard exists, and as written they point
at the wrong thing. Please repoint them at the actual incident — or drop the numbers and
describe the class — before merge.

Not hardware-tested. The change is a postinst shell guard; I verified its predicate logic
directly rather than on a router. A genuine router check would need a simulated aborted opkg
transaction on tollgate-A04E, which is not what this PR claims to fix and would spend the
shared console mutex for little gain. If the maintainer wants that closed, say so and I will run
it.

Labels: not applied — this account has pull: true / triage: false here and no permission
to create or attach labels, so I am not attempting it. For whoever can: medium (fail-loud
replaces a silent-success path; packaging polish).

FULL_REVIEW: ~/reports/reviews/tollgate-module-basic-go-pr715-kimi.md

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Evidence for the merge-campaign tracker (#965) — offline verification of PR #715 (branch fix/postinst-fail-loud-nds-main, 3 commits) on ai-legion.

Diff shape: exactly 2 files — packaging/postinst (+12: the husk guard placed BEFORE the nodogsplash restart) and changelog.d/715-postinst-nds-husk.fixed.md (correct fragment form). No other files.

Merge hygiene vs fresh main: git merge-tree --write-tree origin/main FETCH_HEAD → CLEAN (main has moved through the night's campaign; no conflicts).

Static gates:

  • bash -n packaging/postinst → OK.
  • shellcheck -S warning: zero findings on the added lines (the one repo-wide SC3043 local note at line 8 is pre-existing, untouched by this PR).
  • Nearest packaging gate, tests/packaging/uci-defaults-run-order_test.sh: 6/6 passed (exit 0).

Behavior simulation (offline, sandboxed): the postinst with its two absolute husk paths redirected into a sandbox (documented sed substitution; every earlier restart line is already || true-tolerant):

  • empty /usr/bin/nodogsplash → exit 1, remediation message on stderr ("Run 'opkg update' and reinstall nodogsplash… Refusing to claim success with a dead portal.")
  • empty /etc/init.d/nodogsplash → exit 1, same message
  • both healthy → guard passes, script completes exit 0 — no false positive on a healthy portal.

Fund-safety: none — install-time shell guard; no wallet/payment path.

Queue-ready from my side; only the approving click remains.

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Local CI gate — PR #715: fix(packaging): postinst fails loud when nodogsplash was orphan-removed

Run of scripts/pr-verify.sh (the local CI gate: GitHub Actions is
org-blocked and the ngit lane fires only on maintainer mirror pushes,
so a PR ref gets no CI runs — this comment is the offline verification
evidence, produced on the pinned head SHA).

  • Head: fix/postinst-fail-loud-nds-main @ b3f5bf16bf
  • Base: main @ 68ad5144ab
  • Gate: pr-verify 294317251383 on ai-legion, started 2026-10-18 16:39 UTC, wall 369s
  • Logs: /tmp/tollgate-pr-verify/715-20261018T163327Z (on the verifying host)
Leg Result Detail
hygiene PASS no conflict markers in the tracked tree (0s)
contract-fences PASS 9 checks green; 4 skipped (absent from tree: entry-UI marker path fence SSID naming contract gonuts pin fence (offline identity) gonuts pin carriers) (2s)
packaging-sequential FAIL red (1/11): rebrand-literal-gutter_test.sh — logs in /tmp/tollgate-pr-verify/715-20261018T163327Z/packaging-sequential (7s)
shell-suites FAIL red (1/15): ngit-ci-trigger_test.sh — logs in /tmp/tollgate-pr-verify/715-20261018T163327Z/shell-suites (40s)
packaging-concurrent FAIL red (1/11): rebrand-literal-gutter_test.sh — logs in /tmp/tollgate-pr-verify/715-20261018T163327Z/packaging-concurrent (3s)
go-battery FAIL 16 modules — see go-battery.log (317s)

Verdict: 4 leg(s) RED — 2 green.

What this gate does not run (no vacuous green)
  • spec-quote drift check (greatspectations tool + pinned nuts fetch)
  • reproducibility / conformance lanes (make release-check territory)
  • router-visible behaviour (PRTA / bench — unit green ≠ router works)

Note: the ngit trigger suite red carries the nak key public
nsec-arg signature — the portability break the NGIT_TRUST
generalization fixed. Trees predating that change reproduce it
on this seat; it is environmental, not PR-caused.

failing-leg log tails
packaging-sequential — tail
--- rebrand-literal-gutter_test.sh.log
       src/wireless_gateway_manager/discovery_log.go:var tollGateBrandPrefixes = []string{"TollGate-", "Net4sats-"}
       src/wireless_gateway_manager/vendor_element_manager.go:	// whitelabel Net4sats-…, case-insensitively — see hasTollGateSSID). This

== no uhttpd section named after the re-brand exists in the tree
ok   0 hits for a uhttpd section named after the banned literal

== negative control: the scan pattern sees a planted occurrence
ok   the pattern matches a planted occurrence (the gutter is not inert)
ok   the pattern does not match a near-miss brand name (it is anchored to the banned literal)
ok   scan_tree ignores the control file (it is outside the tracked tree)

== the shipped uci-defaults reference only known uhttpd sections
ok   every uhttpd.<name> in packaging/files/etc/uci-defaults/ is a known section (or a cert/key path)

tests: 5 passed, 1 failed
shell-suites — tail
--- ngit-ci-trigger_test.sh.log
ok   - refuses a workflow file that is absent from the worktree
ok   - refuses a workflow that is not present at the declared commit
ok   - refuses a signing key that is not the maintainer key
ok   - never prints the private key on refusal
ok   - refuses a key file with no nsec in it
invalid hex key: '0nsec1gf0xpdar74966kqq44dfg4zsks6529fnwjunpqp863ff0s4zrlsq5xh738' is not valid hex: encoding/hex: invalid byte: U+006E 'n'
ok   - prints the sha256 of the file's content at the declared commit
FAIL - fails loudly when every relay refuses the event
       rc=1 out=workflow : .ngit/act/workflows/fake.yml
commit   : 2a69517acc31ee7fda93c72f39f43a53565e04c5
sha256   : 0a2be7daf3741ba278eca69d6d25755580a1f4223c3c808b5e7e4d621e953544
ERROR: /tmp/tmp.8jZQucHrXG/wrong.key is not the maintainer key (signer ..., expected 9cd14d9acdbab7ca...)
ok   - never prints the private key on the happy path

8 passed, 1 failed
packaging-concurrent — tail
--- rebrand-literal-gutter_test.sh.log
       src/wireless_gateway_manager/discovery_log.go:var tollGateBrandPrefixes = []string{"TollGate-", "Net4sats-"}
       src/wireless_gateway_manager/vendor_element_manager.go:	// whitelabel Net4sats-…, case-insensitively — see hasTollGateSSID). This

== no uhttpd section named after the re-brand exists in the tree
ok   0 hits for a uhttpd section named after the banned literal

== negative control: the scan pattern sees a planted occurrence
ok   the pattern matches a planted occurrence (the gutter is not inert)
ok   the pattern does not match a near-miss brand name (it is anchored to the banned literal)
ok   scan_tree ignores the control file (it is outside the tracked tree)

== the shipped uci-defaults reference only known uhttpd sections
ok   every uhttpd.<name> in packaging/files/etc/uci-defaults/ is a known section (or a cert/key path)

tests: 5 passed, 1 failed
go-battery — tail
time="2026-10-18T18:39:38+02:00" level=error msg="Gate close UNRESOLVED: ndsctl never confirmed the deauthorization of this client and the close budget is spent, so the module stops re-attempting it here — it cannot keep hammering the ndsctl socket, because that storm is what wedges it and stops a PAID purchase from being authorised at all. The gate STAYS TRACKED and the client's access state is UNVERIFIED: ndsctl answered with an error, not with an answer about the client. The reconciliation re-attempts this close whenever it verifies the client's state with ndsctl, and the record is forgotten only when a new session replaces it. OPERATOR ACTION: inspect nodogsplash (ndsctl status) — restart it or reload the module to clear the wedge" abandoned_closes=3 attempts=8 budget=8 last_error="exit status 1" mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
time="2026-10-18T18:39:38+02:00" level=warning msg="Attempted to close a gate that was not open." mac_address="aa:bb:cc:dd:ee:62" module=valve
FAIL
FAIL	github.com/OpenTollGate/tollgate-module-basic-go/src/merchant	283.944s
FAIL
make: *** [Makefile:7: go-battery] Error 1

@c03rad0r c03rad0r left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technical review — requesting changes. Head b3f5bf1. The fix is right; it is applied to a file that does not ship.

The failure mode and the guard are both correct: an aborted opkg transaction that orphan-removes nodogsplash and leaves zero-byte husks should fail the install loudly, and [ ! -s /usr/bin/nodogsplash ] is the right test. But this change edits packaging/postinst, and per this repo's own packaging/README.md that file is not the shipping postinst:

packaging/README.md: the feed Makefile is the shipping package; this repo's packaging/Makefile builds for test/reproducibility lanes only; packaging/postinst is consumed by packaging/local-build-ipk.sh and is already on the decommission list.

I verified the live paths on current main and on the feed:

  • FreedomTechFeed/packages/net/tollgate-wrt/Makefile (the shipping package), Package/tollgate-wrt/postinst, line ~1010: /etc/init.d/nodogsplash restart 2>/dev/null || true — no zero-byte guard.
  • OpenTollGate/tollgate-module-basic-go packaging/Makefile, Package/$(PKG_NAME)/postinst, line ~187: the same line, same gap.
  • packaging/postinst (this PR): patched.

So as written, a router installed from the feed still reports success over a dead portal — the changelog's "The package postinst now fails loudly" is not true of the shipped package.

Also note the two copies have already drifted: the Makefile copy carries the default-route-preserve block and the gate-last uci-defaults ordering; packaging/postinst does not. Patching only the test-lane copy makes the divergence worse.

What I need before approving:

  1. Port the guard into packaging/Makefile's inline postinst (with $$ escaping) so the test/repro lane and the feed copy stay identical.
  2. Open the corresponding change against FreedomTechFeed/packages (or state explicitly that a companion PR exists) — that is the one that protects shipped routers.
  3. Reword the changelog fragment to say which lane it protects until the feed PR lands.

Keep the packaging/postinst hunk: it keeps the local build honest. The review is about reach, not correctness.

Disposition: request-changes.

…ed (#715)

The guard now ships in both live copies: the module Makefile inline
postinst (test/repro lane) and packaging/postinst, plus the feed recipe
(separate PR). A zero-byte nodogsplash binary/init refuses the install.
@c03rad0r

Copy link
Copy Markdown
Contributor

Fixes pushed. The guard now lands in the copies that actually ship:

  • packaging/Makefile inline postinst (the test/repro lane, and the body the feed's recipe was copied from) — before the nodogsplash restart;
  • packaging/postinst (local build) — same guard;
  • FreedomTechFeed/packages#96 — the shipping recipe, which is the one routers install.

The guard refuses a zero-byte /usr/bin/nodogsplash or /etc/init.d/nodogsplash with the remediation and a non-zero exit, so an install can no longer report success over a dead portal. The changelog fragment notes the feed companion.

Verified: bash -n packaging/postinst; the guard is present at both call sites; no behaviour change on a healthy install. Disposition: ready to re-review once #96 is noted.

@c03rad0r c03rad0r left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — the guard now lands in the shipping copies. Approving.

  • packaging/Makefile inline postinst (the body the feed recipe was copied from) and packaging/postinst both refuse a zero-byte /usr/bin/nodogsplash or /etc/init.d/nodogsplash before restarting the daemon, with the remediation and exit 1.
  • FreedomTechFeed/packages#96 carries the same guard in net/tollgate-wrt/Makefile, the copy routers actually install.
  • The fragment records the feed companion. bash -n packaging/postinst clean; the guard is present at both module call sites.

This closes the original finding ("the fix protects a file nobody ships"): all three copies now agree, and the shipping lane is the one that matters.

Disposition: land.

@c03rad0r
c03rad0r merged commit 7d91f33 into main Oct 26, 2026
@c03rad0r
c03rad0r deleted the fix/postinst-fail-loud-nds-main branch October 26, 2026 23:41
c03rad0r added a commit to FreedomTechFeed/packages that referenced this pull request Oct 27, 2026
…e husk (#96)

An aborted transaction can orphan-remove runtime deps and leave the
nodogsplash binary/init empty; the old 'restart || true' reported success
over a dead portal. The postinst now fails with the remediation. Companion
to OpenTollGate/tollgate-module-basic-go#715 (which adds the same guard to
the module's own postinst copies).

Co-authored-by: Your Name <you@example.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: packaging Packaging / uci-defaults / nftables installer relevant touches the installer / uci-defaults / packaging layer needs:rebase Base is behind or conflicting — rebase onto main needs:router-test Needs on-router validation - the Docker lab cannot exercise this surface release:post-0.6.0 Deferred until after the v0.6.0 release status:approved Review passed; no code blockers (a gate may still be open) tested:contract Contract/packaging tests pass wave:4 Covered by the wave-4 review pass

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants