Files
Panama/README.md
T

13 KiB

Panama

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

bash <(curl -fsSL https://git.gbrown.org/gib/Panama/raw/branch/main/boot)

boot installs git if the machine lacks it, clones this repository to ~/.local/share/Panama (or $PANAMA_PATH), and hands off to install. It is deliberately small enough to read first, and the same two steps by hand work identically:

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

Both are safe to run again: an existing clone is fast-forwarded rather than replaced, and install is the upgrade path.

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
link-user Links the personal content in user/ — agent instructions, SSH host aliases — but only on a machine that answered yes. See user/README.md
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

Then panama migrate applies any repairs this machine has not had yet. That is the half of an upgrade installing cannot do: the stages above only ever add, so nothing there can remove a file this repository stopped shipping or repair a symlink that now points nowhere. A fresh machine is marked as already caught up rather than having those repairs run against it, and a machine with work waiting is told at the next login rather than left to find out.

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.

Displays is one of the panels it owns, and the only one where a wrong answer can leave you unable to see well enough to undo it. So every change there is applied as one complete layout, read back from the compositor, and reverted after fifteen seconds unless you keep it — resolution, scale, rotation, position and primary display, and now colour profile, bit depth, SDR brightness and saturation, and mirroring with them. Two of those opt out of part of that, for reasons rather than convenience. A per-display variable refresh rate override is applied but never verified, because hyprctl reports whether adaptive sync is live this instant rather than what was asked for. And a mirrored display's position is not asserted at all: the compositor stacks it on the display it mirrors and ignores the coordinates the rule carried, so holding it to them would make Keep permanently unavailable. Monitor brightness sits outside the transaction entirely — it is the panel's own backlight over DDC, and the buttons on the bezel change it behind our back.

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/containers/ Container definitions systemd runs as units — currently the speech-to-text server behind dictation
config/dot/vicinae/ Raycast-style launcher, themed. Its commands live in config/local/share/vicinae/ — script commands (settings deep links, power menu, reminders, window switcher, kill process, SSH hosts, recent files, color picker), and one compiled extension that adds web search with live suggestions. File search, calculator, clipboard, and emoji are Vicinae's own
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

New machines get a welcome card on first start, teaching the handful of keys that matter and reachable afterwards from the launcher. SUPER + / shows every shortcut the machine has, read from the live keymap so a rebind appears there without anything being kept in sync. Settings carries a manual written for the person using the desktop rather than the person building it, opening with a chapter for people arriving from GNOME, macOS or Windows.

Laptops work: battery, charge limits, idle timings that differ on battery, and a closed lid that suspends when you are leaving and keeps working when you are docked. A desktop sees none of it, because every one of those surfaces hides when the hardware is absent.

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
migrations/     One repair per file, for machines installed before a change
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
  containers/   Quadlets, linked into ~/.config/containers/systemd
  local/        Icons, the cursor theme, and the launcher's commands and
                extensions, 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
  lib/          Shared by more than one stage; the extras catalog reader
  packages/     One package per line; extras/ holds the optional categories
  scripts/      Run in order by ./install
tests/          Contracts. See below
  manual/       The user manual, rendered in Settings
docs/           Settings reference, and the design specs behind the work

Tests

171 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

Projects

A project is the set of windows you open together — which applications, which workspace each was on, and for a terminal, which directory it was sitting in. Arrange the desktop, then run Save Layout as Project from the launcher and name it; Open Project lays it out again.

Workspaces are recorded as positions rather than numbers, and opening a project claims free ones, so it never lands on top of what you are already doing. An application that refuses to open twice — Slack, Thunderbird, the browser — is moved into place rather than launched again. Saved layouts are listed on the Desktop settings page, which is also where they are removed.

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 apps       # choose applications to install, by category
panama app        # applications no repository carries; build one by name

panama apps is the optional-application catalog, opened after the fact. The interview offers the same categories during ./install, whole; this picks a category and then the applications inside it, so a machine can acquire Slack in March without having wanted Discord in January. Both read setup/lib/extras-catalog, so the two cannot describe different catalogues.

A category is one file under setup/packages/extras/. A bare line is a dnf package, a flatpak: line is a Flathub id, | Name gives the menu something readable, and an indented line belongs to the entry above it — which is how OBS carries its sixteen plugin extensions as one thing to tick.

Hooks are the extension point: drop a script at ~/.config/panama/hooks/theme-set and it runs whenever the colour scheme changes, with the scheme and accent as arguments. Same for post-upgrade and post-migrate, and a <name>.d/ directory beside each so several things can react without fighting over one file. A broken hook is reported and stepped over, never fatal. Samples are copied into place on install.

panama migrate applies repairs an installed machine has not had yet. Safe to re-run: nothing is applied twice, and a machine with nothing waiting says so.

panama-sudo is pkexec with a stated reason: panama-sudo --reason "why" -- command shows the reason on Panama's password prompt, clearly labeled as an unverified claim beside polkitd's own action text — meant for agents and scripts, so the person typing the password learns why before they do. Without a reason, a running shell, or qs it behaves exactly like pkexec.

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.