Skip to content

Certificates page: make the netboot transport two radios, titled as the force it performs - #1731

Closed
darksidemilk wants to merge 2 commits into
working-1.6from
fix/netboot-proto-radio
Closed

darksidemilk wants to merge 2 commits into
working-1.6from
fix/netboot-proto-radio

Conversation

@darksidemilk

@darksidemilk darksidemilk commented Sep 8, 2026

Copy link
Copy Markdown
Member

Draft. See Not verified locally before merging — phpstan could not be run
here, and this adds assertions to a file the tests baseline counts.

Before

[ON ]  The web certificate chains to a public CA      Turn this on for a Let's Encrypt or …
[   ]  Redirect HTTP to HTTPS                         Off by default on purpose. …
[   ]  Rebuild iPXE with this server's CA embedded    Only needed for HTTPS netboot …
┌────┐
│http│  Netboot fetches boot.php over                 Separate from the HTTP redirect above, …
└────┘

Two problems, both in what the browser receives.

It does not look like a control. A form-select-sm in the table's narrow
first column renders as a bare http chip — a status badge sitting beside three
switches that are obviously interactive. A select also shows one value and hides
the other behind a click, on a row whose entire subject is a choice between two
transports.

The title is a dangling phrase. Netboot fetches boot.php over was written
to be completed by the select. With the control in the cell to its left the
sentence never finishes, and the three rows above it are all complete
statements.

After

( ) http  (o) https   Force netboot over HTTP or HTTPS
                      BOOT_url_proto

Two radios show the domain and the current value at once, which is what the
switches beside them do. This does not fight the prior decision: the comment
that chose a select argued "its domain is http|https, so it is a select rather
than a switch, and a checkbox would have to invent which way is 'on'"
— radios
satisfy that reasoning better than the select did.

Titled Force, because forcing is what writing this key does. The page
cannot set BOOT_url_proto_forced — it is deliberately absent from the helper's
allowlist, and ADR 0036 spends a rejected alternative on why — but it does not
need to: _resolveInstallMode() sets that key to yes whenever an explicit
value is supplied, so recording a transport here is forcing it. The old
title hid that. The description now names the key that records it and says the
derivation stops, rather than mentioning it only for the https case.

The title's label points at the radio that is already checked, so the
setting name stays the click target the switches' names are without a click on
it changing anything. Pointing it at a fixed id would make one click on the
title silently force the transport — the exact misclick that same rejected
alternative refused to expose.

JS

change fires only on the radio being selected, never on the one being
cleared, so it still posts once per click and keeps the one-key-per-call shape.
The revert on error re-checks a sibling rather than restoring a value, and the
group is located by shared name — the attribute that makes them mutually
exclusive in the first place, so it cannot drift from the group the browser
sees.

Testing

tests/certificate-table.test.php gains ten assertions on the rendered row,
not on a call site — the failure mode here is entirely in the markup. A radio
group that lost its shared name still renders as two radios and still posts,
but both can be checked at once and the row stops describing one setting.

baseline 33 checks
now 43 checks, all passing
against the previous markup 9 of the 10 new ones fail
with the shared name removed exactly the mutual-exclusivity assertion fails

The tenth is a negative guard (the writable render disables neither radio) and
passes either way by design.

tests/certificate-management-permission.test.php 19/19 — it inspects the
helper's allowlist, which this does not touch, and is the test that would catch
an attempt to make BOOT_url_proto_forced settable from the page.

Not verified locally

  • phpstan (both passes) was not run — no vendor/ on this machine. Worth
    noting because the tests/ pass baselines occurrence counts, and this adds
    a preg_match_all and two closures to a baselined file; if a count: needs
    bumping, CI is where it will show.
  • The gettext catalog was not regenerated — no xgettext here, and the
    pre-commit hook skipped it. One translatable string changed and one was
    reworded, so messages.pot and the .po files will drift until the
    regenerate job corrects them.
  • Not rendered in a browser. The markup and its assertions are verified; how it actually looks in the row is not.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ps28eGTALgieR6TBUSafVg

darksidemilk and others added 2 commits September 8, 2026 10:14
…erforms

Two problems in one row, both in what the browser receives.

It rendered as a `form-select-sm` in the table's narrow first column, which
came out as a bare `http` chip that does not look interactive -- it reads as a
status badge beside three switches that are obviously controls. A select also
shows one value and hides the other behind a click, on a row whose entire
subject is a choice between two transports. Two radios show the domain AND the
current value at once, which is what the switches next to them do.

And it was titled "Netboot fetches boot.php over", a dangling phrase the select
was there to complete. With the control in the cell to its left the sentence no
longer finishes, and the three rows above it are all complete statements.

Titled "Force netboot over HTTP or HTTPS", because forcing is what writing this
key does and the old title hid it. The page cannot set BOOT_url_proto_forced --
it is deliberately absent from the helper's allowlist, and ADR 0036 spends a
rejected alternative on why -- but it does not need to: _resolveInstallMode()
sets that key to yes whenever an explicit value is supplied, so recording a
transport here IS forcing it. The row's description now names the key that
records it and says the derivation stops, rather than only mentioning it for
the https case.

The title's label points at the radio that is ALREADY checked, so the setting
name stays the click target the switches' names are without a click on it
changing anything. Pointing it at a fixed id would have made one click on the
title silently force the transport, which is the misclick that same rejected
alternative refused to expose.

JS follows the control. `change` fires only on the radio being selected and
never on the one being cleared, so it still posts once per click. The revert on
error re-checks a sibling rather than restoring a value, and the group is found
by shared `name` -- the attribute that makes them mutually exclusive in the
first place, so it cannot drift from the group the browser sees.

tests/certificate-table.test.php pins the rendered row: two radios and no
select, one shared name, exactly one checked and it is the reported transport,
the force wording, the key named, the already-checked `for` target, and both
radios disabled without system.pki. Nine of the ten fail against the previous
markup, and dropping the shared `name` fails exactly the assertion about it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps28eGTALgieR6TBUSafVg
@darksidemilk

Copy link
Copy Markdown
Member Author

Superseded by #1732. Radios were the wrong control here: the question this row asks is not which transport but forced to https, or left to derive — and while building it I found the actual bug, which #1731 would have shipped intact. Both http and https set BOOT_url_proto_forced=yes when the installer reads them, so any control offering only those two is a one-way door: a server deriving https from a public certificate gets pinned by the first interaction and never derives again. #1732 makes it a switch, adds an auto value that clears the force, and shows whether the current transport was derived or chosen.

@darksidemilk
darksidemilk deleted the fix/netboot-proto-radio branch September 8, 2026 16:45
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