|
@@ -42,6 +42,28 @@ get_sync_list() {
|
|
|
} | grep -v '^\s*$' | sort -u || true
|
|
} | grep -v '^\s*$' | sort -u || true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+# Get combined package list (common + groups + host-specific, deduplicated).
|
|
|
|
|
+# Unlike dotfiles, package lists are ADDITIVE: every applicable list is unioned
|
|
|
|
|
+# rather than overridden, so a host list adds to the common one, never replaces it.
|
|
|
|
|
+get_pkg_list() {
|
|
|
|
|
+ {
|
|
|
|
|
+ cat "$script_dir/pkglist" 2>/dev/null || true
|
|
|
|
|
+ while IFS= read -r group; do
|
|
|
|
|
+ cat "$script_dir/pkglist.@$group" 2>/dev/null || true
|
|
|
|
|
+ done < <(get_groups)
|
|
|
|
|
+ cat "$script_dir/pkglist.$hostname_id" 2>/dev/null || true
|
|
|
|
|
+ } | grep -v '^\s*#' | grep -v '^\s*$' | sort -u || true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Explicitly-installed packages on this host.
|
|
|
|
|
+# -Qqe (explicit) keeps AUR *dependencies* out of the list; they get pulled in
|
|
|
|
|
+# automatically by their parent. The -debug filter drops artifacts makepkg
|
|
|
|
|
+# generates when building locally -- they only exist on the machine that built
|
|
|
|
|
+# them and mostly aren't installable elsewhere.
|
|
|
|
|
+installed_packages() {
|
|
|
|
|
+ yay -Qqe | grep -v -- '-debug$' | sort -u || true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
# Resolve source path: common -> groups (alpha) -> host-specific (last wins)
|
|
# Resolve source path: common -> groups (alpha) -> host-specific (last wins)
|
|
|
resolve_source() {
|
|
resolve_source() {
|
|
|
local p="$1"
|
|
local p="$1"
|
|
@@ -158,6 +180,8 @@ Subcommands:
|
|
|
--group <name>: add to group-specific config
|
|
--group <name>: add to group-specific config
|
|
|
list List currently synced dotfiles
|
|
list List currently synced dotfiles
|
|
|
status Show git status of dotfiles repo
|
|
status Show git status of dotfiles repo
|
|
|
|
|
+ packages [install|diff|save] Sync packages (additive: pkglist + pkglist.@<group>
|
|
|
|
|
+ + pkglist.$hostname_id). Not run by 'sync'.
|
|
|
|
|
|
|
|
Host: $hostname_id
|
|
Host: $hostname_id
|
|
|
Groups: ${groups:-none}
|
|
Groups: ${groups:-none}
|
|
@@ -508,12 +532,84 @@ sub_status(){
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+sub_packages(){
|
|
|
|
|
+ local action=${1:-install}
|
|
|
|
|
+
|
|
|
|
|
+ if [[ "$action" == "-h" || "$action" == "--help" ]]; then
|
|
|
|
|
+ cat << EOF
|
|
|
|
|
+Usage: $ProgName packages [install|diff|save]
|
|
|
|
|
+
|
|
|
|
|
+ install Install tracked packages missing on this host (default)
|
|
|
|
|
+ diff Show drift in both directions, change nothing
|
|
|
|
|
+ save Write this host's explicit packages to pkglist.$hostname_id
|
|
|
|
|
+
|
|
|
|
|
+Lists are additive and resolved as:
|
|
|
|
|
+ pkglist + pkglist.@<group> + pkglist.$hostname_id
|
|
|
|
|
+
|
|
|
|
|
+Not run by '$ProgName sync': installing needs sudo and takes time, whereas
|
|
|
|
|
+sync is unprivileged and fast. Keep them separate.
|
|
|
|
|
+EOF
|
|
|
|
|
+ return 0
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ local tracked installed missing extra
|
|
|
|
|
+ tracked=$(get_pkg_list)
|
|
|
|
|
+
|
|
|
|
|
+ if [[ -z "$tracked" ]]; then
|
|
|
|
|
+ log_error "No package lists found (pkglist, pkglist.@<group>, pkglist.$hostname_id)"
|
|
|
|
|
+ log_error "Seed one with: $ProgName packages save"
|
|
|
|
|
+ return 1
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ installed=$(installed_packages)
|
|
|
|
|
+ missing=$(comm -23 <(echo "$tracked") <(echo "$installed"))
|
|
|
|
|
+ extra=$(comm -13 <(echo "$tracked") <(echo "$installed"))
|
|
|
|
|
+
|
|
|
|
|
+ case $action in
|
|
|
|
|
+ install)
|
|
|
|
|
+ if [[ -z "$missing" ]]; then
|
|
|
|
|
+ log_info "All $(echo "$tracked" | wc -l) tracked packages already installed"
|
|
|
|
|
+ return 0
|
|
|
|
|
+ fi
|
|
|
|
|
+ log_info "Installing $(echo "$missing" | wc -l) missing package(s):"
|
|
|
|
|
+ echo "$missing" | sed 's/^/ /'
|
|
|
|
|
+ # Pass targets as arguments rather than piping to stdin: yay opens
|
|
|
|
|
+ # /dev/tty for its prompts, and feeding it stdin breaks that.
|
|
|
|
|
+ echo "$missing" | xargs -r yay -S --needed
|
|
|
|
|
+ ;;
|
|
|
|
|
+ diff)
|
|
|
|
|
+ if [[ -n "$missing" ]]; then
|
|
|
|
|
+ log_warn "Tracked but NOT installed ($(echo "$missing" | wc -l)):"
|
|
|
|
|
+ echo "$missing" | sed 's/^/ /'
|
|
|
|
|
+ fi
|
|
|
|
|
+ if [[ -n "$extra" ]]; then
|
|
|
|
|
+ log_warn "Installed but NOT tracked ($(echo "$extra" | wc -l)):"
|
|
|
|
|
+ echo "$extra" | sed 's/^/ /'
|
|
|
|
|
+ fi
|
|
|
|
|
+ if [[ -z "$missing" && -z "$extra" ]]; then
|
|
|
|
|
+ log_info "In sync: $(echo "$tracked" | wc -l) packages"
|
|
|
|
|
+ fi
|
|
|
|
|
+ ;;
|
|
|
|
|
+ save)
|
|
|
|
|
+ echo "$installed" > "$script_dir/pkglist.$hostname_id"
|
|
|
|
|
+ log_info "Wrote $(echo "$installed" | wc -l) packages to pkglist.$hostname_id"
|
|
|
|
|
+ log_warn "Move entries shared with other hosts into pkglist so they are not duplicated"
|
|
|
|
|
+ ;;
|
|
|
|
|
+ *)
|
|
|
|
|
+ log_error "Unknown packages action: '$action'"
|
|
|
|
|
+ log_error "Run '$ProgName packages --help' for usage."
|
|
|
|
|
+ return 1
|
|
|
|
|
+ ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
subcommand=${1:-}
|
|
subcommand=${1:-}
|
|
|
case $subcommand in
|
|
case $subcommand in
|
|
|
"" | "-h" | "--help")
|
|
"" | "-h" | "--help")
|
|
|
sub_help
|
|
sub_help
|
|
|
;;
|
|
;;
|
|
|
- sync|list|status)
|
|
|
|
|
|
|
+ sync|list|status|packages)
|
|
|
shift
|
|
shift
|
|
|
"sub_${subcommand}" "$@"
|
|
"sub_${subcommand}" "$@"
|
|
|
;;
|
|
;;
|