Commit Graph
11 Commits
Author SHA1 Message Date
Gabriel Brown fd99569666 Add a Firewall page, led by what is actually reachable
Listing zones and services is what firewall-cmd already does. The
question it does not answer needs both halves at once: a port is
reachable only when something is LISTENING on a network address AND the
firewall permits it.

On this machine that crossing is the whole story. The rules look
unremarkable -- one zone, three services, a port range -- and what they
mean is that PostgreSQL and Redis, published by rootless containers on
every interface, are reachable by anyone on the network. Neither half
says that alone, which is exactly how a tidy rules list coexists with an
open database. Nothing was misconfigured: Fedora's default zone met
podman's default publish behaviour.

Ephemeral client sockets are excluded. A browser's outbound UDP port is
indistinguishable from a service in ss, and listing twenty of them
buried the two rows that mattered.

Closing the port range names what it would cut off, by service, before
doing it, and removing ssh says so when someone is connected over it.
Rich rules are shown and never edited: a syntax is not a setting, but
hiding it would misrepresent the configuration.

The contract needed a recorded firewall, and the reason is worth
keeping. The rule this page exists for cannot be tested against this
machine -- its zone permits everything above 1024, so "listening" and
"listening and permitted" give identical answers, and a blocked listener
needs a port below 1024, which needs root. With the crossing deleted,
the contract passed. It now runs against a fixture where two listeners
are blocked, and catches it.

Also here: polkit response files are written 0600 rather than at the
default mask, the agent sweeps requests left by an instance that did not
exit cleanly, and the write sweep waits for its harness to be ready
instead of reporting the startup race as settings that failed.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 19:46:16 -04:00
Gabriel Brown 4cbe3b882a Add a Gaming page, and let the desktop react to games
Live first, because unlike every other page here this one has a live
dimension: card temperature, power draw, whether Game Mode actually
engaged. It polls only while it is open, since a settings page nobody is
looking at has no business waking the CPU.

The part that makes it Panama's page rather than a gamemode config
editor is the hook. gamemode runs a script when a game asks for it and
another when the game exits, so the power profile switches to
performance and notifications go quiet for exactly the duration of a
game -- and afterwards both go back to what they WERE, not to a default.
A Do Not Disturb someone set by hand survives a game; a power profile
someone chose is restored rather than replaced. Verified against real
gamemode activation, not merely by calling the hook.

Two things the page reports rather than hides. Game Mode's headline
trick is switching the CPU governor to performance, and this machine
already runs performance, so it says so instead of implying it helps.
And Proton builds are listed but never chosen: Steam picks the runtime
per game, and a control here would claim an authority this page does not
have.

The hook first called a notifications function that did not exist, and
the one that did was a TOGGLE -- the wrong primitive entirely, since
toggling at game start would unsilence notifications that were already
silent. The shell gained an explicit setter and reader.

search-routing-contract kept its own hand-written list of every page,
which made adding one fail as "not a known page" -- a sixth place to
register a page and a sixth chance to forget. It now derives the mapping
from the shell, which already knows it.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 18:55:10 -04:00
Gabriel Brown 8f0fe23377 Add Software Update, across packages, applications and firmware
Three sources that fail independently, so they are counted and applied
separately: a flatpak mirror being down says nothing about whether a
kernel security fix is waiting. Blending them into one number would hide
exactly the case that matters.

Checking costs about nine seconds, which is too long to spend every time
a page opens, so the page opens on the last result and says when it was
taken. A first visit with nothing cached goes and finds out rather than
showing a confident "up to date" it has no basis for.

Installing packages takes a snapshot first, named after what is about to
happen, so Snapshots shows "before 32 package updates" rather than a
timestamp. Best effort: a machine without snapper still updates, because
an update that refuses to run when a nicety fails would be worse than
one without a restore point.

Automatic updates cover applications only, through a Panama-owned user
timer running daily with a randomized delay. Packages still ask, and
dnf-automatic is reported as absent rather than offered, because
installing software is not a settings action.

