Gabriel Brown 215da285f3 Let applications be chosen a few at a time
The catalog held fourteen applications. This machine runs thirty-four flatpaks,
so most of what is actually used had no way to be installed from here at all --
Zoom, Slack, Obsidian, Spotify, LibreOffice, OBS and its sixteen plugins.

So the catalog is seeded from the machine, and `panama apps` opens it: pick a
category, tick what you want, install just those. ./install still offers the
same catalog as whole categories, because during a first install you want coarse
and fast. Both read setup/lib/extras-catalog. Two parsers would eventually
disagree about what a category contains, and the one that disagreed quietly
would be the one that runs unattended.

Two pieces of syntax earn their keep. A `| Name` suffix gives the menu something
readable, since com.obsproject.Studio is not a name anybody wants to pick from a
list. An indented line belongs to the entry above it, which is how OBS carries
its plugins as one thing to tick rather than seventeen -- they are extensions of
the flatpak, useless alone.

That is also why creative moved from dnf to Flathub: the plugins attach only to
the flatpak, so the dnf build cannot have them. The rest of the category
followed rather than leave one machine with GIMP from dnf and its neighbour from
Flathub.

The contract now reads the catalog through the same parser instead of keeping a
third idea of the format, and checks the two things this syntax can break
silently: a label leaking into an install command, and a bundle that installs
the application without its plugins. It caught a typo in the Pixelorama id on
the first run.

It also got slow enough to be worth fixing -- fifty-one names, each its own
network call. One bulk query per manager took it from minutes to four seconds.
That query needs `flatpak remote-ls --all`: without it, end-of-life applications
are hidden and read as missing, which reported yuzu as gone from Flathub when it
installs perfectly well.

Claude-Session: https://claude.ai/code/session_01Q84axqUE5inJhf5Jz9CFy1
2026-08-20 23:58:36 -04:00

Panama

Formerly Sunhat. A personal config for Fedora, with the intention of helping a user set up their Fedora system with one command.

git clone https://git.gbrown.org/gib/Panama.git ~/.local/share/Panama
~/.local/share/Panama/install

install asks its questions first and then runs the stages in setup/scripts/ in order, without stopping again:

Script Does
interview Every prompt, before anything is installed. Answers last one run and are never written to a durable path
install-packages Repos (RPM Fusion, Terra, Hyprland COPR), the package lists in setup/packages/, then whichever optional categories were chosen
link-dotfiles Symlinks config/dot/<name>~/.config/<name>, and seeds the wallpaper, cursor theme and Firefox chrome
change-settings Copies config/copy/ over /, applies gsettings, enables user services
link-vicinae-scripts Publishes the Vicinae script commands
setup-identity git config, gh auth login, an SSH key — whichever were asked for
install-hardware NVIDIA, Secure Boot enrollment, Fedora's extras, firmware — each only if it was asked for. Last, because enrollment and firmware are consumed at the next boot

The run ends with a health summary from panama-doctor, which reports what is actually running rather than what was attempted. It never fails the install: on a fresh machine it legitimately reports things as not yet configured.

Optional applications

Every machine gets the lists in setup/packages/. The interview also offers the categories in setup/packages/extras/ as a checklist, so a work laptop need not acquire emulators and a desktop need not skip Steam. Nothing is preselected.

A category is one file. A bare line is a dnf package and a flatpak: line is a Flathub ID, because the applications in a category do not all come from one place. Adding a category is adding a file — the menu is read from the directory, not written down anywhere.

Existing configs are moved to config/old/ rather than overwritten.

The desktop

Hyprland, with a shell written from scratch. It began as a replacement for a GNOME session — Forge for tiling, Dash-to-Dock, Openbar, Vitals — and was built to reproduce it closely enough that muscle memory transferred: same keybinds, same panel contents, same dock, same Tokyo Night Moon palette.

