Commit Graph
4 Commits
Author SHA1 Message Date
ekklesiaandClaude Opus 5 30d2d99230 fix: a hidden Wi-Fi survives the hand-over, and a failed one leaves an owner
The prayer-room laptop's 0.2.5 migration built a NetworkManager
connection, could not bring it up, rolled back, and left the wireless
adapter belonging to nobody. Four separate defects, each of which would
have been enough on its own.

THE HIDDEN FLAG WAS THROWN AWAY. The stanza said wpa-scan-ssid 1 and the
parser read it — into options, where the only accessor ignored it. The
profile came out with 802-11-wireless.hidden: no, and NetworkManager
will not associate with an SSID it has not seen in a scan. A hidden one
is never in a scan. credentials() returned a pair; profile() now returns
the whole network, and add() writes the flag in BOTH directions rather
than only when true, because a default that happens to be right is not
the same as an answer.

IFDOWN RAN AFTER THE STANZA WAS COMMENTED OUT. ifupdown reads
/etc/network/interfaces to find out how to bring an interface down; with
the stanza gone it says "Ignoring unknown interface" and does nothing.
So ifupdown's own wpa_supplicant and DHCP client were still holding the
radio when NetworkManager was asked to associate with it, which is a
fight NetworkManager loses. Down first, while the file still says how;
rewrite the file second.

THE ROLLBACK LEFT THE ADAPTER UNMANAGED. restore() ended with
`device set <dev> managed no`, which is right while ifupdown can bring
the interface up and is a radio nobody owns when ifup has failed too.
That is the state the laptop was found in, and why `nmcli device set
wlp2s0 managed yes` was a manual recovery step. It now escalates: if
ifupdown cannot get the network back either, the interface goes to
NetworkManager rather than to neither, and it says so loudly.

A '#' IN A PASSWORD WAS SILENTLY TRUNCATED. The parser cut every line at
its first '#'. interfaces(5) is explicit that only whole-line comments
exist, so a pre-shared key containing one became a prefix of itself —
and the only symptom is an association failing exactly the way a wrong
password fails.

Two smaller ones went with them. The rollback un-comments its own marked
lines in place rather than copying the backup over the whole file, so a
wired change made between the migration and the rollback is no longer
silently reverted. And nmcli terse output is split on its own escaping
rules under LC_ALL=C, so a network whose name contains a colon cannot
make a successful migration look like a failed one.

--adopt now also refuses a network it cannot recreate before taking the
interface away from ifupdown to find out, and reports an
unmanaged-devices line in NetworkManager's configuration, which survives
the stanza going away.

Verified against a real NetworkManager 1.52.1: the keyfile carries
hidden=true, the SSID keeps its space, and a psk containing '#' is
intact, at 0600 root:root with no connection.permissions. The new test
drives the whole hand-over against stubbed tools and asserts the
ordering, the rollback and the managed state — including that a machine
already migrated, which is the laptop, is not migrated again.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-22 20:20:53 -05:00
ekklesiaandClaude Opus 5 35e3bdcbbd fix: give a Wi-Fi the installer set up to NetworkManager, by hand
The Bible study laptop had working internet and a panel that said there
was no connection. Both were true, and one thing caused both.

Measured on the machine:

    nmcli device status          wlp2s0  wifi  unmanaged
    nmcli networking connectivity  none        while curl fetched fine
    nmcli device wifi list       nothing, with the radio enabled
    /run/network/ifstate         wlp2s0=wlp2s0
    a second wpa_supplicant, -i wlp2s0, with no -c config file
    dhcpcd holding the lease
    /etc/network/interfaces      0600 root:root

Debian's installer had been given the Wi-Fi, so it wrote it into
/etc/network/interfaces and ifupdown brings it up. NetworkManager ships
with plugins=ifupdown and [ifupdown] managed=false, so an interface named
in that file is UNMANAGED — which is why the applet had nothing to report
and why scanning found nothing. An unmanaged device is not a radio
NetworkManager will scan with. rfkill was ruled out separately: the radio
is enabled.

managed=false stays; it is what keeps a tower's static Ethernet where an
administrator put it. Only a wireless interface is moved.