Health gained a check, and that is where the bug was: it first returned
status "degraded", which is not in the doctor's vocabulary of ok,
warning, error and unconfigured. It was counted as nothing at all while
the summary still said healthy -- the same silent no-op this codebase
keeps relearning. A contract now asserts every status a check can return
is one the doctor counts, and the doctor's own contract knows about the
new check rather than failing on its arrival.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 17:25:11 -04:00
Gabriel Brown 6997dd535f Add high contrast, and make remote desktop configurable
Two of the three panels still handed to GNOME, having actually checked
each rather than repeating that they were not worth owning.

Universal Access turned out to be mostly ours already: the magnifier,
pointer size, text scale, motion and dimming were all present. High
contrast was the real gap. It reaches GTK4 applications through the
desktop portal, which republishes GNOME's accessibility setting as
org.freedesktop.appearance contrast -- so no high-contrast theme is
involved, and none is installed here. Verified end to end: committing
the preference drove gsettings and the portal reported contrast 1.

Sticky, slow and bounce keys stay absent. There is no Wayland or
Hyprland implementation, and the compositor would store the XKB option
while nothing ever acted on it.

Remote desktop gained port, view-only, and clearing stored credentials.
SETTING credentials opens a terminal running grdctl, which prompts for
the password itself. That is not a hand-off for lack of effort: grdctl
takes the password on a terminal and core-dumps without one, and the
only alternative -- passing it as an argument -- would publish it
through /proc to every process on this machine. Typed into grdctl
directly it never passes through Panama, and a contract now fails if it
ever appears on a command line.

Color stays with GNOME, and not for lack of effort either. colord runs
here with seven profiles and zero devices registered, because the
daemons that register displays do not run under this session, and
Hyprland exposes no ICC, gamma, or color-management option at all. A
Color page could import a profile, attach it to nothing, and change
nothing -- the same failure refused for rollback and printer drivers.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 16:42:20 -04:00
Gabriel Brown 1f40f8e136 Add a snapshot manager, and start covering home
The machine already had snapper running hourly on btrfs, so the tool was
never missing. What was missing is that snapper's only configuration
covered / -- and /home is a separate subvolume with no configuration at
all. Six hundred and forty-three snapshots existed and not one of them
contained a document. Anyone reaching for file history would have found
their system and none of their files.

/home now has a configuration on the same hourly timeline, with
deliberately conservative retention: Steam's 1.2 TB lives on that
subvolume and churns on every game update, so keeping five hourly and
seven daily bounds what those updates can pin.

Per volume, because on this machine "one is covered and the important
one is not" was the news, and a timeline opening on system snapshots
would have buried it. Inside a volume the timeline is the familiar view:
points in time, newest first, each openable as a folder tree to take a
file out of.

Restoring sets the current version aside as .before-restore-N rather
than overwriting it. A restore that destroys the thing you were about to
compare against is how someone loses the work they were trying to save.

Rollback is deliberately absent. snapper's rollback changes the btrfs
default subvolume, and this system's fstab pins subvol= explicitly,
which overrides it -- so a rollback would report success and change
nothing after a reboot. A recovery feature that silently does nothing is
worse than not having one, and making it work means editing fstab and
the bootloader, whose failure cannot be repaired from inside the
desktop.

Per-snapshot size is reported as not measured, because measuring it
needs btrfs quotas that cost performance on every write. Free space is
shown instead, which is the number that decides anything.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 16:11:50 -04:00
Gabriel Brown e9d567aa72 Own printers, driverless only
The fourth panel this desktop handed to GNOME Settings, and the last one
worth owning.

Driverless only, deliberately. Adding a printer that describes its own
capabilities over IPP is supported; choosing a PPD or fetching a vendor
driver is not, and the page says so rather than pretending. That
restraint is the whole design: a wrong driver produces a printer that
accepts jobs, reports success, and prints nothing, which is the worst
failure this page could ship because it looks like it worked. A printer
old enough to need a PPD stays a job for the system printer tool.

Printers and the queue are separate cards because they answer separate
questions. Which printers exist is one; where a document went is the
other, and it is the one that actually brings someone here -- so the
queue is a single list across every printer.

Device URIs are validated by scheme before reaching CUPS, whose backends
run as root. file: and pipe: do not lead to a printer and are refused
here rather than further down.

