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
This commit is contained in:
Gabriel Brown
2026-08-20 23:58:36 -04:00
parent 48f7c1e962
commit 215da285f3
10 changed files with 422 additions and 35 deletions
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
# Reading the optional-application catalog.
#
# Sourced by both front doors -- the interview's checklist during ./install, and
# `panama apps` afterwards -- so the two can never disagree about what exists.
# One catalog, parsed once, here.
#
# The format is one file per category under setup/packages/extras/, and a line
# is one of:
#
# gimp a dnf package
# flatpak:org.gimp.GIMP a Flathub id
# flatpak:com.slack.Slack | Slack either, with a name for the menu
# flatpak:com.obsproject.Studio.Plugin.SceneSwitcher
#
# An indented line belongs to the entry above it and is installed with it, which
# is how OBS carries its sixteen plugin extensions as a single thing to tick
# rather than seventeen. Comments and blank lines are ignored.
#
# Without an explicit name, the menu derives one from the last dotted component
# of a Flathub id, or uses the package name as it stands.
# Every category name, one per line.
catalog_categories() {
local dir="$1" file
for file in "$dir"/*; do
[[ -f "$file" ]] && basename "$file"
done
}
# The selectable entries in one category, as `target<TAB>label`. Indented
# continuation lines are folded into the entry above and never listed.
catalog_entries() {
local file="$1" line target label
while IFS= read -r line; do
line="${line%%#*}"
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
[[ "$line" =~ ^[[:space:]] ]] && continue
if [[ "$line" == *"|"* ]]; then
target="${line%%|*}"
label="${line#*|}"
else
target="$line"
label=""
fi
target="${target#"${target%%[![:space:]]*}"}"; target="${target%"${target##*[![:space:]]}"}"
label="${label#"${label%%[![:space:]]*}"}"; label="${label%"${label##*[![:space:]]}"}"
if [[ -z "$label" ]]; then
label="${target#flatpak:}"
[[ "$target" == flatpak:* ]] && label="${label##*.}"
fi
printf '%s\t%s\n' "$target" "$label"
done <"$file"
}
# Every install target for one entry: the entry itself, then the indented lines
# beneath it. Called with the category file and the entry's target.
catalog_targets() {
local file="$1" want="$2" line target seen=0
while IFS= read -r line; do
line="${line%%#*}"
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
if [[ "$line" =~ ^[[:space:]] ]]; then
(( seen == 1 )) || continue
line="${line#"${line%%[![:space:]]*}"}"
printf '%s\n' "${line%%|*}" | sed 's/[[:space:]]*$//'
continue
fi
target="${line%%|*}"
target="${target#"${target%%[![:space:]]*}"}"; target="${target%"${target##*[![:space:]]}"}"
if [[ "$target" == "$want" ]]; then
seen=1
printf '%s\n' "$target"
elif (( seen == 1 )); then
break
fi
done <"$file"
}
# Every install target in a whole category, in file order.
catalog_all_targets() {
local file="$1" line
while IFS= read -r line; do
line="${line%%#*}"
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
line="${line#"${line%%[![:space:]]*}"}"
line="${line%%|*}"
printf '%s\n' "${line%"${line##*[![:space:]]}"}"
done <"$file"
}
# Is this target already on the machine? Used to mark the menu, never to skip
# silently -- a re-run that reinstalls what is present is harmless, but a menu
# that hides it is confusing.
catalog_installed() {
local target="$1"
if [[ "$target" == flatpak:* ]]; then
flatpak info "${target#flatpak:}" >/dev/null 2>&1
else
rpm -q "$target" >/dev/null 2>&1
fi
}
+5 -4
View File
@@ -1,8 +1,9 @@
# Chat and messaging. Every one of these is Flathub only -- none is packaged
# Chat, mail and calls. Every one of these is Flathub only -- none is packaged
# for Fedora, and each publishes its own flatpak.
#
# Thunderbird is deliberately absent: it is already in flatpak-packages as
# org.mozilla.thunderbird_esr, which every machine gets.
flatpak:com.discordapp.Discord
flatpak:com.slack.Slack
flatpak:org.signal.Signal
flatpak:com.discordapp.Discord | Discord
flatpak:com.slack.Slack | Slack
flatpak:org.signal.Signal | Signal
flatpak:us.zoom.Zoom | Zoom
+32 -6
View File
@@ -1,7 +1,33 @@
# Images, video and audio. All from dnf or RPM Fusion.
gimp
# Images, video and audio.
#
# Flatpaks rather than the dnf builds these used to name. That was not a
# preference: OBS's plugins are published as Flathub extensions and attach only
# to the flatpak, so the dnf build cannot have them at all. The rest follow so
# one machine does not end up with GIMP from dnf and its neighbour from Flathub.
flatpak:org.gimp.GIMP | GIMP
flatpak:org.kde.kdenlive | Kdenlive
flatpak:fr.handbrake.ghb | HandBrake
flatpak:com.orama_interactive.Pixelorama | Pixelorama
flatpak:org.gnome.gitlab.YaLTeR.VideoTrimmer | Video Trimmer
flatpak:org.gnome.gThumb | gThumb
# Video editing, screen capture, and transcoding what comes out of them.
kdenlive
obs-studio
HandBrake
# OBS and the plugins that make it usable for capture on Wayland. Ticking this
# installs all of them: they are extensions of the OBS flatpak, useless on their
# own, and choosing them one at a time is a menu nobody wants to read.
flatpak:com.obsproject.Studio | OBS Studio
flatpak:com.obsproject.Studio.Plugin.AdvancedMasks
flatpak:com.obsproject.Studio.Plugin.BackgroundRemoval
flatpak:com.obsproject.Studio.Plugin.CompositeBlur
flatpak:com.obsproject.Studio.Plugin.DownstreamKeyer
flatpak:com.obsproject.Studio.Plugin.GStreamerVaapi
flatpak:com.obsproject.Studio.Plugin.Gstreamer
flatpak:com.obsproject.Studio.Plugin.InputOverlay
flatpak:com.obsproject.Studio.Plugin.MoveTransition
flatpak:com.obsproject.Studio.Plugin.OBSPWVideo
flatpak:com.obsproject.Studio.Plugin.OBSVkCapture
flatpak:com.obsproject.Studio.Plugin.PipeWireAudioCapture
flatpak:com.obsproject.Studio.Plugin.SceneSwitcher
flatpak:com.obsproject.Studio.Plugin.Shaderfilter
flatpak:com.obsproject.Studio.Plugin.SourceClone
flatpak:com.obsproject.Studio.Plugin.SourceRecord
flatpak:com.obsproject.Studio.Plugin.WaylandHotkeys
+13 -3
View File
@@ -1,5 +1,5 @@
# Games and the things that run them. A work laptop declines this whole
# category; a desktop wants all of it.
# category; a desktop wants most of it.
#
# steam is in RPM Fusion nonfree, which install-packages enables before it
# reads this file.
@@ -10,5 +10,15 @@ lutris
mangohud
gamescope
# Proton and Wine build management for Steam. Flathub only -- there is no RPM.
flatpak:com.vysp3r.ProtonPlus
# Proton and Wine build management. Flathub only -- there is no RPM.
flatpak:com.vysp3r.ProtonPlus | ProtonPlus
flatpak:com.usebottles.bottles | Bottles
flatpak:com.mojang.Minecraft | Minecraft
flatpak:com.moonlight_stream.Moonlight | Moonlight
# Flathub still carries yuzu and it still installs, but it is marked
# end-of-life upstream and receives no further work. Kept because it is on the
# machine this catalog was seeded from; said out loud so choosing it is a
# decision rather than a surprise.
flatpak:org.yuzu_emu.yuzu | yuzu (unmaintained)
+5
View File
@@ -0,0 +1,5 @@
# CAD, slicing and game engines -- the things that produce files rather than
# consume them.
flatpak:org.freecad.FreeCAD | FreeCAD
flatpak:com.bambulab.BambuStudio | Bambu Studio
flatpak:org.godotengine.Godot | Godot
+9
View File
@@ -0,0 +1,9 @@
# Documents, notes and media playback.
#
# LibreOffice is the flatpak rather than Fedora's split packages: sunhat
# installed writer, calc and impress separately and got three-quarters of a
# suite, because the one nobody listed was the one eventually needed.
flatpak:org.libreoffice.LibreOffice | LibreOffice
flatpak:md.obsidian.Obsidian | Obsidian
flatpak:com.spotify.Client | Spotify
flatpak:tv.plex.PlexDesktop | Plex
+11
View File
@@ -0,0 +1,11 @@
# Small tools for managing the machine itself.
#
# Flatseal edits flatpak permissions, Gear Lever manages AppImages, and
# Extension Manager is for the GNOME session -- which Panama no longer installs,
# so it is here to be chosen rather than in any core list.
flatpak:com.github.tchx84.Flatseal | Flatseal
flatpak:it.mijorus.gearlever | Gear Lever
flatpak:com.mattjakeman.ExtensionManager | Extension Manager
flatpak:org.localsend.localsend_app | LocalSend
flatpak:org.torproject.torbrowser-launcher | Tor Browser
flatpak:org.getmonero.Monero | Monero
+12 -2
View File
@@ -25,6 +25,11 @@ packages_in() {
# --- Defined Paths ---
PANAMA_PATH="$HOME/.local/share/Panama"
# Reading the extras catalog, shared with `panama apps` so the two front doors
# cannot disagree about what a category contains.
# shellcheck source=../lib/extras-catalog
source "$PANAMA_PATH/setup/lib/extras-catalog"
echo -e "\n--- Installing Repositories ---"
log "Installing RPM Fusion Free and Nonfree Repositories"
sudo dnf install -y https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm > /dev/null
@@ -197,6 +202,11 @@ fi
# package and a `flatpak:` line is a Flathub ID, so one file per category holds
# the whole answer rather than splitting each category across two.
#
# Reading the file is setup/lib/extras-catalog's job, not this function's, because
# `panama apps` offers the same catalog from the other side. Two parsers would
# eventually disagree about what a category contains, and the one that disagreed
# quietly would be this one -- it runs unattended.
#
# Neither install is fatal. A category is a set of applications somebody wanted,
# not a dependency of the desktop, and losing the rest of the run because one of
# them was renamed upstream would be the wrong trade.
@@ -205,8 +215,8 @@ install_extra_category() {
name="$(basename "$file")"
local dnf_packages flatpak_ids
dnf_packages=$(packages_in "$file" | tr ' ' '\n' | grep -v '^flatpak:' | tr "\n" " ")
flatpak_ids=$(packages_in "$file" | tr ' ' '\n' | sed -n 's/^flatpak://p' | tr "\n" " ")
dnf_packages=$(catalog_all_targets "$file" | grep -v '^flatpak:' | tr "\n" " ")
flatpak_ids=$(catalog_all_targets "$file" | sed -n 's/^flatpak://p' | tr "\n" " ")
if [[ -n "${dnf_packages// /}" ]]; then
log "Installing $name: $dnf_packages"