classroom-wifi grows --check, --adopt (with --dry-run) and --rollback.
--adopt builds a NetworkManager SYSTEM connection from the SSID and key
already in that root-only file, so the password never leaves the machine
and never reaches a human or a log; it lands in a 0600 root file with
psk-flags=0, the same shape 0.2.4 established, and the polkit rules
protecting it are untouched.

Deliberately NOT run by the upgrade. It moves a machine's only link
between managers and the machine it could strand is a laptop in a locked
room, so classroom-migrate only looks and prints what it found. An
administrator runs it over SSH while watching.

Two things this learned from testing the rollback rather than from
reading the code, and both are why the rollback is worth testing:

  * ifdown was missing in the lab, subprocess raised FileNotFoundError,
    and the exception escaped AFTER the stanza was commented out. No
    rollback ran. Every hand-over command now goes through a runner that
    tolerates a missing binary, and the whole hand-over is inside a guard
    that restores the file on any failure at all.
  * it now refuses to start when ifdown or ifup are absent, because
    without them the hand-over would be a one-way door.

Success also requires the interface itself to be carrying the connection,
not merely that the machine is online — a machine with a second link
would otherwise report a failed migration as a success.

Verified in the lab against real NetworkManager with a real ifupdown
file: --check and --dry-run leave it byte-identical, a failed --adopt
rolls back byte-identically, and --rollback restores a half-migrated
machine byte-identically and exits 0. The one thing no lab can do is the
hand-over succeeding, which needs the laptop's radio.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-21 21:11:19 -05:00
ekklesiaandClaude Opus 5 bf7b959893 feat: the room may use the church Wi-Fi and may not read its password
These are shared computers in rooms full of people, and in a stock Plasma
desktop "use the network" and "read the network's password" are much
closer together than they look.

Measured on Debian 13 with NetworkManager 1.52 and Plasma 6.3:

  * org.freedesktop.NetworkManager.settings.modify.own ships as "yes" for
    an active session — no prompt, no administrator. It covers any
    connection whose connection.permissions names that user, and a Wi-Fi
    network added from the desktop is created exactly that way. So a
    password a teacher types in is readable by the next person to sit
    down.
  * the applet offers "Show Network's QR Code" on every saved wireless
    connection, and the QR code contains the pre-shared key.
  * that action, and the "Configure…" beside it, are contextual actions in
    ConnectionItem.qml and do NOT consult
    authorizeControlModule("kcm_networkmanagement"). The Kiosk key gates
    the toolbar button only. Hiding buttons was never going to be the
    answer.

Every one of those paths ends at NetworkManager's GetSecrets, so that is
where this is answered. A polkit rule refuses settings.modify.own,
settings.modify.system, both wifi.share actions and the machine-wide
settings, for the classroom user by name.

Deliberately NOT refused: network-control, wifi.scan and the
enable/disable actions. None returns a secret, and they are what
connecting and seeing a network are made of. Automatic reconnection does
not go through polkit at all — NetworkManager does it as root — so nothing
here can stop the room coming back on the network after a reboot, which is
the property that matters most on a Sunday.

An administrator's account and root are untouched, and the override route
is the same one the sleep rule has.

classroom-wifi is the other half, and it is a requirement rather than a
convenience: it provisions the church Wi-Fi as a SYSTEM connection, with
no connection.permissions, psk-flags=0 and a 0600 root:root file. A
connection the machine owns is one nobody in the room can read. Verified
against real NetworkManager in the lab — the classroom account gets
"Permission denied" on the file, "--" from nmcli and "Insufficient
privileges" from GetSecrets, while autoconnect stays on.

There is deliberately no teacher-facing join dialog. Anything that lets
the room add a connection lets the room own it, and an owned connection is
a readable one; docs/networking.md says so rather than leaving it to be
rediscovered.

The rule is plain JavaScript, so the test evaluates it with polkit's
objects stubbed and asserts both halves: which actions come back NO, and
which are left alone. What it cannot test is the last mile — polkit
resolves a subject through logind and a container has no graphical seat
session, so every check there is denied for reasons unrelated to this
rule. That one is a physical check, and it is written down as one.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 21:04:07 -05:00
ekklesia 7f66228b9e Release 0.1.37 deployment prep 2026-09-06 17:30:30 -05:00