That is history now rather than a second option. Panama installs and configures one desktop, and the GNOME session it grew out of is neither installed nor configured here. What each piece replaced is recorded in config/dot/hypr/DESKTOP-PARITY.md and in the comments of the components themselves, because knowing what a thing was modelled on explains why it behaves the way it does.

GNOME is not gone from the machine: gnome-control-center is a declared dependency, and Panama's own Settings hands off to it for the panels it deliberately does not own — Online Accounts, Color, Sound, Network, Keyboard, Privacy, Wellbeing, Accessibility, and System for users, date and time, region and remote desktop. The allow-list in services/SystemSettings.qml is what decides; anything not on it is a panel Panama owns itself.

Piece What it is
config/dot/hypr/ Compositor config. Lua, not hyprlang — see its README
config/dot/quickshell/ The shell: bar, dock, Continuum overview, Settings, Screen Intelligence, focus sessions, quick settings, notifications, screenshot UI
config/dot/vicinae/ Raycast-style launcher, themed
config/dot/uwsm/ Session environment (see the uwsm caveat in the hypr README)
config/dot/wofi/ Fallback launcher, in case the shell fails to start
config/dot/xdg-desktop-portal/ Portal backend routing

Start here: config/dot/hypr/README.md — it covers the Lua migration, the uwsm environment gotcha, the HDR decision, the full keymap, and troubleshooting.

Log in as "Hyprland (uwsm-managed)", not plain "Hyprland".

Layout

bin/            Small user-facing commands on PATH; `panama` is the entry point
config/
  bash/         .bashrc, aliases, env (env is gitignored)
  copy/         Files copied verbatim over / (needs sudo)
  dot/          Symlinked into ~/.config
  firefox/      Vendored Firefox chrome, linked into the browser profile
  local/        Icons and the cursor theme, linked into ~/.local/share
  old/          Backups of whatever was replaced (gitignored)
  wallpapers/   Copied into ~/Pictures/Wallpapers when absent
setup/
  apps/         Applications built from source, one file each
  packages/     One package per line; extras/ holds the optional categories
  scripts/      Run in order by ./install
tests/          Contracts. See below
docs/           Settings reference, and the design specs behind the work

Tests

121 of them, under tests/. Run the lot, or a subset by pattern:

panama test           # everything
panama test dock      # just the ones matching "dock"
tests/setup/interview-contract   # or one directly; they are plain executables

They are called contracts rather than unit tests because that is what they are: each one pins a decision that was expensive to get right and is cheap to undo by accident. Most read or measure the real thing — launching a shell to measure a surface's geometry, standing stub commands on PATH to see what a stage would have installed, running a script against a throwaway HOME — rather than asserting things about source text, because the bugs worth catching here have all been ones that source text looked fine for.

tests/setup/       The installer: the interview, package lists, hardware, extras
tests/quickshell/  The shell and its settings pages
tests/hypr/        The compositor config

The panama command

panama update     # review, commit and sync this repo
panama edit       # open it in Neovim
panama doctor     # what is actually running, not what was installed
panama test       # every contract, or a subset by pattern
panama upgrade    # re-run ./install from anywhere
panama app        # applications no repository carries; build one by name

panama app is deliberately not part of ./install. Everything else Panama installs comes from dnf or Flathub; these are built from source because no packaged form exists, and a source build is slow, wants the network throughout, and depends on an upstream that moves. That is the failure the interview exists to prevent, so asking for one is something you do on purpose — and it is also how you rebuild when a new version ships. Nothing is pinned: each build takes the current upstream and reports a failure rather than working around it.

Adding one is adding a file to setup/apps/, and the file has to say why the exception exists.

None of the scripts in this repository carry a .sh extension. A shebang and the executable bit already select the interpreter, and the extension only becomes something to keep in sync — which it did not stay.

S
Description
No description provided
Readme
6.1 MiB
Languages
QML 44.2%
Shell 38%
Python 11.2%
CSS 3.5%
Lua 2.1%
Other 0.9%