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
337 lines
15 KiB
Markdown
337 lines
15 KiB
Markdown
# Settings beyond GNOME
|
|
|
|
Written 2026-08-19, after the last GNOME panel worth owning was taken.
|
|
|
|
The goal has changed. Up to now the target was parity: make GNOME Settings
|
|
unnecessary. That is done except for Color, which is blocked by the platform
|
|
rather than by effort. What follows is the other direction — the things a
|
|
settings application should own on *this* machine, which neither macOS nor
|
|
Windows can offer because neither assumes you run your own infrastructure.
|
|
|
|
Everything below was checked against this machine before it was written. Where
|
|
something is not possible, it says so and why.
|
|
|
|
---
|
|
|
|
## What is already true
|
|
|
|
| | |
|
|
|---|---|
|
|
| `dnf5`, `dnf`, `flatpak` | present; **2 flatpak updates pending** at time of writing |
|
|
| `firewalld`, `nmcli` | present |
|
|
| `podman` | 22 images, 5.31 GB reclaimable |
|
|
| `gamemoded`, `mangohud` | present; Steam library is 1.2 TB |
|
|
| Nextcloud client | installed and running |
|
|
| `rustdesk` | installed |
|
|
| Self-hosted services | cloud, git, home, agentchat all answering over HTTPS |
|
|
| `wg`, `tailscale` | **absent** — the WireGuard tunnel lives on the router, not here |
|
|
|
|
That last row matters: a VPN page on this machine would have nothing to drive.
|
|
Skipped for that reason, not forgotten.
|
|
|
|
---
|
|
|
|
## Batch 1 — Keeping the machine current
|
|
|
|
The clearest "this OS is unfinished" gap. Both macOS and Windows put this front
|
|
and centre; here you update in a terminal.
|
|
|
|
### 1.1 Software Update
|
|
|
|
- Pending updates by source (dnf and flatpak, counted separately because they
|
|
fail separately), download size, and which are security fixes.
|
|
- Whether a kernel update means a reboot, stated plainly rather than implied.
|
|
- When it last checked, and a switch for automatic updates (`dnf-automatic`).
|
|
- Update history, and what `dnf history undo` can and cannot reverse.
|
|
|
|
**Mechanism.** `dnf5` has machine-readable output; `flatpak remote-ls --updates`
|
|
is already scriptable. Applying updates needs root, so pkexec, prompting through
|
|
the agent that is already running.
|
|
|
|
**Deliberately excluded.** Automatic *unattended* installation of anything but
|
|
security updates. A machine that reboots itself into a new kernel while you are
|
|
mid-sentence is not a feature.
|
|
|
|
### 1.2 Snapshot before updating
|
|
|
|
The reason to do updates first. Snapper is wired up as of yesterday, so an
|
|
update can take a restore point before it touches anything — with the snapshot
|
|
named after the transaction, so the Snapshots page shows *"before 47 package
|
|
updates"* rather than a timestamp.
|
|
|
|
Neither macOS nor Windows does this cleanly. It is the single most distinctive
|
|
thing on this list and it is nearly free now.
|
|
|
|
**Done when** the machine can be brought fully current from Settings, and going
|
|
back is one click on a page that already exists.
|
|
|
|
**Landed 2026-08-19.** All three sources, counted and applied separately.
|
|
Firmware was added to the plan after the fact and belonged there: `fwupd` is
|
|
installed, reports cleanly as JSON, and answers instantly.
|
|
|
|
Two things the shape of the data forced. Checking costs about nine seconds, so
|
|
the page opens on a cached 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. And `dnf5` exits 100 when upgrades exist, which is
|
|
success, not failure.
|
|
|
|
Automatic updates apply to applications only, through a Panama-owned systemd
|
|
user timer running daily with a randomized delay. Packages still ask.
|
|
dnf-automatic is reported as absent rather than offered, because installing
|
|
software is not a settings action.
|
|
|
|
Health gained a check for it, which is where the interesting bug was: it first
|
|
returned status "degraded", and the doctor's vocabulary is ok, warning, error,
|
|
unconfigured. The check was counted as nothing at all while the summary still
|
|
reported healthy -- the same silent no-op this codebase keeps relearning, this
|
|
time caught by writing the contract. A contract now asserts every status a check
|
|
can return is one the doctor counts.
|
|
|
|
---
|
|
|
|
## Batch 2 — Your infrastructure — DROPPED 2026-08-19
|
|
|
|
Recorded rather than deleted, so it does not get proposed again.
|
|
|
|
The machine turned out to run essentially every popular self-hosted service:
|
|
Immich, Beszel, Uptime Kuma, Sentry, Gitea with runners, Jellyfin, Plex, RomM,
|
|
SearXNG, Open WebUI, ollama, ComfyUI, whisper, piper, the *arr stack, Vaultwarden
|
|
and more, across a home server and a VPS. On seeing it, the plan below was
|
|
revised once (read Beszel and Uptime Kuma rather than reimplement their polling)
|
|
and then dropped entirely, on Gabriel's judgement, which was right:
|
|
|
|
* Almost all of it is infrastructure FOR other services rather than anything a
|
|
desktop would consume. whisper on the server exists to feed Bazarr's
|
|
subtitles, not to transcribe speech here.
|
|
* The desktop has a better GPU than either server, so anything worth running
|
|
for the desktop's sake should run on the desktop.
|
|
* The one genuinely desktop-shaped idea -- dictation -- is already solved on
|
|
this machine by Wispr Flow.
|
|
|
|
The lesson is worth keeping: the services existing is not a reason to integrate
|
|
them. A page earns its place by answering a question someone actually asks, and
|
|
"is my media server up" is a question Uptime Kuma already answers better,
|
|
somewhere the desktop is not.
|
|
|
|
**Possible survivors, both local rather than remote.** The Nextcloud desktop
|
|
client runs here and its sync state is a real desktop concern in the way iCloud
|
|
Drive is; RustDesk is installed here and pairs with the RDP controls already
|
|
built. Neither is "reach into my server", which is what was dropped.
|
|
|
|
---
|
|
|
|
## Batch 2, as originally written
|
|
|
|
The part no shipped operating system has, because no shipped operating system
|
|
assumes the user runs the servers.
|
|
|
|
### 2.1 Services
|
|
|
|
A page listing your self-hosted services with reachability, TLS certificate
|
|
expiry, and version where the service exposes one.
|
|
|
|
**Mechanism.** Endpoints stored as a preference (so the list is yours, not
|
|
hardcoded); an HTTP HEAD per endpoint and an `openssl s_client` for expiry.
|
|
|
|
**Deliberately a status view, not management.** Settings will not administer a
|
|
VPS. "Is my stuff up, and does anything expire soon" is the question worth
|
|
answering here; anything more belongs in the service's own admin surface.
|
|
|
|
**Watch for.** Certificate expiry is the row that earns this page. It is also
|
|
the one that must never report a false green — a check that fails should say
|
|
"could not check", never "fine".
|
|
|
|
### 2.2 Nextcloud
|
|
|
|
The iCloud Drive equivalent, and the reason this list exists rather than a
|
|
OneDrive page. Sync state, which folders, quota, conflicts, pause and resume.
|
|
|
|
**Mechanism.** The desktop client is running; its configuration is readable and
|
|
`nextcloudcmd` exists. **Risk to check first:** the client's status surface may
|
|
be too thin to drive a page honestly. If it is, this becomes a smaller card —
|
|
"syncing / paused / N conflicts" and a button to open the client — rather than
|
|
a pretend management panel.
|
|
|
|
### 2.3 RustDesk
|
|
|
|
The peer to the RDP controls already built. ID, whether unattended access is on,
|
|
and — the part that matters to you — whether it is pointed at your own relay or
|
|
the public one.
|
|
|
|
**Mechanism.** `rustdesk --get-id`, plus its config. Credentials follow the same
|
|
rule as everywhere else: never through Panama, never on a command line.
|
|
|
|
---
|
|
|
|
## Batch 3 — Machine control
|
|
|
|
### 3.1 Firewall
|
|
|
|
You expose services, so this is worth owning. Zones, what is open, and which
|
|
zone a connection is in.
|
|
|
|
**Mechanism.** `firewall-cmd`, which is fully scriptable, plus polkit for
|
|
changes.
|
|
|
|
**Deliberately excluded.** Rich rules and direct rules. They are a syntax, not a
|
|
setting, and a settings page that half-supports a syntax is a trap.
|
|
|
|
**This one is security-sensitive**: every change must state what it exposes,
|
|
and closing a port someone is currently connected over should say so first.
|
|
|
|
### 3.2 Containers
|
|
|
|
podman is here and Storage already found 5.31 GB of reclaimable images. Running
|
|
containers, images, disk, logs, start and stop.
|
|
|
|
**Deliberately excluded.** Building images and editing compose files. That is a
|
|
development tool, not a setting.
|
|
|
|
### 3.3 Gaming
|
|
|
|
You have `gamemoded`, `mangohud`, and a terabyte of Steam. Windows has Game
|
|
Mode; macOS added one.
|
|
|
|
Gamemode status and which process triggered it; the MangoHud overlay and preset;
|
|
how it pairs with the power profile Settings already controls; Proton versions;
|
|
where the library lives.
|
|
|
|
**Watch for.** This is the page most likely to become a dumping ground. It
|
|
should own the handful of things that are actually settings and link out for the
|
|
rest.
|
|
|
|
**Landed 2026-08-19.** Live-first, because unlike every other page here this one
|
|
has a genuinely live dimension: card temperature, power draw, whether Game Mode
|
|
actually engaged. It polls only while it is open.
|
|
|
|
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 with "balanced".
|
|
Verified with real gamemode activation, not just by calling the hook.
|
|
|
|
Two honest reports the page makes rather than hides. Game Mode's headline trick
|
|
is switching the CPU governor to performance, and this machine already runs
|
|
performance, so it says the change does nothing here. And Proton builds are
|
|
listed but never chosen: Steam picks the runtime per game, and a control here
|
|
would claim an authority the page does not have.
|
|
|
|
One bug worth recording. The hook first called a notifications IPC function that
|
|
did not exist, and the one that did exist was a TOGGLE -- which is the wrong
|
|
primitive entirely, because toggling at game start would unsilence notifications
|
|
that were already silenced. The shell gained an explicit setter and reader.
|
|
|
|
---
|
|
|
|
## Batch 4 — The developer surface
|
|
|
|
### 4.1 SSH and keys
|
|
|
|
Keys, agent state, known hosts, per-host configuration. Neither macOS nor
|
|
Windows does this well, and this machine has a pile of `ksshaskpass` entries in
|
|
the keyring already.
|
|
|
|
**Rules carried from the keyring work.** A private key is never read, never
|
|
displayed, and never leaves its file. The page manages *which* keys exist and
|
|
what they are for.
|
|
|
|
### 4.2 Settings that follow you
|
|
|
|
`SettingsBackup` already exists. Nextcloud as transport would make Panama
|
|
settings arrive on another machine.
|
|
|
|
**Watch for.** Machine-specific values — monitor layout, device names — must not
|
|
follow. A sync that carries a display arrangement to a laptop with one screen is
|
|
worse than no sync.
|
|
|
|
---
|
|
|
|
## Cross-cutting, and worth more than any single page
|
|
|
|
**Make snapshots ambient.** Now that they exist, anything risky can offer to
|
|
take one first: updates, restoring defaults, firewall changes. This is the
|
|
thread that makes a settings application feel *safe* rather than merely capable,
|
|
and it is cheap everywhere it applies.
|
|
|
|
**Make findings actionable.** Storage reports 86 GB in the trash and 5.31 GB of
|
|
reclaimable images, then leaves you to deal with it. The offer belongs next to
|
|
the finding — carefully, since these are the destructive ones.
|
|
|
|
---
|
|
|
|
## Principles this plan inherits
|
|
|
|
Every one of these was learned the hard way in the work before it.
|
|
|
|
- **No switch that lies.** If the mechanism cannot act — Color's missing
|
|
devices, snapper's overridden rollback, a driver we cannot choose — the page
|
|
says so instead of offering a control that changes nothing.
|
|
- **A write is not done until it is read back.** The failure that made "some
|
|
things in the settings app don't work" true was a refresh that silently no-oped
|
|
after a successful write.
|
|
- **Guards read the object, not a binding over it.** A derived property is stale
|
|
inside the handler that changes it.
|
|
- **A test must be seen to fail.** "Something returned an error" is not evidence
|
|
that your own code refused it; assert the reason.
|
|
- **Check what exists before writing a file.** Two components were overwritten in
|
|
one session by not looking first.
|
|
|
|
---
|
|
|
|
## Order, and why
|
|
|
|
Batch 1 first: it is the biggest gap, it is self-contained, and the snapshot
|
|
pairing makes it distinctive rather than a copy of someone else's panel.
|
|
|
|
Batch 2 second: Services is read-only, which makes it low-risk and high-value,
|
|
and it is the page that makes this feel like *your* operating system.
|
|
|
|
Batch 3 and 4 in either order. Firewall is the most valuable of the remainder
|
|
and also the one that most deserves care.
|
|
|
|
|
|
---
|
|
|
|
## The authentication prompt
|
|
|
|
Not on the original list, and worth recording because of how it was built.
|
|
|
|
hyprpolkitagent's dialog is compiled into its binary -- no config, no
|
|
stylesheet, nothing to theme -- and it was the one window on the desktop that
|
|
looked like it belonged to something else. Panama now provides the agent.
|
|
|
|
**The split is the security design.** A Python process owns the D-Bus side: it
|
|
registers with polkitd, receives the request, and hands the shell the action,
|
|
the message, who may answer, and a one-time cookie. It never sees a password.
|
|
The shell draws the prompt, and on submit spawns the setuid
|
|
`polkit-agent-helper-1` itself and writes the password to that helper's stdin;
|
|
the helper performs the PAM conversation and reports to polkitd directly. So the
|
|
password exists in the shell process and the helper's stdin, and nowhere else --
|
|
never on a command line, never over D-Bus, never through IPC arguments.
|
|
|
|
The request travels as a file created 0600 with O_EXCL in a 0700 runtime
|
|
directory. A cookie is not a password, but it is a capability, and capabilities
|
|
do not belong in a process listing either.
|
|
|
|
**Three things cost real time and are worth writing down.**
|
|
|
|
polkitd calls `BeginAuthentication` back on the same connection that called
|
|
`RegisterAuthenticationAgent`. Exporting the object on the session bus while
|
|
registering from the system bus produced no error at all -- every request just
|
|
failed as "Not authorized" without ever prompting.
|
|
|
|
`XDG_SESSION_ID` is not in the systemd user environment, because a user unit
|
|
runs under `[email protected]`, which belongs to no login session. `GetSessionByPID`
|
|
fails for the same reason. The user object's `Display` property is the answer.
|
|
|
|
PyGObject does not accept the `@` placeholder in variant format strings; a tuple
|
|
has to be assembled from already-built variants.
|
|
|
|
**The fallback is deliberate.** hyprpolkitagent stays installed, and only one
|
|
agent may register per session, so the autostart starts exactly one and the
|
|
comment beside it says how to get the stock prompt back. A session with no
|
|
working agent can still authenticate from a terminal, which is the escape hatch
|
|
that made this safe to attempt at all.
|