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:
+119
@@ -10,6 +10,7 @@
|
||||
# doctor Report what is actually running on this machine
|
||||
# test Run every contract under tests/
|
||||
# upgrade Re-run the installer from anywhere
|
||||
# apps Choose applications to install, by category
|
||||
# app Build and install an application that no repository packages
|
||||
# help Show this help
|
||||
#
|
||||
@@ -72,6 +73,9 @@ ${BOLD}Commands:${RESET}
|
||||
a subset: 'panama test dock' runs the ones matching 'dock'.
|
||||
${GREEN}upgrade${RESET} Re-run ./install from anywhere. Safe: every stage is
|
||||
idempotent and this is the documented upgrade path.
|
||||
${GREEN}apps${RESET} Choose applications to install: pick a category, then tick
|
||||
what you want. The same catalog ./install offers, minus the
|
||||
install.
|
||||
${GREEN}app${RESET} Build and install an application that neither dnf nor
|
||||
Flathub carries. With no name, lists what is available.
|
||||
${GREEN}help${RESET} Show this help (also -h, --help).
|
||||
@@ -86,6 +90,7 @@ ${BOLD}Examples:${RESET}
|
||||
$PROGRAM doctor --summary
|
||||
$PROGRAM test dock
|
||||
$PROGRAM upgrade
|
||||
$PROGRAM apps
|
||||
$PROGRAM app
|
||||
$PROGRAM app claude-desktop
|
||||
EOF
|
||||
@@ -393,6 +398,119 @@ cmd_app() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Command: apps
|
||||
# ----------------------------------------------------------------------------
|
||||
# The optional applications, chosen a few at a time rather than all at once.
|
||||
#
|
||||
# ./install offers the same catalog as whole categories, because during a first
|
||||
# install you want coarse and fast. This is the other end of it: pick a
|
||||
# category, then tick the applications inside, and install just those. Both read
|
||||
# setup/lib/extras-catalog, so neither can drift from the other.
|
||||
#
|
||||
# Source-built applications appear here too, as their own category. They are not
|
||||
# part of ./install for good reason -- a build is slow, wants the network
|
||||
# throughout, and can fail on an upstream that moved -- but "choose, then
|
||||
# install" is exactly the right shape for them, which is what this is.
|
||||
cmd_apps() {
|
||||
if ! command -v gum >/dev/null 2>&1; then
|
||||
err "gum is not installed. It is in setup/packages/initial-packages; run 'panama upgrade'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=../setup/lib/extras-catalog
|
||||
source "$PANAMA_DIR/setup/lib/extras-catalog"
|
||||
|
||||
local extras_dir="$PANAMA_DIR/setup/packages/extras"
|
||||
local apps_dir="$PANAMA_DIR/setup/apps"
|
||||
|
||||
local -a categories=()
|
||||
mapfile -t categories < <(catalog_categories "$extras_dir")
|
||||
[[ -d "$apps_dir" ]] && categories+=("built from source")
|
||||
|
||||
(( ${#categories[@]} > 0 )) || { err "No application categories found."; exit 1; }
|
||||
|
||||
local category
|
||||
category="$(gum choose --header "Which kind of application?" "${categories[@]}")" || return 0
|
||||
[[ -n "$category" ]] || return 0
|
||||
|
||||
if [[ "$category" == "built from source" ]]; then
|
||||
cmd_apps_source "$apps_dir"
|
||||
return
|
||||
fi
|
||||
|
||||
local file="$extras_dir/$category"
|
||||
local -a labels=() targets=()
|
||||
local target label marker
|
||||
while IFS=$'\t' read -r target label; do
|
||||
[[ -n "$target" ]] || continue
|
||||
# Marked, not hidden: reinstalling what is present is harmless, but a menu
|
||||
# that silently omits it leaves you wondering where it went.
|
||||
if catalog_installed "$target"; then marker=" (installed)"; else marker=""; fi
|
||||
targets+=("$target")
|
||||
labels+=("$label$marker")
|
||||
done < <(catalog_entries "$file")
|
||||
|
||||
local chosen
|
||||
chosen="$(gum choose --no-limit --header "$category — space to select, enter to accept" "${labels[@]}")" || return 0
|
||||
[[ -n "$chosen" ]] || { info "Nothing selected."; return 0; }
|
||||
|
||||
# Back from labels to install targets, then out to everything each one carries.
|
||||
local -a install_targets=()
|
||||
local pick i
|
||||
while IFS= read -r pick; do
|
||||
[[ -n "$pick" ]] || continue
|
||||
for i in "${!labels[@]}"; do
|
||||
if [[ "${labels[$i]}" == "$pick" ]]; then
|
||||
mapfile -t -O "${#install_targets[@]}" install_targets < <(catalog_targets "$file" "${targets[$i]}")
|
||||
break
|
||||
fi
|
||||
done
|
||||
done <<< "$chosen"
|
||||
|
||||
local -a dnf_packages=() flatpak_ids=()
|
||||
for target in "${install_targets[@]}"; do
|
||||
if [[ "$target" == flatpak:* ]]; then flatpak_ids+=("${target#flatpak:}"); else dnf_packages+=("$target"); fi
|
||||
done
|
||||
|
||||
header "About to install"
|
||||
(( ${#dnf_packages[@]} > 0 )) && printf ' dnf %s\n' "${dnf_packages[*]}"
|
||||
(( ${#flatpak_ids[@]} > 0 )) && printf ' flatpak %s\n' "${flatpak_ids[*]}"
|
||||
echo
|
||||
confirm "Install these?" || { warn "Nothing installed."; return 0; }
|
||||
|
||||
if (( ${#dnf_packages[@]} > 0 )); then
|
||||
info "Installing ${#dnf_packages[@]} package(s) with dnf"
|
||||
sudo dnf install -y "${dnf_packages[@]}" || warn "Some packages did not install"
|
||||
fi
|
||||
if (( ${#flatpak_ids[@]} > 0 )); then
|
||||
info "Installing ${#flatpak_ids[@]} flatpak(s)"
|
||||
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo >/dev/null
|
||||
sudo flatpak install -y flathub "${flatpak_ids[@]}" || warn "Some flatpaks did not install"
|
||||
fi
|
||||
ok "Done."
|
||||
}
|
||||
|
||||
# The source-built applications, offered by the same two-step flow and handed to
|
||||
# the existing `panama app` so there is one build path, not two.
|
||||
cmd_apps_source() {
|
||||
local apps_dir="$1"
|
||||
local -a names=()
|
||||
mapfile -t names < <(for f in "$apps_dir"/*; do [[ -f "$f" ]] && basename "$f"; done)
|
||||
(( ${#names[@]} > 0 )) || { err "No applications in $apps_dir."; return 1; }
|
||||
|
||||
local chosen
|
||||
chosen="$(gum choose --no-limit --header "Built from source — these take a while" "${names[@]}")" || return 0
|
||||
[[ -n "$chosen" ]] || { info "Nothing selected."; return 0; }
|
||||
|
||||
local name
|
||||
while IFS= read -r name; do
|
||||
[[ -n "$name" ]] || continue
|
||||
header "Building $name"
|
||||
cmd_app "$name" || warn "$name did not build"
|
||||
done <<< "$chosen"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Dispatcher
|
||||
# ----------------------------------------------------------------------------
|
||||
@@ -405,6 +523,7 @@ main() {
|
||||
test) shift; cmd_test "$@" ;;
|
||||
upgrade) shift; cmd_upgrade "$@" ;;
|
||||
app) shift; cmd_app "$@" ;;
|
||||
apps) shift; cmd_apps "$@" ;;
|
||||
help|-h|--help|"") usage ;;
|
||||
--version) printf '%s %s\n' "$PROGRAM" "$VERSION" ;;
|
||||
*)
|
||||
|
||||
Reference in New Issue
Block a user