This machine has no printer, so the page was built against a temporary
CUPS queue that was created, exercised through the service, and removed;
the service was confirmed to observe the removal rather than merely
perform it. Discovery and the driverless add path are verified by their
refusals rather than against hardware.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 15:07:17 -04:00
Gabriel Brown a23b42841a Own user accounts and sharing
Two of the panels this desktop still handed to GNOME Settings.

Users manages the account through accountsservice -- the same daemon
GNOME's panel drives, so a name or picture set here is what the login
screen and lock screen read. Name, picture, account type, password,
automatic login, and adding or removing other accounts. Every change is
authorized by polkit through the agent this session already runs; a
dismissed prompt is a normal outcome and says so.

A new password is read from the helper's stdin, hashed by openssl
reading its own stdin, and handed over D-Bus from inside that process.
It is never an argument: argv is world-readable through /proc, so a
password passed that way is published to every process on the machine.
Removing an account takes two presses and says it destroys their files;
the last administrator cannot be removed or demoted, because a machine
nobody can administer is not a state to offer.

Sharing reports what is actually true, including "the software for this
is not installed" -- the honest answer for Samba here, and the case the
panel it replaces shows as a switch that does nothing. Password sign-in
is reported from sshd's configuration rather than assumed: claiming
"keys only" when the file is silent would state a security property that
cannot be backed up.

The Control Center now draws the account's real picture and name. A
generic glyph sat there while a real avatar was already set, which made
the desktop look like it did not know whose it was.

Also here: the KDE Connect contract no longer requires a phone to be
awake. kdeconnectd drops its device objects for a phone it has not seen
recently while the pairing survives in its config, so demanding one
failed whenever the phone was off.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 13:05:13 -04:00
Gabriel Brown 99433c0e8e Add a Storage page
Nothing showed what was using the drive, and removable media was handled
by a tray helper with no surface in Settings at all.

One scroll rather than tabs: space above, the device below. Every other
settings page is a scrolling card stack, and a tab would not be
deep-linkable from the launcher command or from search.

Three things the page has to get right, each now pinned by a contract,
because each is a way it could quietly lie. / and /home are one btrfs
filesystem sharing one pool of free space, and a page that copies df
shows double the free space that exists. zram is a block device and is
not storage; counting it as a drive overstates this machine by 8 GB.
Unmount and eject refuse anything not on a removable drive, because the
UI is what asks and a UI can be wrong.

The cheap read -- layout, usage, health -- runs when the page opens, at
around 90ms. Measuring what is filling the drive means walking every
file, so it happens on request and says so rather than showing an empty
list that reads as "nothing here".

Partitioning and formatting are deliberately absent. A settings pane is
the wrong place to put erasing a disk two clicks deep; the page opens
GNOME Disks for that.

Adding the page found a fourth hard-coded page list in ShellState. A
page missing from it does not error -- openSettings() falls back to
"home", so the launcher opens the wrong page and logs nothing. A
registry contract now holds the three lists together.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 10:52:10 -04:00
Gabriel Brown a68e4f6dcd Make every settings page reachable from the launcher
Settings had a search index and the launcher had script commands, and
neither knew about the other: finding a setting meant opening Settings
first and searching there. One command per page is now generated from
the same sources the in-app search reads -- the sidebar page list, the
group routing, and the schema labels -- so a setting searchable inside
Settings is searchable from the launcher without a second list to
maintain.

Generating it surfaced a gap in the in-app search as well. Six pages had
no vocabulary at all, because their contents come from the system rather
than our schema, so searching "volume" found nothing in Settings either.
They now carry entries, which improves both surfaces.

The product prefix is dropped from every hand-written command title. The
generated ones are qualified "Settings: <page>" instead: a bare page
label collides with the feature of the same name, and two commands
sharing a title are indistinguishable in a launcher.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 09:47:07 -04:00
Gabriel Brown d855a26bd9 Add quiet System Health entry points 2026-08-18 10:19:03 -04:00
Gabriel Brown b28edcd01f Add Panama commands to the launcher 2026-08-18 07:31:11 -04:00