From 035c88c31c433928a091b49721367b7b0ff85433 Mon Sep 17 00:00:00 2001 From: "thomas.kopp" Date: Fri, 7 Aug 2026 16:11:45 +0200 Subject: [PATCH] feat: package lists per role, with a check-and-install script The lists are derived rather than typed. beastix has 512 explicit packages - years of experiments among them - so taking that as "what this machine needs" would be meaningless. curate.sh combines four usage signals, each of which alone has a blind spot: shell history sees no GUI application, KDE's activity database sees no chat client, autostart sees only what starts by itself, and flatpak entries never appear in pacman -Qqe at all. That reduces 512 to 62. The split follows the data: what beastix and shron both have becomes the base, the rest of beastix becomes desktop, shron's own becomes server. base-devel is one entry in the server list rather than its 26 members. It stopped being a package group in 2022 - "pacman -Sg base-devel" now fails outright - and is a meta package today. base, grub and linux-lts are dropped entirely; they arrive with the operating system. packages.sh never removes anything. Extras are reported and left alone, for the same reason the BookStack playbook refuses to: on a server, individual packages carry mail and web services. It avoids process substitution because /bin/sh is dash on Debian and a POSIX-mode bash on macOS, and this has to run on both. Verified on beastix: 62 wanted, 0 missing, 450 extra reported and untouched; install is a no-op. The server list is a snapshot from 2026-08-06 and wants re-reading when shron is reachable again - its login session has since expired. Co-Authored-By: Claude Opus 5 --- .scripts/curate.sh | 42 +++++++++++++++++ .scripts/packages.sh | 88 +++++++++++++++++++++++++++++++++++ packages/arch-base.txt | 27 +++++++++++ packages/arch-desktop.txt | 47 +++++++++++++++++++ packages/arch-server.txt | 78 +++++++++++++++++++++++++++++++ packages/brew-cask-mobile.txt | 38 +++++++++++++++ packages/brew-mobile.txt | 38 +++++++++++++++ packages/flatpak-desktop.txt | 6 +++ 8 files changed, 364 insertions(+) create mode 100755 .scripts/curate.sh create mode 100755 .scripts/packages.sh create mode 100644 packages/arch-base.txt create mode 100644 packages/arch-desktop.txt create mode 100644 packages/arch-server.txt create mode 100644 packages/brew-cask-mobile.txt create mode 100644 packages/brew-mobile.txt create mode 100644 packages/flatpak-desktop.txt diff --git a/.scripts/curate.sh b/.scripts/curate.sh new file mode 100755 index 0000000..4136f00 --- /dev/null +++ b/.scripts/curate.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Build a candidate "daily use" package list from two independent signals. +# +# Shell history alone is blind to anything started from the desktop menu, and a +# .desktop file alone says nothing about whether the thing is ever opened. Taken +# together they cover both halves: CLI tools by how often they were actually +# run, GUI applications by the fact that they were explicitly installed and ship +# a menu entry. +# +# Everything here is a candidate, not a decision. The point is to replace +# guessing about 512 packages with a list short enough to read. + +set -u +OUT=/home/templis/.claude/jobs/e44cbeae/tmp +EXPLICIT=$(mktemp) +pacman -Qqe | sort -u > "$EXPLICIT" + +# One pass over the file lists instead of one pacman -Qo per command: -Qo forks +# a lookup every time and takes minutes for 40 commands. +MAP="$OUT/file2pkg.txt" +pacman -Ql $(cat "$EXPLICIT") 2>/dev/null | awk '$2 ~ /\/(bin|sbin)\/[^\/]+$/ {n=split($2,a,"/"); print a[n], $1}' | sort -u > "$MAP" + +# --- Signal 1: commands actually run, mapped to their package ------------- +: > "$OUT/cli_pkgs.txt" +while read -r count cmd; do + [ "${count:-0}" -lt 5 ] && continue + pkg=$(awk -v c="$cmd" '$1 == c {print $2; exit}' "$MAP") + [ -n "$pkg" ] && printf '%s\t%s\t%s\n' "$count" "$pkg" "$cmd" +done < /tmp/claude-1000/cmdfreq.txt | sort -rn -k1 > "$OUT/cli_pkgs.txt" + +# --- Signal 2: explicitly installed packages that ship a menu entry ------- +: > "$OUT/gui_pkgs.txt" +pacman -Qlq $(cat "$EXPLICIT") 2>/dev/null | grep -E '/usr/share/applications/[^/]+\.desktop$' > "$OUT/desktop_files.txt" +while read -r pkg; do + if pacman -Qlq "$pkg" 2>/dev/null | grep -qE '/usr/share/applications/[^/]+\.desktop$'; then + echo "$pkg" + fi +done < "$EXPLICIT" > "$OUT/gui_pkgs.txt" + +echo "CLI-Pakete aus History : $(awk '{print $2}' "$OUT/cli_pkgs.txt" | sort -u | wc -l)" +echo "GUI-Pakete mit Menue : $(wc -l < "$OUT/gui_pkgs.txt")" +rm -f "$EXPLICIT" diff --git a/.scripts/packages.sh b/.scripts/packages.sh new file mode 100755 index 0000000..65bbe89 --- /dev/null +++ b/.scripts/packages.sh @@ -0,0 +1,88 @@ +#!/bin/sh +# Compare the installed packages against the lists for this machine's role. +# +# packages.sh what is missing and what is extra +# packages.sh install install what is missing +# +# It never removes anything. Extra packages are reported and left alone: on a +# server, individual packages carry mail and web services, and a tool that +# tidies up on its own is a tool that eventually takes one of them down. The +# same caution is why the BookStack upgrade playbook refuses to remove +# anything either. +# +# The lists are data, not code - plain names, one per line. That is deliberate: +# they map onto home-manager and environment.systemPackages almost unchanged if +# this ever moves to Nix, whereas an install script would not. +set -eu + +ROLE=$(cat "$HOME/.config/dotconfs/role" 2>/dev/null || echo desktop) +DIR="$(cd "$(dirname "$0")/../packages" 2>/dev/null && pwd)" || { + echo "packages/ not found next to $0" >&2 + exit 1 +} + +# Comments and blank lines are for the reader, not the package manager. +read_list() { + [ -f "$1" ] || return 0 + sed -E 's/#.*//' "$1" | tr -d '\r' | awk 'NF' +} + +case "$(uname -s)" in + Linux) + command -v pacman >/dev/null 2>&1 || { echo "not an Arch machine" >&2; exit 1; } + WANT=$(read_list "$DIR/arch-base.txt"; read_list "$DIR/arch-$ROLE.txt" || true) + HAVE=$(pacman -Qqe) + INSTALL="yay -S --needed --noconfirm" + command -v yay >/dev/null 2>&1 || INSTALL="sudo pacman -S --needed" + ;; + Darwin) + command -v brew >/dev/null 2>&1 || { echo "Homebrew not in PATH - is this a login shell?" >&2; exit 1; } + WANT=$(read_list "$DIR/brew-$ROLE.txt") + HAVE=$(brew leaves) + INSTALL="brew install" + ;; + *) + echo "unsupported platform: $(uname -s)" >&2 + exit 1 + ;; +esac + +# Temporary files rather than process substitution: <(...) is not POSIX, and +# /bin/sh is dash on Debian and a POSIX-mode bash on macOS. This script has to +# run on both. +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT INT TERM +printf '%s\n' "$WANT" | awk 'NF' | sort -u > "$TMP/want" +printf '%s\n' "$HAVE" | awk 'NF' | sort -u > "$TMP/have" +MISSING=$(comm -23 "$TMP/want" "$TMP/have") +EXTRA=$(comm -13 "$TMP/want" "$TMP/have") +WANT=$(cat "$TMP/want") +HAVE=$(cat "$TMP/have") + +count() { printf '%s\n' "$1" | awk 'NF' | wc -l | tr -d ' '; } + +case "${1:-check}" in + check) + echo "role: $ROLE wanted: $(count "$WANT") installed: $(count "$HAVE")" + echo + echo "missing ($(count "$MISSING")):" + printf '%s\n' "$MISSING" | awk 'NF' | sed 's/^/ /' + echo + echo "extra, not touched ($(count "$EXTRA")):" + printf '%s\n' "$EXTRA" | awk 'NF' | sed 's/^/ /' + ;; + install) + if [ -z "$(printf '%s\n' "$MISSING" | awk 'NF')" ]; then + echo "nothing to install" + exit 0 + fi + echo "installing:" + printf '%s\n' "$MISSING" | awk 'NF' | sed 's/^/ /' + # shellcheck disable=SC2086 + $INSTALL $(printf '%s ' $MISSING) + ;; + *) + echo "usage: $0 [check|install]" >&2 + exit 1 + ;; +esac diff --git a/packages/arch-base.txt b/packages/arch-base.txt new file mode 100644 index 0000000..6a543c4 --- /dev/null +++ b/packages/arch-base.txt @@ -0,0 +1,27 @@ +# Packages wanted on every Arch machine, regardless of role. +# +# Derived, not curated by hand: the intersection of what beastix actually uses +# (shell history, KDE's activity database, autostart, flatpak - see +# .scripts/curate.sh) with what shron has installed explicitly. A package on +# both machines has earned its place in the base by being useful with and +# without a desktop. +bind +bpytop +docker +ex-vi-compat +git +htop +lnav +man-db +mc +nano +ncdu +neovim +openssh +redis +tailscale +tmux +vim +wget +yay +zsh diff --git a/packages/arch-desktop.txt b/packages/arch-desktop.txt new file mode 100644 index 0000000..846e1a7 --- /dev/null +++ b/packages/arch-desktop.txt @@ -0,0 +1,47 @@ +# Packages for the desktop role, on top of arch-base.txt. +# +# From the same four usage signals on beastix, minus everything shron also has. +# The original 512 explicit packages are years of experiments; these are the +# ones something actually points at. +android-tools +blueman +brscan4 +claude-code +clicker-git +cwitch +firefox +flameshot +hytale-launcher-bin +inetutils +kdeconnect +kdotool +keepassxc +kitty +libpulse +lsd +mtr-gtk +networkmanager +ntfs-3g +openai-codex +pamixer +path-of-building-community-git +pdftk +pipewire +pipewire-pulse +redshift +seatd +signal-desktop +smartmontools +snapper +steam +sway +tailwindcss-bin +teamspeak +thunderbird +uxplay +whois +wine +wireplumber +yazi +ytdl +zoiper-bin diff --git a/packages/arch-server.txt b/packages/arch-server.txt new file mode 100644 index 0000000..09dd100 --- /dev/null +++ b/packages/arch-server.txt @@ -0,0 +1,78 @@ +# Packages for the server role, on top of arch-base.txt. +# +# Taken from shron's explicit list of 2026-08-06. Re-check it when shron is +# reachable again - the snapshot was read while a login session still existed, +# and that session has since expired. +# +# base-devel appears as a single entry rather than its 26 members. It stopped +# being a package group in 2022 and is a meta package now, so pulling in the +# members individually is both noisier and no longer how it is installed. +# base, grub and linux-lts are left out entirely: they come with the operating +# system and nobody installs them from a dotfiles script. +7zip +acme.sh +base-devel +btop +caddy +certbot +certbot-dns-gandi +certbot-nginx +composer +crowdsec +crowdsec-firewall-bouncer-iptables +docker-compose +dotpac +endlessh-git +fail2ban +ffmpeg +figlet +fzf +gdu +gitmux +kitty-shell-integration +lazydocker +libappimage +libpam-google-authenticator +librsvg +libxcrypt-compat +links +mariadb +mdadm +mtools +mtr +net-tools +nextcloud-client +nginx +nmon +ntfysh-bin +php +php-apcu +php-fpm +php-gd +php-imagick +php-legacy +php-legacy-gd +php-legacy-memcache +php-legacy-memcached +php-legacy-odbc +php-legacy-redis +php-legacy-sodium +php-legacy-sqlite +php-legacy-tidy +php-redis +php-sodium +php-sqlite +php-tidy +powerline +progress +python-pip +python-pipx +rsync +screen +sshfs +teamspeak3-server +unace +unarj +unrar +unzip +zip diff --git a/packages/brew-cask-mobile.txt b/packages/brew-cask-mobile.txt new file mode 100644 index 0000000..7fbf09e --- /dev/null +++ b/packages/brew-cask-mobile.txt @@ -0,0 +1,38 @@ +# Homebrew casks for the mobile role - applications rather than command line +# tools, installed with "brew install --cask". +alfred +appcleaner +claude-code +codex +cyberduck +dbeaver-community +drawio +font-hack-nerd-font +font-inconsolata-nerd-font +font-ubuntu-mono-nerd-font +font-ubuntu-nerd-font +ghostty +gimp +iterm2 +joplin +jordanbaird-ice +karabiner-elements +keepassxc +kitty +libreoffice +mactex +mullvad-vpn +mullvadvpn +neovide +neovide-app +nextcloud +obsidian +openvpn-connect +rectangle +rustdesk +sublime-text +thunderbird@esr +utm +vimr +vlc +wireshark-chmodbpf diff --git a/packages/brew-mobile.txt b/packages/brew-mobile.txt new file mode 100644 index 0000000..2f6bd18 --- /dev/null +++ b/packages/brew-mobile.txt @@ -0,0 +1,38 @@ +# Homebrew formulae for the mobile role (the Mac). +# +# "brew leaves" rather than "brew list": leaves are the things asked for, the +# full list is those plus everything dragged in as a dependency. +aria2 +bat +cabextract +cdrtools +colima +composer +docker +docker-compose +fd +fzf +git-delta +go +hashcat +helix +imapsync +john +lazygit +midnight-commander +nmap +node +perl +putty +pypy +ripgrep +rust +tailscale +telnet +tmux +tree +vim +wget +wimlib +wireguard-tools +wireshark diff --git a/packages/flatpak-desktop.txt b/packages/flatpak-desktop.txt new file mode 100644 index 0000000..594b25b --- /dev/null +++ b/packages/flatpak-desktop.txt @@ -0,0 +1,6 @@ +# Flatpaks on the desktop. +# +# These never appear in "pacman -Qqe", which is why they need their own list - +# a package survey based on pacman alone silently loses them. +com.hypixel.HytaleLauncher +org.gnome.Mines