Añade pkgsync: sincronización de paquetes entre máquinas (mismo patrón que chezmoi)
Nuevo pkgsync/pkgsync.sh + servicios systemd --user (push al cerrar sesión, pull+diff+notifica al iniciar sesión, sin aplicar cambios solo) usando metapac como motor de diff/aplicación sobre los arrays de packages-common.sh. Lista de paquetes críticos protegidos de borrado automático, fichero de overrides local para divergencias entre máquinas, y comandos de introspección (diff/log/incoming/audit). De paso, grub/efibootmgr y los drivers gráficos pasan de estar hardcodeados en cada <de>/setup.sh a vivir en PACMAN_COMMON, para que pkgsync pueda declararlos y protegerlos.
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
#!/usr/bin/env bash
|
||||
# pkgsync: sincroniza qué paquetes (pacman + AUR + flatpak) hay instalados
|
||||
# entre las dos máquinas, usando shared/packages-common.sh (en un clon propio
|
||||
# del repo arch-setup) como fuente de verdad y metapac como motor de
|
||||
# diff/aplicación. Mismo patrón que chezmoi-boot-sync.sh/chezmoi-shutdown-sync.sh
|
||||
# (repo arch-configs) pero para paquetes en vez de dotfiles.
|
||||
#
|
||||
# pkgsync.sh bootstrap instala metapac + config, primera vez o retrofit
|
||||
# pkgsync.sh push captura inventario real -> arrays -> commit+push (pkgsync-push.service)
|
||||
# pkgsync.sh pull git pull + diff + notifica, NO instala/desinstala (pkgsync-pull.service)
|
||||
# pkgsync.sh diff como pull pero solo imprime, sin tocar git ni notificar
|
||||
# pkgsync.sh apply metapac sync + metapac clean, ambos con confirmación interactiva
|
||||
# pkgsync.sh log [--patch]
|
||||
# pkgsync.sh incoming
|
||||
# pkgsync.sh audit
|
||||
set -uo pipefail
|
||||
|
||||
REPO_URL="https://gitea.josevi.ovh/josevi/arch-setup.git"
|
||||
REPO_DIR="$HOME/.local/share/arch-setup"
|
||||
LOG="$HOME/.local/share/pkgsync.log"
|
||||
|
||||
METAPAC_CONFIG_DIR="$HOME/.config/metapac"
|
||||
METAPAC_GROUP="$METAPAC_CONFIG_DIR/groups/arch-setup.toml"
|
||||
OVERRIDES_FILE="$METAPAC_CONFIG_DIR/groups/overrides.local.toml"
|
||||
|
||||
# flatpak sin --user/--system instala en "system" con la config actual de
|
||||
# install_flatpaks() en packages-common.sh. Si tu máquina usa --user, cambia
|
||||
# esto (o verifica con: flatpak list --columns=installation,application).
|
||||
FLATPAK_INSTALLATION="system"
|
||||
|
||||
# Paquetes que NUNCA se borran automáticamente, pase lo que pase en el array
|
||||
# en un momento dado -- red de seguridad independiente, para que sobreviva
|
||||
# aunque el array se corrompa por error humano. Confirmada con el usuario
|
||||
# 2026-08-18 (kernel/arranque, gráficos, red de rollback btrfs, y todo lo que
|
||||
# sea soporte de hardware: CPU/energía, audio, bluetooth, red física, USB).
|
||||
CRITICAL_PACKAGES=(
|
||||
linux linux-lts linux-lts-headers linux-firmware grub efibootmgr amd-ucode
|
||||
mesa vulkan-icd-loader vulkan-radeon vulkan-intel intel-media-driver
|
||||
btrfs-progs snapper snap-pac grub-btrfs zram-generator
|
||||
cpupower power-profiles-daemon
|
||||
pipewire pipewire-alsa pipewire-jack pipewire-pulse wireplumber libpulse
|
||||
gst-plugin-pipewire gst-plugins-good gst-plugins-bad gst-libav sof-firmware
|
||||
bluez bluez-utils
|
||||
networkmanager wpa_supplicant
|
||||
smartmontools usbutils gvfs-mtp
|
||||
)
|
||||
|
||||
# --- inventario real ---------------------------------------------------------
|
||||
installed_pacman_native() { pacman -Qqen 2>/dev/null; } # explícitos de repos oficiales
|
||||
installed_pacman_foreign() { pacman -Qqem 2>/dev/null; } # explícitos AUR/locales
|
||||
installed_flatpak() {
|
||||
command -v flatpak &>/dev/null || return 0
|
||||
flatpak list --app --columns=application 2>/dev/null
|
||||
}
|
||||
|
||||
# --- arrays declarados (leídos del clon en $REPO_DIR) -----------------------
|
||||
# Sourcea packages-common.sh en un subshell: solo lee los arrays, nunca llama
|
||||
# a los install_*.
|
||||
declared_array() {
|
||||
local name="$1"
|
||||
( source "$REPO_DIR/shared/packages-common.sh" 2>/dev/null
|
||||
case "$name" in
|
||||
PACMAN_COMMON) printf '%s\n' "${PACMAN_COMMON[@]}" ;;
|
||||
AUR_COMMON) printf '%s\n' "${AUR_COMMON[@]}" ;;
|
||||
FLATPAK_COMMON) printf '%s\n' "${FLATPAK_COMMON[@]}" ;;
|
||||
esac )
|
||||
}
|
||||
|
||||
# Paquetes en el override local (parser mínimo: basta para sacar los
|
||||
# packages = [...] de [arch] y [flatpak], ignorando líneas comentadas).
|
||||
overrides_packages() {
|
||||
[[ -f "$OVERRIDES_FILE" ]] || return 0
|
||||
awk '
|
||||
/^[[:space:]]*#/ { next }
|
||||
/packages[[:space:]]*=[[:space:]]*\[/ { inarr=1 }
|
||||
inarr {
|
||||
while (match($0, /"[^"]+"/)) {
|
||||
print substr($0, RSTART+1, RLENGTH-2)
|
||||
$0 = substr($0, RSTART+RLENGTH)
|
||||
}
|
||||
}
|
||||
/\]/ { inarr=0 }
|
||||
' "$OVERRIDES_FILE"
|
||||
}
|
||||
|
||||
# --- parcheo del array en packages-common.sh sin tocar comentarios ----------
|
||||
# Quita de $array_name los tokens listados en $remove_list (uno por línea) y
|
||||
# añade los de $add_list en un bloque marcador al final, dejando el resto de
|
||||
# líneas (incluidos los comentarios de agrupación) intactas.
|
||||
patch_array_file() {
|
||||
local file="$1" array_name="$2" remove_list="$3" add_list="$4"
|
||||
local marker=" # --- pkgsync: nuevos detectados (revisa y reorganiza a mano) ---"
|
||||
|
||||
local -A to_remove=()
|
||||
local pkg
|
||||
while IFS= read -r pkg; do
|
||||
[[ -n "$pkg" ]] && to_remove["$pkg"]=1
|
||||
done <<<"$remove_list"
|
||||
|
||||
local -a lines
|
||||
mapfile -t lines <"$file"
|
||||
|
||||
local start_idx=-1 end_idx=-1 i
|
||||
for i in "${!lines[@]}"; do
|
||||
if [[ "${lines[$i]}" == "${array_name}=(" ]]; then start_idx=$i; break; fi
|
||||
done
|
||||
if (( start_idx < 0 )); then
|
||||
echo "!! no encuentro el array $array_name en $file" >&2
|
||||
return 1
|
||||
fi
|
||||
for ((i = start_idx + 1; i < ${#lines[@]}; i++)); do
|
||||
if [[ "${lines[$i]}" == ")" ]]; then end_idx=$i; break; fi
|
||||
done
|
||||
if (( end_idx < 0 )); then
|
||||
echo "!! no encuentro el cierre de $array_name en $file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Ojo: las líneas de paquetes que quedaron bajo el marcador de una pasada
|
||||
# anterior NO se descartan aquí -- pasan por el mismo filtro de to_remove
|
||||
# que cualquier otra línea, así que lo que ya se añadió sigue en el array
|
||||
# aunque esta pasada no tenga nada nuevo que añadir (add_list vacío). Solo
|
||||
# se quita la línea de comentario del marcador en sí, para no acumular
|
||||
# comentarios duplicados; si hay contenido nuevo se vuelve a escribir abajo.
|
||||
local -a new_body=()
|
||||
for ((i = start_idx + 1; i < end_idx; i++)); do
|
||||
local line="${lines[$i]}"
|
||||
if [[ "$line" == "$marker" ]]; then continue; fi
|
||||
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
|
||||
new_body+=("$line")
|
||||
continue
|
||||
fi
|
||||
local indent="${line%%[^[:space:]]*}"
|
||||
local -a tokens=($line)
|
||||
local -a kept=()
|
||||
local t
|
||||
for t in "${tokens[@]}"; do
|
||||
[[ -n "${to_remove[$t]:-}" ]] || kept+=("$t")
|
||||
done
|
||||
(( ${#kept[@]} > 0 )) && new_body+=("${indent}${kept[*]}")
|
||||
done
|
||||
|
||||
if [[ -n "$add_list" ]]; then
|
||||
new_body+=("$marker")
|
||||
local p
|
||||
while IFS= read -r p; do
|
||||
[[ -n "$p" ]] && new_body+=(" $p")
|
||||
done <<<"$add_list"
|
||||
fi
|
||||
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
printf '%s\n' "${lines[@]:0:start_idx+1}" "${new_body[@]}" "${lines[@]:end_idx}" >"$tmp"
|
||||
mv "$tmp" "$file"
|
||||
}
|
||||
|
||||
# --- group file de metapac ---------------------------------------------------
|
||||
# Regenerado en bootstrap/push/pull. Fuerza la unión con CRITICAL_PACKAGES: es
|
||||
# la segunda capa de protección, para que metapac nunca vea esos paquetes como
|
||||
# "no declarados" aunque el array se corrompiera.
|
||||
write_group_file() {
|
||||
mkdir -p "$(dirname "$METAPAC_GROUP")"
|
||||
{
|
||||
echo "# generado por pkgsync -- no editar a mano, se sobreescribe en cada bootstrap/push/pull"
|
||||
echo "[arch]"
|
||||
echo "packages = ["
|
||||
{ declared_array PACMAN_COMMON; declared_array AUR_COMMON; printf '%s\n' "${CRITICAL_PACKAGES[@]}"; } \
|
||||
| sort -u | while IFS= read -r p; do [[ -n "$p" ]] && printf ' "%s",\n' "$p"; done
|
||||
echo "]"
|
||||
echo
|
||||
echo "[flatpak]"
|
||||
echo "packages = ["
|
||||
declared_array FLATPAK_COMMON | sort -u | while IFS= read -r p; do
|
||||
[[ -n "$p" ]] && printf ' { name = "%s:%s", options = { remote = "flathub" } },\n' "$FLATPAK_INSTALLATION" "$p"
|
||||
done
|
||||
echo "]"
|
||||
} >"$METAPAC_GROUP"
|
||||
}
|
||||
|
||||
# --- diff declarado vs instalado --------------------------------------------
|
||||
# Deja el resultado en arrays globales: TO_INSTALL_* (declarado, no instalado)
|
||||
# y TO_REVIEW_REMOVE_* (instalado, no declarado).
|
||||
compute_diff() {
|
||||
local work
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' RETURN
|
||||
|
||||
{ declared_array PACMAN_COMMON; declared_array AUR_COMMON; printf '%s\n' "${CRITICAL_PACKAGES[@]}"; } | sort -u >"$work/declared_arch"
|
||||
declared_array FLATPAK_COMMON | sort -u >"$work/declared_flatpak"
|
||||
overrides_packages | sort -u >"$work/overrides"
|
||||
{ installed_pacman_native; installed_pacman_foreign; } | sort -u >"$work/installed_arch"
|
||||
installed_flatpak | sort -u >"$work/installed_flatpak"
|
||||
|
||||
mapfile -t TO_INSTALL_ARCH < <(comm -23 "$work/declared_arch" "$work/installed_arch" | comm -23 - "$work/overrides")
|
||||
mapfile -t TO_REVIEW_REMOVE_ARCH < <(comm -13 "$work/declared_arch" "$work/installed_arch" | comm -23 - "$work/overrides")
|
||||
mapfile -t TO_INSTALL_FLATPAK < <(comm -23 "$work/declared_flatpak" "$work/installed_flatpak" | comm -23 - "$work/overrides")
|
||||
mapfile -t TO_REVIEW_REMOVE_FLATPAK < <(comm -13 "$work/declared_flatpak" "$work/installed_flatpak" | comm -23 - "$work/overrides")
|
||||
}
|
||||
|
||||
print_list() {
|
||||
if (( $# == 0 )); then echo " (nada)"; return; fi
|
||||
printf ' %s\n' "$@"
|
||||
}
|
||||
|
||||
print_diff() {
|
||||
echo "== pendiente de instalar (declarado, no instalado) =="
|
||||
print_list "${TO_INSTALL_ARCH[@]}" "${TO_INSTALL_FLATPAK[@]}"
|
||||
echo
|
||||
echo "== instalado pero no declarado (candidato a revisar/borrar con 'pkgsync apply') =="
|
||||
print_list "${TO_REVIEW_REMOVE_ARCH[@]}" "${TO_REVIEW_REMOVE_FLATPAK[@]}"
|
||||
}
|
||||
|
||||
notify() {
|
||||
command -v notify-send &>/dev/null && notify-send --app-name=pkgsync "$1" "$2"
|
||||
}
|
||||
|
||||
# --- subcomandos --------------------------------------------------------------
|
||||
cmd_bootstrap() {
|
||||
echo ">> pkgsync bootstrap"
|
||||
|
||||
if ! command -v metapac &>/dev/null; then
|
||||
echo ">> instalando metapac (AUR)..."
|
||||
if command -v paru &>/dev/null; then
|
||||
paru -S --needed --noconfirm metapac
|
||||
else
|
||||
echo "!! no encuentro paru; instala metapac a mano (helper AUR) y vuelve a correr 'pkgsync bootstrap'." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$METAPAC_CONFIG_DIR/groups" "$(dirname "$REPO_DIR")" "$(dirname "$LOG")"
|
||||
|
||||
if [[ ! -f "$METAPAC_CONFIG_DIR/config.toml" ]]; then
|
||||
cat >"$METAPAC_CONFIG_DIR/config.toml" <<'EOF'
|
||||
enabled_backends = ["arch", "flatpak"]
|
||||
|
||||
[arch]
|
||||
package_manager = "paru"
|
||||
EOF
|
||||
echo ">> $METAPAC_CONFIG_DIR/config.toml creado"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$OVERRIDES_FILE" ]]; then
|
||||
cat >"$OVERRIDES_FILE" <<'EOF'
|
||||
# Paquetes que SOLO tienen sentido en ESTA máquina.
|
||||
#
|
||||
# pkgsync nunca lee ni escribe este fichero de forma automática: no vive en
|
||||
# el repo arch-setup, no viaja a la otra máquina. metapac sí lo combina con
|
||||
# el resto de group files de ~/.config/metapac/groups/ para sus propios
|
||||
# sync/clean, así que un paquete puesto aquí queda protegido de 'pkgsync
|
||||
# apply' igual que si estuviera en el array compartido.
|
||||
#
|
||||
# Descomenta y edita, por ejemplo:
|
||||
#
|
||||
# [arch]
|
||||
# packages = ["algun-paquete-solo-de-esta-maquina"]
|
||||
#
|
||||
# [flatpak]
|
||||
# packages = ["system:com.algo.SoloAqui"]
|
||||
EOF
|
||||
echo ">> scaffold de $OVERRIDES_FILE creado (vacío, edítalo a mano)"
|
||||
fi
|
||||
|
||||
if [[ ! -d "$REPO_DIR/.git" ]]; then
|
||||
echo ">> clonando arch-setup en $REPO_DIR..."
|
||||
git clone "$REPO_URL" "$REPO_DIR"
|
||||
fi
|
||||
|
||||
write_group_file
|
||||
echo ">> bootstrap completo. Activa los servicios con:"
|
||||
echo " systemctl --user enable --now pkgsync-push.service pkgsync-pull.service"
|
||||
}
|
||||
|
||||
cmd_push() {
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
exec >>"$LOG" 2>&1
|
||||
echo "=== push $(date -Iseconds) ==="
|
||||
|
||||
if [[ ! -d "$REPO_DIR/.git" ]]; then
|
||||
echo "!! no existe $REPO_DIR, corre 'pkgsync bootstrap' primero"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local work
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' RETURN
|
||||
|
||||
installed_pacman_native | sort -u >"$work/inst_native"
|
||||
installed_pacman_foreign | sort -u >"$work/inst_foreign"
|
||||
cat "$work/inst_native" "$work/inst_foreign" | sort -u >"$work/inst_arch_any"
|
||||
installed_flatpak | sort -u >"$work/inst_flatpak"
|
||||
overrides_packages | sort -u >"$work/overrides"
|
||||
printf '%s\n' "${CRITICAL_PACKAGES[@]}" | sort -u >"$work/critical"
|
||||
|
||||
declared_array PACMAN_COMMON | sort -u >"$work/decl_pacman"
|
||||
declared_array AUR_COMMON | sort -u >"$work/decl_aur"
|
||||
declared_array FLATPAK_COMMON | sort -u >"$work/decl_flatpak"
|
||||
cat "$work/decl_pacman" "$work/decl_aur" | sort -u >"$work/decl_arch_any"
|
||||
|
||||
comm -23 "$work/critical" "$work/inst_arch_any" | while IFS= read -r c; do
|
||||
[[ -n "$c" ]] && echo "!! AVISO: paquete crítico '$c' no está instalado localmente; se mantiene declarado (no se toca)"
|
||||
done
|
||||
|
||||
local remove_pacman add_pacman remove_aur add_aur remove_flatpak add_flatpak
|
||||
remove_pacman="$(comm -23 "$work/decl_pacman" "$work/inst_arch_any" | comm -23 - "$work/overrides" | comm -23 - "$work/critical")"
|
||||
add_pacman="$(comm -23 "$work/inst_native" "$work/decl_arch_any" | comm -23 - "$work/overrides")"
|
||||
remove_aur="$(comm -23 "$work/decl_aur" "$work/inst_arch_any" | comm -23 - "$work/overrides" | comm -23 - "$work/critical")"
|
||||
add_aur="$(comm -23 "$work/inst_foreign" "$work/decl_arch_any" | comm -23 - "$work/overrides")"
|
||||
remove_flatpak="$(comm -23 "$work/decl_flatpak" "$work/inst_flatpak" | comm -23 - "$work/overrides" | comm -23 - "$work/critical")"
|
||||
add_flatpak="$(comm -23 "$work/inst_flatpak" "$work/decl_flatpak" | comm -23 - "$work/overrides")"
|
||||
|
||||
local pc="$REPO_DIR/shared/packages-common.sh"
|
||||
patch_array_file "$pc" PACMAN_COMMON "$remove_pacman" "$add_pacman"
|
||||
patch_array_file "$pc" AUR_COMMON "$remove_aur" "$add_aur"
|
||||
patch_array_file "$pc" FLATPAK_COMMON "$remove_flatpak" "$add_flatpak"
|
||||
|
||||
write_group_file
|
||||
|
||||
cd "$REPO_DIR" || return 0
|
||||
git add shared/packages-common.sh
|
||||
if git diff --cached --quiet; then
|
||||
echo "sin cambios de paquetes que subir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
git commit -m "pkgsync: actualiza inventario de paquetes $(date '+%Y-%m-%d %H:%M')"
|
||||
if timeout 20 git push origin main; then
|
||||
echo "push OK"
|
||||
else
|
||||
echo "push falló (¿sin red?), el commit queda en local para el próximo intento"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_pull() {
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
exec >>"$LOG" 2>&1
|
||||
echo "=== pull $(date -Iseconds) ==="
|
||||
|
||||
if ! timeout 15 git ls-remote --exit-code "$REPO_URL" &>/dev/null; then
|
||||
echo "sin conexión con el remoto, me quedo con el estado local"
|
||||
elif [[ ! -d "$REPO_DIR/.git" ]]; then
|
||||
echo "clonando arch-setup en $REPO_DIR..."
|
||||
mkdir -p "$(dirname "$REPO_DIR")"
|
||||
timeout 30 git clone "$REPO_URL" "$REPO_DIR" && echo "clon inicial OK" || echo "clone falló"
|
||||
else
|
||||
if timeout 30 git -C "$REPO_DIR" pull --ff-only origin main; then
|
||||
echo "git pull OK"
|
||||
else
|
||||
echo "git pull falló (conflicto local o sin red); sigo con el estado que haya"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -d "$REPO_DIR/.git" ]]; then
|
||||
echo "no hay clon de arch-setup, no puedo calcular diff"
|
||||
return 0
|
||||
fi
|
||||
|
||||
write_group_file
|
||||
compute_diff
|
||||
|
||||
local n_install=$(( ${#TO_INSTALL_ARCH[@]} + ${#TO_INSTALL_FLATPAK[@]} ))
|
||||
local n_remove=$(( ${#TO_REVIEW_REMOVE_ARCH[@]} + ${#TO_REVIEW_REMOVE_FLATPAK[@]} ))
|
||||
|
||||
print_diff
|
||||
|
||||
if (( n_install == 0 && n_remove == 0 )); then
|
||||
echo "sin cambios pendientes"
|
||||
else
|
||||
notify "pkgsync: cambios de paquetes pendientes" \
|
||||
"$n_install a instalar, $n_remove a revisar para borrar. Detalle: pkgsync diff (o $LOG)"
|
||||
fi
|
||||
|
||||
if [[ "${PKGSYNC_AUTO_INSTALL:-0}" == "1" ]] && (( n_install > 0 )); then
|
||||
echo "PKGSYNC_AUTO_INSTALL=1: instalando automáticamente lo que falta (nunca borra)"
|
||||
metapac sync --no-confirm
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_apply() {
|
||||
echo ">> pkgsync apply: metapac sync (instalar) + metapac clean (borrar, con confirmación)"
|
||||
[[ -d "$REPO_DIR/.git" ]] || { echo "!! no existe $REPO_DIR, corre 'pkgsync bootstrap' primero"; return 1; }
|
||||
write_group_file
|
||||
metapac sync
|
||||
metapac clean
|
||||
}
|
||||
|
||||
cmd_log() {
|
||||
[[ -d "$REPO_DIR/.git" ]] || { echo "!! no hay clon en $REPO_DIR, corre 'pkgsync bootstrap' primero"; return 1; }
|
||||
if [[ "${1:-}" == "--patch" ]]; then
|
||||
git -C "$REPO_DIR" log --follow -p -- shared/packages-common.sh
|
||||
else
|
||||
git -C "$REPO_DIR" log --follow --stat -- shared/packages-common.sh
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_incoming() {
|
||||
[[ -d "$REPO_DIR/.git" ]] || { echo "!! no hay clon en $REPO_DIR, corre 'pkgsync bootstrap' primero"; return 1; }
|
||||
git -C "$REPO_DIR" fetch --quiet origin
|
||||
local n
|
||||
n="$(git -C "$REPO_DIR" log HEAD..origin/main --oneline | wc -l)"
|
||||
if (( n == 0 )); then
|
||||
echo "sin commits nuevos en origin/main"
|
||||
else
|
||||
echo "$n commit(s) nuevos en origin/main:"
|
||||
git -C "$REPO_DIR" log HEAD..origin/main --oneline
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_audit() {
|
||||
local work
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' RETURN
|
||||
|
||||
{ declared_array PACMAN_COMMON; declared_array AUR_COMMON; printf '%s\n' "${CRITICAL_PACKAGES[@]}"; overrides_packages; } | sort -u >"$work/declared_arch"
|
||||
{ declared_array FLATPAK_COMMON; overrides_packages; } | sort -u >"$work/declared_flatpak"
|
||||
|
||||
installed_pacman_native | sort -u >"$work/inst_native"
|
||||
installed_pacman_foreign | sort -u >"$work/inst_foreign"
|
||||
installed_flatpak | sort -u >"$work/inst_flatpak"
|
||||
|
||||
echo "== pacman (oficiales) instalados, no declarados en ningún array =="
|
||||
comm -23 "$work/inst_native" "$work/declared_arch"
|
||||
echo
|
||||
echo "== AUR/foráneos instalados, no declarados en ningún array =="
|
||||
comm -23 "$work/inst_foreign" "$work/declared_arch"
|
||||
echo
|
||||
echo "== flatpaks instalados, no declarados =="
|
||||
comm -23 "$work/inst_flatpak" "$work/declared_flatpak"
|
||||
echo
|
||||
echo "(nota: los paquetes específicos de KDE/GNOME hardcodeados en <de>/setup.sh"
|
||||
echo " -- plasma-desktop, sddm, temas AUR, etc. -- siempre van a salir aquí: no"
|
||||
echo " viven en los arrays de packages-common.sh por diseño, no es drift real)"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
uso: pkgsync.sh <bootstrap|push|pull|diff|apply|log|incoming|audit>
|
||||
|
||||
bootstrap instala metapac + config + clon local (primera vez / retrofit)
|
||||
push captura el inventario real y lo sube al repo (usa pkgsync-push.service)
|
||||
pull git pull + calcula diff + notifica; NO instala ni desinstala nada (usa pkgsync-pull.service)
|
||||
diff como pull pero solo imprime el diff, sin tocar git ni notificar
|
||||
apply aplica lo pendiente con metapac (sync + clean), pidiendo confirmación
|
||||
log [--patch] historial de shared/packages-common.sh
|
||||
incoming commits nuevos en origin/main aún no traídos
|
||||
audit paquetes instalados localmente que no están declarados en ningún sitio
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
local cmd="${1:-}"
|
||||
[[ $# -gt 0 ]] && shift
|
||||
case "$cmd" in
|
||||
bootstrap) cmd_bootstrap ;;
|
||||
push) cmd_push ;;
|
||||
pull) cmd_pull ;;
|
||||
diff) compute_diff; print_diff ;;
|
||||
apply) cmd_apply ;;
|
||||
log) cmd_log "$@" ;;
|
||||
incoming) cmd_incoming ;;
|
||||
audit) cmd_audit ;;
|
||||
*) usage; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user