Compare commits

..
39 Commits
Author SHA1 Message Date
Josevi 5c353e7287 pkgsync: actualiza inventario de paquetes 2026-09-21 09:14 2026-09-21 09:14:36 +02:00
Josevi 9ad957f4a0 pkgsync: actualiza inventario de paquetes 2026-09-20 23:28 2026-09-21 09:10:42 +02:00
JoseviandClaude Sonnet 5 2c288e1aec kde/setup.sh: actualiza comentario de plasmoides vía chezmoi
claudeusageswitcher y tt.launchpadPlasma ya no están en ningún panel
real (auditoría de plasmoides instalados vs. usados) y se quitaron del
tracking de chezmoi en arch-configs. commandoutput y codexbar sí están
en uso pero no viajaban -- añadidos al tracking. Refleja ambos cambios
aquí para que el comentario no mienta sobre qué plasmoides trae chezmoi.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:16:04 +02:00
JoseviandClaude Sonnet 5 da076af6ad kde/setup.sh, packages-common.sh: quita temas/efectos gráficos sin usar
Auditado contra los look-and-feel Vampire-Light/Vampire-dark (fuente de
verdad real: ~/.local/share/plasma/look-and-feel/*/contents/defaults)
y el kwinrc en vivo. Desinstalado de la máquina real y quitado de los
arrays: plasma6-themes-layan-git, layan-cursor-theme-git,
googledot-cursor-theme, breeze-enhanced, papirus-icon-theme,
kwin-effect-rounded-corners-git, plasma6-applets-panel-colorizer,
kwin-effects-better-blur-dx, klassy. Ninguno aparecía en los defaults
de los temas Vampire-* ni en kwinrc (icono real es Tela/Tela-dark,
cursor Bibata-Modern-Classic, decoración sierrabreezeenhanced).
kwin-effects-glass-git SÍ se queda (glassEnabled=true en kwinrc).

De paso, corregido kdeglobals[KDE]DefaultDarkLookAndFeel de la
referencia colgante a Layan (ya desinstalado) a Vampire-dark.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:13:02 +02:00
JoseviandClaude Sonnet 5 9a9f6d9931 packages-common.sh: migra fastpotify-bin a spotifast-bin (renombrado upstream)
fastpotify-bin avisaba en su propia descripción "Renamed to
spotifast-bin"; desinstalado y sustituido por el paquete AUR con el
nombre nuevo en la máquina real.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:06:32 +02:00
JoseviandClaude Sonnet 5 923da6d375 packages-common.sh: quita tauri-cli (sin ningún proyecto Tauri en la máquina)
Desinstalado del sistema real junto con ~30 dependencias huérfanas
acumuladas (pacman -Qdtq); tauri-cli quedó de una prueba puntual del
31-ago-2026 y nada lo usaba.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:05:18 +02:00
JoseviandClaude Sonnet 5 f86e424491 kde/setup.sh: pasos no críticos no abortan la instalación completa
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:01:17 +02:00
JoseviandClaude Sonnet 5 a8b4e3f51d kde/setup.sh: añade bit ejecutable
git ls-files lo tenía como 100644; ./kde/setup.sh (tal como indica el
README) fallaba con "permission denied" en un clon fresco. Detectado
durante una prueba end-to-end en VM.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 21:06:35 +02:00
JoseviandClaude Sonnet 5 226b439684 kde/setup.sh: mueve configure_btrfs_rollback() al final
snap-pac crea un snapshot pre+post por cada transacción de pacman/paru en
cuanto existe /etc/snapper/configs/root, y dentro de arch-chroot no hay
systemd para que el timer de cleanup aplique NUMBER_LIMIT. Con snapper
configurado antes de las ~30 instalaciones AUR, se acumulan decenas de
snapshots sin poda posible durante toda la instalación (visto en real:
llenó el disco de una VM de prueba). Configurarlo al final evita
snapshotear la propia instalación inicial.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 21:06:35 +02:00
JoseviandClaude Sonnet 5 68e30e9feb snapper: fuerza --no-dbus (falla en arch-chroot sin dbus-daemon)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 19:23:41 +02:00
Josevi 5b33bd26f9 pkgsync: actualiza inventario de paquetes 2026-09-20 11:03 2026-09-20 11:03:24 +02:00
Josevi 7a8693de8a pkgsync: actualiza inventario de paquetes 2026-09-19 21:55 2026-09-19 21:55:48 +02:00
Josevi df9cf52acf pkgsync: actualiza inventario de paquetes 2026-09-19 21:26 2026-09-19 21:26:29 +02:00
Josevi 4f2c3e937b pkgsync: actualiza inventario de paquetes 2026-09-19 17:15 2026-09-19 17:15:52 +02:00
Josevi 56c924d0a6 pkgsync: actualiza inventario de paquetes 2026-09-17 18:58 2026-09-17 18:58:48 +02:00
JoseviandClaude Sonnet 5 1994f7b030 pkgsync: añade aviso por Gotify a los fallos, además de notify-send
notify-send depende de la sesión gráfica -- poco fiable justo cuando
push falla en ExecStop durante el apagado (sesión/D-Bus cayéndose).
notify_fail() ahora también hace POST a push.josevi.ovh (mismo Gotify
que ya usa WUD para avisos de actualizaciones), best-effort con
timeout corto, leyendo el token de ~/.config/pkgsync/gotify.env --
fichero local-only, nunca tracked (mismo criterio que
overrides.local.toml), con scaffold comentado en bootstrap.

De paso: durante la verificación se descubrió que notify-send llevaba
tiempo fallando en silencio en esta máquina (applet de Notifications
ausente de shownItems/extraItems en el systray) -- arreglado aparte,
no es parte de este commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 18:58:37 +02:00
Josevi 56742247ce pkgsync: actualiza inventario de paquetes 2026-09-17 18:49 2026-09-17 18:49:34 +02:00
JoseviandClaude Sonnet 5 64bb53eadf pkgsync: aborta sin commitear si falla el parcheo, notifica fallos
cmd_push() seguía adelante y commiteaba lo que hubiera en disco aunque
patch_array_file no encontrara los arrays -- así fue como el fichero
acabó vacío en origin/main. Ahora aborta sin tocar git si cualquiera
de los 3 parcheos falla.

Añade notify_fail() (notify-send con urgencia critical) en los puntos
de fallo real de push/pull: parcheo fallido, git push/pull/clone
fallidos. Como push corre en ExecStop durante el apagado (sesión/D-Bus
cayéndose, notify-send no garantizado), también deja un marcador en
disco que el siguiente pull revisa al arrancar con la sesión completa,
para no depender de que la notificación del apagado llegara a verse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 18:49:26 +02:00
JoseviandClaude Sonnet 5 598e8b52c8 Restaura shared/packages-common.sh, vaciado por un push corrupto
El push de pkgsync del 2026-08-27 17:41 (8575c4f) commiteó el fichero
ya vacío en disco (algo lo truncó a 0 bytes fuera de pkgsync entre las
16:10 y las 17:41 de ese día -- causa exacta desconocida), porque
cmd_push() no validaba que el parcheo de arrays hubiera funcionado
antes de commitear. Restaurado desde f3cdc35 (último commit bueno,
mismo día 07:37). El siguiente push recapturará los 3 semanas de
drift real desde entonces.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 18:49:20 +02:00
Josevi 8575c4ffa3 pkgsync: actualiza inventario de paquetes 2026-08-27 17:41 2026-08-27 17:41:05 +02:00
Josevi f3cdc3525d pkgsync: actualiza inventario de paquetes 2026-08-27 07:37 2026-08-27 07:37:13 +02:00
Josevi 25beb525bc pkgsync: actualiza inventario de paquetes 2026-08-26 18:06 2026-08-26 18:06:31 +02:00
Josevi 179affc994 pkgsync: actualiza inventario de paquetes 2026-08-26 09:42 2026-08-26 09:42:58 +02:00
Josevi 3a95ae12e9 pkgsync: actualiza inventario de paquetes 2026-08-25 10:44 2026-08-25 10:44:25 +02:00
Josevi f190e739f9 pkgsync: actualiza inventario de paquetes 2026-08-24 21:55 2026-08-24 21:55:31 +02:00
Josevi d891ca5dd4 pkgsync: actualiza inventario de paquetes 2026-08-24 10:21 2026-08-24 10:21:08 +02:00
Josevi a2ae1da768 pkgsync: actualiza inventario de paquetes 2026-08-24 07:15 2026-08-24 07:15:59 +02:00
Josevi 5fda5aaaf6 pkgsync: actualiza inventario de paquetes 2026-08-18 12:31 2026-08-18 12:31:11 +02:00
Josevi 9f0d0f1179 Quita la variante GNOME: KDE Plasma es el único escritorio que se usa
En el uso real nunca se ha instalado GNOME en ninguna de las dos máquinas.
Elimina gnome/ entero (archinstall-config.json + setup.sh) y limpia todas
las referencias en README.md, INSTALL.md, packages-common.sh y pkgsync.sh
(comentarios "<de>/setup.sh" genéricos, comparaciones GNOME/KDE, la variable
DE ya inútil en kde/setup.sh). Sigue disponible en el historial de git si
algún día hiciera falta.
2026-08-18 11:35:35 +02:00
Josevi 8d1a839578 README: explica por qué dotfiles y paquetes viven en repos separados 2026-08-18 11:30:32 +02:00
Josevi d411593149 configure_btrfs_rollback: ALLOW_GROUPS=wheel para que el usuario use snapper sin sudo
El chmod/chown de /.snapshots no basta -- snapper tiene su propia lista de
permitidos (ALLOW_USERS/ALLOW_GROUPS) en la config, independiente de los
permisos de fichero. Sin esto 'snapper -c root list' da 'Sin permisos' para
cualquiera que no sea root. Detectado en real en Vampire-HM80.
2026-08-18 11:26:30 +02:00
Josevi dc30383bf7 Añade inotify-tools: grub-btrfsd lo necesita, es dep. opcional de grub-btrfs
Detectado en real en Vampire-HM80: los 3 paquetes de rollback btrfs estaban
declarados e instalados, pero grub-btrfsd moría con exit 1 porque pacman
--needed no arrastra dependencias opcionales. Sin inotify-tools el daemon
nunca regenera el grub.cfg con los snapshots -- toda la red de seguridad de
rollback quedaba muda en silencio.
2026-08-18 11:23:38 +02:00
Josevi 41d64de461 pkgsync: notify-send con timeout, no debe poder colgar pkgsync-pull.service
Un StartServiceByName de D-Bus atascado hizo que notify-send se colgara,
comiéndose el TimeoutStartSec=60 del servicio systemd y matándolo (probado
en la máquina real). Acotado a 5s como los git ls-remote/clone/pull.
2026-08-18 11:12:45 +02:00
Josevi b740ed5db5 pkgsync: actualiza inventario de paquetes 2026-08-18 11:09 2026-08-18 11:09:32 +02:00
Josevi d1077f0547 Arregla bug de trap RETURN en pkgsync.sh y quita mención stale a DavMail
trap RETURN persiste entre retornos de función en bash, no solo el de la
función que lo puso: al volver de compute_diff/cmd_push/cmd_audit hacia
arriba en la pila, volvía a dispararse sobre un $work ya fuera de scope
('work: variable sin asignar' bajo set -u). Cambiado a rm -rf explícito en
cada punto de salida.

DavMail se desinstaló del sistema; ya no debía aparecer en la lista de apps.
2026-08-18 11:08:54 +02:00
Josevi c074095d68 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.
2026-08-18 10:46:23 +02:00
Josevi f19b6f0f93 feat: rollback btrfs + kernel LTS + sync paquetes/flatpaks
- Añade linux-lts como kernel fallback en archinstall (GNOME/KDE).
- Implementa snapper + snap-pac + grub-btrfs para rollback de actualizaciones.
- Configura snapshots solo en operaciones de pacman (timeline deshabilitado).
- Sincroniza PACMAN_COMMON/AUR_COMMON con la máquina maestra.
- Añade FLATPAK_COMMON para Heroic, Karere y Buffer.
- Elimina paquetes no presentes: obsidian, davmail, karere, heroic-games-launcher-bin.
- Añade packagekit-qt6 y kwin-decoration-sierra-breeze-enhanced-git en KDE.
- Actualiza PROJECT_CONTEXT.md con la nueva arquitectura.
2026-08-13 17:11:49 +02:00
JoseviandClaude Sonnet 5 f87c2c08ce update settings
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 13:43:14 +02:00
JoseviandClaude Sonnet 5 e8e5b2060b Sincroniza kde/setup.sh con lo instalado hoy en esta máquina
- ksshaskpass: ssh-add helper vía kwallet, instalado a mano hoy (03/08).
- kwin-effects-glass-git: efecto de cristal para kwin, mismo día, junto al
  resto de efectos de blur ya trackeados.

packagekit-qt6 sigue instalado en esta máquina pero se deja fuera del script
a propósito: la decisión de quitarlo (commit 4cb89eb) sigue vigente, es solo
resto de antes de esa limpieza.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 13:42:28 +02:00
11 changed files with 956 additions and 256 deletions
+17 -1
View File
@@ -15,7 +15,23 @@
"Bash(chezmoi managed *)", "Bash(chezmoi managed *)",
"Bash(chezmoi source-path *)", "Bash(chezmoi source-path *)",
"Bash(chezmoi status *)", "Bash(chezmoi status *)",
"Bash(chezmoi add *)" "Bash(chezmoi add *)",
"Bash(find / -maxdepth 4 -iname \"PROJECT_CONTEXT.md\")",
"Bash(find / -maxdepth 6 -iname \"PROJECT_CONTEXT.md\")",
"Bash(pacman -Qqe)",
"Bash(paru -Qqem)",
"Bash(awk '/^PACMAN_COMMON=\\\\\\(/,/^\\\\\\)/' shared/packages-common.sh)",
"Bash(grep -v '^$')",
"Bash(awk '/install_pacman \"\\\\${PACMAN_COMMON\\\\[@\\\\]}\"/,/^$/' kde/setup.sh)",
"Bash(grep -v '^$\\\\|install_pacman\\\\|PACMAN_COMMON')",
"Bash(awk '/^AUR_COMMON=\\\\\\(/,/^\\\\\\)/' shared/packages-common.sh)",
"Bash(awk '/install_aur \"\\\\${AUR_COMMON\\\\[@\\\\]}\"/,/^install_helpwire/' kde/setup.sh)",
"Bash(grep -v '^$\\\\|install_aur\\\\|AUR_COMMON\\\\|install_helpwire')",
"Bash(flatpak list *)",
"Bash(pacman -Q mesa)",
"Bash(pacman -Qm)",
"Bash(python3 -c ' *)",
"Bash(git add *)"
] ]
} }
} }
+5 -5
View File
@@ -1,7 +1,7 @@
# Procedimiento de instalación # Procedimiento de instalación
Pasos para instalar Arch en las dos máquinas (NVMe 250G + SSD SATA 1TB) desde el Pasos para instalar Arch en las dos máquinas (NVMe 250G + SSD SATA 1TB) desde el
USB live, en un comando. Variantes: GNOME o KDE. USB live, en un comando. Escritorio: KDE Plasma.
## 0. Una vez (preparación de repos) ## 0. Una vez (preparación de repos)
@@ -41,7 +41,7 @@ como `sda`. Confirma con `lsblk` que solo tus discos reales tienen `RM` a `0`. E
pacman -Sy --noconfirm git pacman -Sy --noconfirm git
git clone https://gitea.josevi.ovh/josevi/arch-setup git clone https://gitea.josevi.ovh/josevi/arch-setup
cd arch-setup cd arch-setup
./gnome/setup.sh # o ./kde/setup.sh ./kde/setup.sh
``` ```
## 5. Responder los prompts ## 5. Responder los prompts
@@ -60,9 +60,9 @@ y **email** de git, y la **contraseña** del usuario. A partir de ahí va solo:
reboot reboot
``` ```
Quita el USB. En el **primer login gráfico** se aplica el tema Catppuccin, Quita el USB. El tema (Layan + iconos Tela + cursores Layan-white) y el resto
iconos, fuentes, dock (GNOME) y el teclado `us-intl`. Ese oneshot se del look ya vienen aplicados por `kde/setup.sh` y chezmoi; solo falta el
autodestruye; el segundo login arranca limpio. primer login gráfico normal.
## 7. Segunda máquina ## 7. Segunda máquina
+136 -17
View File
@@ -2,13 +2,13 @@
Instalación automatizada de Arch Linux para mis dos máquinas (hardware idéntico), Instalación automatizada de Arch Linux para mis dos máquinas (hardware idéntico),
en **un único comando** desde el USB live, sin reiniciar entre la base y la config. en **un único comando** desde el USB live, sin reiniciar entre la base y la config.
Dos variantes de escritorio: **GNOME** y **KDE Plasma**. Escritorio: **KDE Plasma** (único, sin GNOME — ver "Decisiones").
``` ```
arch-setup/ arch-setup/
├── gnome/ archinstall-config.json + setup.sh (GNOME, gdm) ├── kde/ archinstall-config.json + setup.sh (KDE Plasma, sddm)
├── kde/ archinstall-config.json + setup.sh (KDE Plasma, sddm) ├── shared/ packages-common.sh (paquetes base)
├── shared/ packages-common.sh (paquetes comunes a ambos) ├── pkgsync/ pkgsync.sh + systemd/ (sync de paquetes entre máquinas)
└── README.md └── README.md
``` ```
@@ -20,14 +20,14 @@ arch-setup/
pacman -Sy --noconfirm git pacman -Sy --noconfirm git
git clone https://gitea.josevi.ovh/josevi/arch-setup git clone https://gitea.josevi.ovh/josevi/arch-setup
cd arch-setup cd arch-setup
./gnome/setup.sh # o ./kde/setup.sh ./kde/setup.sh
``` ```
El script te pide: usuario a crear, token de Gitea (para el repo `arch-configs` El script te pide: usuario a crear, token de Gitea (para el repo `arch-configs`
privado), nombre y email de git. Después, sin más intervención: privado), nombre y email de git. Después, sin más intervención:
1. `archinstall --silent` con el JSON de la variante → instala base + escritorio 1. `archinstall --silent` con `kde/archinstall-config.json` → instala base +
en `/mnt`. KDE Plasma en `/mnt`.
2. Se copia a sí mismo a `/mnt` y entra en `arch-chroot`. 2. Se copia a sí mismo a `/mnt` y entra en `arch-chroot`.
3. Dentro del chroot: crea el usuario, instala paquetes (pacman + AUR vía 3. Dentro del chroot: crea el usuario, instala paquetes (pacman + AUR vía
`paru`), aplica el aspecto y ejecuta `chezmoi init --apply` apuntando a `paru`), aplica el aspecto y ejecuta `chezmoi init --apply` apuntando a
@@ -40,13 +40,29 @@ privado), nombre y email de git. Después, sin más intervención:
`paru` (se bootstrapea solo). Sin Flatpak salvo que lo añadas. `paru` (se bootstrapea solo). Sin Flatpak salvo que lo añadas.
- **Contenedores**: Podman + `podman-docker` (capa de compat), no Docker. - **Contenedores**: Podman + `podman-docker` (capa de compat), no Docker.
- **Navegador**: Zen Browser. También ungoogled-chromium. - **Navegador**: Zen Browser. También ungoogled-chromium.
- **Aspecto**: JetBrains Mono Nerd en ambos escritorios; el resto diverge por DE. - **Aspecto**: JetBrains Mono Nerd + tema global Layan + iconos Tela + cursores
GNOME: Catppuccin Mocha + iconos Papirus-Dark + Dash-to-Dock y extensiones Layan-white (paquetes AUR, ver `kde/setup.sh`) + splash Animated-Abstract-Cube;
(AppIndicator, Blur my Shell, Caffeine, Clipboard Indicator, Vitals). KDE: tema look capturado del portátil con `shared/capture-kde.sh` y aplicado vía chezmoi.
global Layan + iconos Tela + cursores Layan-white (paquetes AUR, ver - **Solo KDE Plasma, sin GNOME**: hubo variante GNOME en su día; se retiró
`kde/setup.sh`) + splash Animated-Abstract-Cube; look capturado del portátil porque en el uso real KDE es lo único que se usa. Si algún día hiciera falta
con `shared/capture-kde.sh` y aplicado vía chezmoi. recuperarla, está en el historial de git (commit anterior a la retirada).
- **Dotfiles**: `chezmoi` apuntando a un repo separado (`arch-configs`). - **Dotfiles en repo separado, paquetes en este mismo**: no es arbitrario.
`arch-configs` (dotfiles, vía `chezmoi`) es datos personales sin relación
con "cómo se instala nada" — cambian solos a su ritmo, generan un historial
de commits ruidoso ("Auto-backup HH:MM"), y `setup.sh` los aplica como
**último** paso, con el sistema ya funcionando; si `chezmoi init --apply`
fallara entero, igualmente tendrías un Arch+KDE arrancable, solo que sin tu
toque personal. `shared/packages-common.sh` en cambio *es* el instalador,
no datos sobre el instalador: `kde/setup.sh` hace `source` de ese fichero
y pasa sus arrays directamente a `install_pacman`/`install_aur` — el array
es el input directo de la lógica de instalación, en el mismo repo que
arranca todo el proceso. Meterlo en un repo aparte obligaría a `setup.sh` a
clonar un segundo repo a medias de la instalación (dentro del chroot, sin
red garantizada) solo para saber qué paquetes meter. La línea divisoria es
"cómo se construye el sistema" (este repo, código + su propia lista de
paquetes) vs "qué aspecto tiene una vez construido" (`arch-configs`, datos
personales) — `pkgsync` (sección más abajo) vive aquí porque edita
exactamente el mismo array que el instalador consume.
## ⚠️ Antes de lanzar ## ⚠️ Antes de lanzar
@@ -73,7 +89,110 @@ privado), nombre y email de git. Después, sin más intervención:
Comunes (`shared/packages-common.sh`): zsh+starship+CLI moderno, git+base-devel, Comunes (`shared/packages-common.sh`): zsh+starship+CLI moderno, git+base-devel,
Podman, nodejs/python/rustup, Bitwarden, DBeaver, Obsidian, GIMP, Syncthing, Podman, nodejs/python/rustup, Bitwarden, DBeaver, Obsidian, GIMP, Syncthing,
Remmina+FreeRDP, Apostrophe, guvcview, KVIrc, Zen Browser, VSCode, DavMail, Remmina+FreeRDP, Apostrophe, guvcview, KVIrc, Zen Browser, VSCode,
Betterbird, OnlyOffice, Spotify, ungoogled-chromium, Karere, HelpWire (vía Betterbird, OnlyOffice, Spotify, ungoogled-chromium, Karere, HelpWire (vía
`.deb`→`debtap`). GNOME añade gnome-calculator/tweaks; KDE añade `.deb`→`debtap`). KDE añade kcalc/ark/spectacle/kdeconnect/kinfocenter
kcalc/ark/spectacle/kdeconnect/kinfocenter. (inline en `kde/setup.sh`, ver "Decisiones").
## pkgsync — sincronización de paquetes entre las dos máquinas
Mismo patrón que chezmoi para los dotfiles (`chezmoi-boot-sync.service` /
`chezmoi-shutdown-sync.service` en el repo `arch-configs`), pero para qué
paquetes hay instalados de verdad (pacman + AUR + flatpak). Fuente de verdad:
los arrays `PACMAN_COMMON`/`AUR_COMMON`/`FLATPAK_COMMON` de
`shared/packages-common.sh`. Motor de diff/aplicación:
[metapac](https://github.com/ripytide/metapac) (AUR), con `paru` como backend
para que cubra repos oficiales y AUR en una sola pasada.
```
pkgsync/
├── pkgsync.sh CLI: bootstrap/push/pull/diff/apply/log/incoming/audit
└── systemd/
├── pkgsync-push.service captura+sube al cerrar sesión/apagar
└── pkgsync-pull.service trae+compara al iniciar sesión (nunca instala/desinstala)
```
### Cómo funciona
- **`pkgsync push`** (dispara `pkgsync-push.service` al cerrar sesión, mismo
truco `ExecStop` en unidad oneshot `RemainAfterExit` que usa
`chezmoi-shutdown-sync.service`): captura el inventario real
(`pacman -Qqen`/`pacman -Qqem`/`flatpak list`), parchea los tres arrays de
`packages-common.sh` en un clon dedicado (`~/.local/share/arch-setup`) —
añade lo nuevo, quita lo que ya no está instalado — y hace `git commit` +
`push` best-effort con timeout, igual que chezmoi. El parcheo respeta los
comentarios de agrupación existentes: lo nuevo se añade en un bloque
marcador al final del array, listo para que lo reorganices a mano cuando
quieras.
- **`pkgsync pull`** (dispara `pkgsync-pull.service` al iniciar sesión): `git
pull` best-effort con timeout (nunca bloquea el login), calcula el diff
entre lo declarado y lo instalado, y **solo notifica** (notificación de
escritorio + detalle en `~/.local/share/pkgsync.log`). No instala ni
desinstala nada por sí solo.
- **`pkgsync apply`** (manual, cuando tú decidas revisar el diff): corre
`metapac sync` (instala lo que falte) y `metapac clean` (desinstala lo
sobrante), **ambos con la confirmación interactiva nativa de metapac** —
nunca se pasa `--no-confirm`, así que siempre ves y confirmas cada borrado
antes de que ocurra.
- **`pkgsync diff`**: igual que el cálculo de `pull` pero solo imprime, sin
tocar git ni notificar.
- **`pkgsync incoming`**: commits nuevos en `origin/main` aún no traídos
(equivalente a `git log HEAD..origin/main`).
- **`pkgsync log [--patch]`**: historial legible de cambios de paquetes
(`git log` sobre `shared/packages-common.sh`).
- **`pkgsync audit`**: paquetes instalados localmente que no están declarados
en ningún array ni en el override local — drift no capturado. Nota: los
paquetes específicos de KDE hardcodeados en `kde/setup.sh`
(`plasma-desktop`, `sddm`, temas AUR, etc.) siempre van a aparecer aquí —
no viven en los arrays por diseño, no es drift real.
### Paquetes críticos (nunca se borran automáticamente)
`pkgsync.sh` lleva una lista `CRITICAL_PACKAGES` hardcodeada (kernel,
bootloader, drivers gráficos, red de rollback btrfs, y todo lo que sea
soporte de hardware: CPU/energía, audio, bluetooth, red física, USB) que es
independiente del array — sobrevive aunque el array se corrompa por error
humano. Dos capas de protección: `push` nunca la quita del array aunque
detecte que un paquete crítico no está instalado localmente (solo avisa en el
log), y el group file que se genera para metapac siempre fuerza la unión con
esta lista, así que `metapac clean` nunca puede verlos como "no declarados".
La lista completa está al principio de `pkgsync.sh`.
### Overrides locales (divergencias legítimas entre máquinas)
`~/.config/metapac/groups/overrides.local.toml` — mismo formato de group file
que usa metapac. No vive en ningún repo git (fuera de `arch-setup`, en
`~/.config/metapac/`), así que ya queda fuera del control de versiones sin
necesidad de nada más. `pkgsync` lo lee solo para no proponer esos paquetes
como "nuevos" en `push` ni marcarlos como drift en `audit`; nunca lo escribe
ni lo sobreescribe. `bootstrap` deja un scaffold comentado si no existe.
### Instalación
**Máquinas nuevas**: automático, `install_pkgsync()` (en
`packages-common.sh`) se llama desde `kde/setup.sh` justo después de
`chezmoi init --apply`.
**Máquinas ya instaladas** (retrofit, sin reinstalar):
```bash
mkdir -p ~/.local/bin ~/.config/systemd/user
git clone https://gitea.josevi.ovh/josevi/arch-setup.git ~/.local/share/arch-setup
cp ~/.local/share/arch-setup/pkgsync/pkgsync.sh ~/.local/bin/ && chmod +x ~/.local/bin/pkgsync.sh
cp ~/.local/share/arch-setup/pkgsync/systemd/*.service ~/.config/systemd/user/
pkgsync.sh bootstrap
systemctl --user enable --now pkgsync-push.service pkgsync-pull.service
```
`loginctl enable-linger` ya está activo por el `run_once` de chezmoi, no hace
falta repetirlo. Verifica una vez con `flatpak list
--columns=installation,application` que tu instalación de Flatpak es
`system` (si es `user`, cambia `FLATPAK_INSTALLATION` al principio de
`pkgsync.sh`).
### Auto-instalar sin supervisión (opt-in, apagado por defecto)
`pkgsync pull` respeta la variable de entorno `PKGSYNC_AUTO_INSTALL=1`: si
está activada, corre `metapac sync --no-confirm` justo después de calcular el
diff (solo instala lo que falte). **Nunca** toca `metapac clean` — los
borrados siguen requiriendo `pkgsync apply` manual, pase lo que pase.
-35
View File
@@ -1,35 +0,0 @@
{
"config_version": "3.0.0",
"archinstall-language": "Spanish",
"bootloader": "Grub",
"hostname": "arch-gnome",
"kernels": [
"linux"
],
"ntp": true,
"swap": true,
"timezone": "Europe/Madrid",
"locale_config": {
"kb_layout": "us-acentos",
"sys_enc": "UTF-8",
"sys_lang": "es_ES.UTF-8"
},
"mirror_config": {
"mirror_regions": {
"Spain": []
}
},
"network_config": {
"type": "nm"
},
"audio_config": {
"audio": "pipewire"
},
"packages": [
"networkmanager",
"git",
"sudo",
"vim",
"curl"
]
}
-149
View File
@@ -1,149 +0,0 @@
#!/usr/bin/env bash
# Instalación + configuración de Arch con GNOME en UN comando desde el USB live.
#
# ./gnome/setup.sh
#
# Dos fases en el mismo fichero (ponytail: una sola fuente de verdad por DE,
# en vez de duplicar la lógica en un bootstrap + un post-install):
# - SETUP_PHASE=host -> corre archinstall y se re-lanza dentro del chroot
# - SETUP_PHASE=chroot -> instala paquetes/tema/chezmoi en el sistema nuevo
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DE="gnome"
SETUP_PHASE="${SETUP_PHASE:-host}"
GITEA="https://gitea.josevi.ovh/josevi"
# ---------------------------------------------------------------------------
# FASE 1 (USB live): archinstall + chroot
# ---------------------------------------------------------------------------
if [[ "$SETUP_PHASE" == "host" ]]; then
[[ $EUID -eq 0 ]] || { echo "Ejecuta como root desde el live ISO."; exit 1; }
read -rp "Usuario a crear: " INSTALL_USER
read -rsp "Token de Gitea (repo arch-configs privado, Enter si es público): " GITEA_TOKEN; echo
read -rp "Nombre para git [$INSTALL_USER]: " GIT_NAME; GIT_NAME="${GIT_NAME:-$INSTALL_USER}"
read -rp "Email para git [josevisw@gmail.com]: " GIT_EMAIL; GIT_EMAIL="${GIT_EMAIL:-josevisw@gmail.com}"
echo ">> Generando disk_config dimensionado a los discos reales..."
# archinstall 4.x quitó "Percent": el layout se calcula aquí (ver shared/gen-disk-config.py)
python3 "$REPO_DIR/shared/gen-disk-config.py" "$REPO_DIR/$DE/archinstall-config.json" >/tmp/archinstall-merged.json
echo ">> Lanzando archinstall (instala base + GNOME en /mnt)..."
archinstall --config /tmp/archinstall-merged.json --silent
echo ">> Copiando arch-setup al sistema nuevo y entrando en chroot..."
cp -r "$REPO_DIR" /mnt/root/arch-setup
arch-chroot /mnt env \
SETUP_PHASE=chroot INSTALL_USER="$INSTALL_USER" GITEA_TOKEN="$GITEA_TOKEN" \
GIT_NAME="$GIT_NAME" GIT_EMAIL="$GIT_EMAIL" \
bash "/root/arch-setup/$DE/setup.sh"
echo ">> Listo. Quita el USB y reinicia: reboot"
exit 0
fi
# ---------------------------------------------------------------------------
# FASE 2 (dentro del chroot)
# ---------------------------------------------------------------------------
: "${INSTALL_USER:?falta INSTALL_USER}"
source "$(dirname "${BASH_SOURCE[0]}")/../shared/packages-common.sh"
echo ">> Creando usuario $INSTALL_USER..."
id "$INSTALL_USER" &>/dev/null || useradd -m -G wheel -s /bin/zsh "$INSTALL_USER"
echo "Contraseña para $INSTALL_USER:"; passwd "$INSTALL_USER"
# wheel con sudo; NOPASSWD temporal para que paru/makepkg no se cuelguen
echo '%wheel ALL=(ALL:ALL) ALL' >/etc/sudoers.d/10-wheel
echo "$INSTALL_USER ALL=(ALL) NOPASSWD: ALL" >/etc/sudoers.d/99-setup-nopasswd
echo ">> GNOME mínimo + apps oficiales (pacman)..."
# GNOME curado en vez del grupo 'gnome' completo (que mete Maps, Weather, Music,
# Tour, Web/Epiphany, Contacts, Rygel...). archinstall solo instala la base;
# el escritorio y los drivers gráficos open-source los ponemos aquí.
install_pacman "${PACMAN_COMMON[@]}" \
gnome-shell gdm gnome-control-center gnome-session gnome-keyring \
xdg-desktop-portal-gnome xdg-user-dirs-gtk gvfs \
nautilus gnome-console gnome-text-editor gnome-calculator loupe \
gnome-tweaks gnome-themes-extra gnome-shell-extension-appindicator \
gnome-backgrounds power-profiles-daemon \
mesa vulkan-icd-loader vulkan-radeon vulkan-intel intel-media-driver
echo ">> Bootloader (GRUB)..."
install_bootloader
echo ">> Bootstrapeando paru y paquetes AUR..."
bootstrap_paru
install_aur "${AUR_COMMON[@]}" \
catppuccin-gtk-theme-mocha \
gnome-shell-extension-dash-to-dock \
gnome-shell-extension-caffeine \
gnome-shell-extension-blur-my-shell \
gnome-shell-extension-clipboard-indicator \
gnome-shell-extension-vitals
install_helpwire
install_claude_code
echo ">> Servicios..."
systemctl enable gdm
systemctl enable NetworkManager
systemctl enable bluetooth
systemctl enable "syncthing@$INSTALL_USER.service"
systemctl enable tailscaled
systemctl enable power-profiles-daemon
systemctl enable cpupower
# --- Aspecto: se aplica en el PRIMER login gráfico ------------------------
# ponytail: dconf/gsettings necesitan sesión D-Bus, que no existe en chroot.
# Un oneshot de autostart lo aplica al primer login y se autodesactiva.
USER_HOME="/home/$INSTALL_USER"
install -d -o "$INSTALL_USER" -g "$INSTALL_USER" "$USER_HOME/.local/bin" "$USER_HOME/.config/autostart"
cat >"$USER_HOME/.local/bin/firstboot-theme.sh" <<'EOS'
#!/usr/bin/env bash
set -e
# Teclado: US internacional con teclas muertas
gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us+intl')]"
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
gsettings set org.gnome.desktop.interface gtk-theme 'catppuccin-mocha-blue-standard+default'
gsettings set org.gnome.desktop.interface icon-theme 'Papirus-Dark'
gsettings set org.gnome.desktop.interface font-name 'JetBrainsMono Nerd Font 11'
gsettings set org.gnome.desktop.interface monospace-font-name 'JetBrainsMono Nerd Font Mono 12'
for e in \
dash-to-dock@micxgx.gmail.com \
appindicatorsupport@rgcjonas.gmail.com \
blur-my-shell@aunetx \
caffeine@patapon.info \
clipboard-indicator@tudmotu.com \
Vitals@CoreCoding.com ; do
gnome-extensions enable "$e" 2>/dev/null || true
done
# Dock estilo macOS abajo
D=org.gnome.shell.extensions.dash-to-dock
gsettings set $D dock-position 'BOTTOM' 2>/dev/null || true
gsettings set $D extend-height false 2>/dev/null || true
gsettings set $D dock-fixed false 2>/dev/null || true
# autodesactivarse
rm -f "$HOME/.config/autostart/firstboot-theme.desktop"
EOS
chmod +x "$USER_HOME/.local/bin/firstboot-theme.sh"
cat >"$USER_HOME/.config/autostart/firstboot-theme.desktop" <<EOS
[Desktop Entry]
Type=Application
Name=First boot theme (GNOME)
Exec=$USER_HOME/.local/bin/firstboot-theme.sh
X-GNOME-Autostart-enabled=true
EOS
chown -R "$INSTALL_USER:$INSTALL_USER" "$USER_HOME/.local" "$USER_HOME/.config"
# --- chezmoi: dotfiles personales (repo arch-configs) --------------------------
# chezmoi ya se instaló arriba (PACMAN_COMMON, repos oficiales desde 2.x).
echo ">> chezmoi init --apply (repo arch-configs)..."
REMOTE="$GITEA/arch-configs.git"
[[ -n "${GITEA_TOKEN:-}" ]] && REMOTE="https://$INSTALL_USER:$GITEA_TOKEN@gitea.josevi.ovh/josevi/arch-configs.git"
as_user "export GIT_NAME='$GIT_NAME' GIT_EMAIL='$GIT_EMAIL'; \
chezmoi init --apply '$REMOTE'"
# quitar el NOPASSWD temporal
rm -f /etc/sudoers.d/99-setup-nopasswd
echo ">> Configuración de GNOME completada."
+2 -1
View File
@@ -4,7 +4,8 @@
"bootloader": "Grub", "bootloader": "Grub",
"hostname": "arch-kde", "hostname": "arch-kde",
"kernels": [ "kernels": [
"linux" "linux",
"linux-lts"
], ],
"ntp": true, "ntp": true,
"swap": true, "swap": true,
Regular → Executable
+65 -33
View File
@@ -3,11 +3,11 @@
# #
# ./kde/setup.sh # ./kde/setup.sh
# #
# Misma estructura de 2 fases que gnome/setup.sh (ver ese fichero para detalle). # Dos fases en el mismo fichero: SETUP_PHASE=host (USB live) lanza archinstall
# y se relanza dentro del chroot; SETUP_PHASE=chroot instala paquetes/tema/chezmoi.
set -euo pipefail set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DE="kde"
SETUP_PHASE="${SETUP_PHASE:-host}" SETUP_PHASE="${SETUP_PHASE:-host}"
GITEA="https://gitea.josevi.ovh/josevi" GITEA="https://gitea.josevi.ovh/josevi"
@@ -24,7 +24,7 @@ if [[ "$SETUP_PHASE" == "host" ]]; then
echo ">> Generando disk_config dimensionado a los discos reales..." echo ">> Generando disk_config dimensionado a los discos reales..."
# archinstall 4.x quitó "Percent": el layout se calcula aquí (ver shared/gen-disk-config.py) # archinstall 4.x quitó "Percent": el layout se calcula aquí (ver shared/gen-disk-config.py)
python3 "$REPO_DIR/shared/gen-disk-config.py" "$REPO_DIR/$DE/archinstall-config.json" >/tmp/archinstall-merged.json python3 "$REPO_DIR/shared/gen-disk-config.py" "$REPO_DIR/kde/archinstall-config.json" >/tmp/archinstall-merged.json
echo ">> Lanzando archinstall (instala base + KDE en /mnt)..." echo ">> Lanzando archinstall (instala base + KDE en /mnt)..."
archinstall --config /tmp/archinstall-merged.json --silent archinstall --config /tmp/archinstall-merged.json --silent
@@ -34,7 +34,7 @@ if [[ "$SETUP_PHASE" == "host" ]]; then
arch-chroot /mnt env \ arch-chroot /mnt env \
SETUP_PHASE=chroot INSTALL_USER="$INSTALL_USER" GITEA_TOKEN="$GITEA_TOKEN" \ SETUP_PHASE=chroot INSTALL_USER="$INSTALL_USER" GITEA_TOKEN="$GITEA_TOKEN" \
GIT_NAME="$GIT_NAME" GIT_EMAIL="$GIT_EMAIL" \ GIT_NAME="$GIT_NAME" GIT_EMAIL="$GIT_EMAIL" \
bash "/root/arch-setup/$DE/setup.sh" bash "/root/arch-setup/kde/setup.sh"
echo ">> Listo. Quita el USB y reinicia: reboot" echo ">> Listo. Quita el USB y reinicia: reboot"
exit 0 exit 0
@@ -63,12 +63,13 @@ echo ">> KDE Plasma mínimo + apps oficiales (pacman)..."
# grupo 'plasma' completo se evita por el resto del bloat. # grupo 'plasma' completo se evita por el resto del bloat.
install_pacman "${PACMAN_COMMON[@]}" \ install_pacman "${PACMAN_COMMON[@]}" \
plasma-desktop sddm sddm-kcm plasma-pa plasma-nm bluedevil powerdevil kscreen systemsettings kinfocenter \ plasma-desktop sddm sddm-kcm plasma-pa plasma-nm bluedevil powerdevil kscreen systemsettings kinfocenter \
kwallet-pam xdg-desktop-portal-kde breeze-gtk kde-gtk-config kvantum appmenu-gtk-module \ kwallet-pam ksshaskpass xdg-desktop-portal-kde breeze-gtk kde-gtk-config kvantum appmenu-gtk-module \
konsole dolphin kate ark spectacle kcalc gwenview okular kdeconnect kdegraphics-thumbnailers \ konsole dolphin kate ark spectacle kcalc gwenview okular kdeconnect kdegraphics-thumbnailers \
plasma-systemmonitor \ plasma-systemmonitor \
adwaita-fonts \ adwaita-fonts \
mesa vulkan-icd-loader vulkan-radeon vulkan-intel intel-media-driver packagekit-qt6
# Sin discover/packagekit-qt6: se usa pamac-all (AUR_COMMON) como gestor gráfico. # Sin discover: se usa pamac-all + pamac-tray-plasma-git como gestor gráfico.
# packagekit-qt6 lo requiere pamac-tray-plasma-git.
echo ">> Bootloader (GRUB)..." echo ">> Bootloader (GRUB)..."
install_bootloader install_bootloader
@@ -76,42 +77,73 @@ install_bootloader
echo ">> Bootstrapeando paru y paquetes AUR..." echo ">> Bootstrapeando paru y paquetes AUR..."
bootstrap_paru bootstrap_paru
# Solo plasmoides que EXISTEN en AUR. Los de KDE Store/GitHub (gnome-pager, kMenu, # Solo plasmoides que EXISTEN en AUR. Los de KDE Store/GitHub (gnome-pager, kMenu,
# kvitals, windowtitlereborn, claudeusageswitcher, tt.launchpadPlasma) NO están en # kvitals, windowtitlereborn, commandoutput, codexbar) NO están en AUR: viajan
# AUR: viajan como dotfiles vía chezmoi (~/.local/share/plasma/plasmoids), capturados # como dotfiles vía chezmoi (~/.local/share/plasma/plasmoids), capturados con
# con shared/capture-kde.sh. No los añadas aquí. # shared/capture-kde.sh. No los añadas aquí. (claudeusageswitcher y
# tt.launchpadPlasma viajaron así hasta que se quitaron por no usarse en
# ningún panel real -- auditoría 2026-09-20.)
install_aur "${AUR_COMMON[@]}" \ install_aur "${AUR_COMMON[@]}" \
plasma6-applets-panel-colorizer kwin-effects-better-blur-dx breeze-enhanced \ kwin-effects-glass-git tela-icon-theme \
plasma6-themes-layan-git tela-icon-theme layan-cursor-theme-git \
plasma6-applets-appgrid plasma6-wallpapers-smart-video-wallpaper-reborn pamac-tray-plasma-git \ plasma6-applets-appgrid plasma6-wallpapers-smart-video-wallpaper-reborn pamac-tray-plasma-git \
yin-yang yin-yang kwin-decoration-sierra-breeze-enhanced-git
install_helpwire run_optional "install_helpwire" install_helpwire
install_claude_code run_optional "install_claude_code" install_claude_code
echo ">> Servicios..." run_optional "install_flatpaks" install_flatpaks "${FLATPAK_COMMON[@]}"
systemctl enable sddm
systemctl enable NetworkManager
systemctl enable bluetooth
systemctl enable "syncthing@$INSTALL_USER.service"
systemctl enable tailscaled
systemctl enable power-profiles-daemon
systemctl enable cpupower
# Tema: Layan (global theme) + iconos Tela + cursores Bibata-Modern-Classic enable_services() {
# (bibata-cursor-theme-bin en AUR_COMMON), vía AUR (plasma6-themes-layan-git/ systemctl enable sddm
# tela-icon-theme/layan-cursor-theme-git de arriba, instalan en /usr/share, sin systemctl enable NetworkManager
# copiar binarios en dotfiles; layan-cursor-theme-git se deja instalado como systemctl enable bluetooth
# alternativa aunque el cursor activo sea Bibata). El resto de la systemctl enable "syncthing@$INSTALL_USER.service"
# personalización (rc files, layout de paneles, splash Animated-Abstract-Cube) systemctl enable tailscaled
# se captura del portátil con shared/capture-kde.sh y se replica vía chezmoi. systemctl enable power-profiles-daemon
# Single-monitor como base. systemctl enable cpupower
}
run_optional "enable_services" enable_services
# Tema: look-and-feel propios Vampire-Light/Vampire-dark (ver PROJECT_CONTEXT.md)
# + iconos Tela (tela-icon-theme de arriba) + cursores Bibata-Modern-Classic
# (bibata-cursor-theme-bin en AUR_COMMON), vía AUR, instalan en /usr/share, sin
# copiar binarios en dotfiles. El resto de la personalización (rc files,
# layout de paneles, splash Animated-Abstract-Cube, los propios paquetes
# look-and-feel Vampire-*) se captura del portátil con shared/capture-kde.sh
# y se replica vía chezmoi. Single-monitor como base.
# --- chezmoi: dotfiles personales (repo arch-configs) -------------------------- # --- chezmoi: dotfiles personales (repo arch-configs) --------------------------
# chezmoi ya se instaló arriba (PACMAN_COMMON, repos oficiales desde 2.x). # chezmoi ya se instaló arriba (PACMAN_COMMON, repos oficiales desde 2.x).
echo ">> chezmoi init --apply (repo arch-configs)..." echo ">> chezmoi init --apply (repo arch-configs)..."
REMOTE="$GITEA/arch-configs.git" REMOTE="$GITEA/arch-configs.git"
[[ -n "${GITEA_TOKEN:-}" ]] && REMOTE="https://$INSTALL_USER:$GITEA_TOKEN@gitea.josevi.ovh/josevi/arch-configs.git" [[ -n "${GITEA_TOKEN:-}" ]] && REMOTE="https://$INSTALL_USER:$GITEA_TOKEN@gitea.josevi.ovh/josevi/arch-configs.git"
as_user "export GIT_NAME='$GIT_NAME' GIT_EMAIL='$GIT_EMAIL'; \ chezmoi_init_apply() {
chezmoi init --apply '$REMOTE'" as_user "export GIT_NAME='$GIT_NAME' GIT_EMAIL='$GIT_EMAIL'; \
chezmoi init --apply '$REMOTE'"
}
run_optional "chezmoi init --apply" chezmoi_init_apply
# --- pkgsync: sincronización de paquetes (repo arch-setup) -----------------
run_optional "install_pkgsync" install_pkgsync
# Snapper AL FINAL, no tras install_bootloader: snap-pac crea un snapshot
# pre+post por cada transacción de pacman/paru en cuanto existe el config de
# snapper, y dentro del chroot no hay systemd corriendo para que el timer de
# cleanup limite NUMBER_LIMIT -- ninguna limpieza puede correr hasta el primer
# arranque real. Con snapper configurado antes de las ~30 instalaciones AUR
# (cada una su propia transacción), se acumulan decenas de snapshots sin poda
# posible; en una VM de prueba con disco pequeño esto llenó el disco entero
# (visto en real). Configurándolo al final, ya con el sistema instalado, no se
# snapshotea la propia instalación -- el primer snapshot legítimo será el de
# un 'pacman -Syu' real ya con el sistema arrancado, que es para lo que existe
# el rollback.
echo ">> Configurando rollback de actualizaciones (snapper)..."
run_optional "configure_btrfs_rollback" configure_btrfs_rollback
rm -f /etc/sudoers.d/99-setup-nopasswd rm -f /etc/sudoers.d/99-setup-nopasswd
if ((${#FAILED_STEPS[@]})); then
echo
echo "⚠️ Instalación completada con avisos. Pasos que fallaron y necesitan revisión manual:"
printf ' - %s\n' "${FAILED_STEPS[@]}"
fi
echo ">> Configuración de KDE completada." echo ">> Configuración de KDE completada."
+554
View File
@@ -0,0 +1,554 @@
#!/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"
# Marca que un push falló. pull la revisa al arrancar (con la sesión gráfica
# ya arriba) para avisar aunque el notify-send del propio push, disparado
# durante el apagado con la sesión cayéndose, no llegara a mostrarse. Se
# borra en el primer push que vuelve a tener éxito.
FAIL_MARKER="$HOME/.local/share/pkgsync.failed"
# Token de Gotify (push.josevi.ovh, mismo servidor que usa WUD) para avisos
# de fallo -- llega también sin sesión gráfica, a diferencia de notify-send.
# Fichero local-only, nunca tracked (ni chezmoi ni el repo arch-setup):
# mismo criterio que OVERRIDES_FILE. Si no existe, se omite sin más.
GOTIFY_ENV="$HOME/.config/pkgsync/gotify.env"
# 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 inotify-tools 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)"
{ 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")
rm -rf "$work"
}
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() {
# timeout: notify-send puede colgarse activando por D-Bus el servicio de
# notificaciones si algo va raro en la sesión -- que eso nunca bloquee el
# pull entero (nos pasó una vez: se comió los 60s de TimeoutStartSec del
# servicio systemd y lo mató).
command -v notify-send &>/dev/null && timeout 5 notify-send --app-name=pkgsync "$1" "$2"
}
# Como notify(), pero urgencia "critical" (se queda en pantalla hasta que se
# cierra a mano) y además deja constancia en $FAIL_MARKER: push corre en
# ExecStop durante el apagado, con la sesión/D-Bus ya cayéndose, así que su
# notify-send puede no llegar a mostrarse -- el marcador es la red de
# seguridad que garantiza el aviso en el siguiente pull, ya con la sesión
# gráfica completa.
notify_fail() {
echo "!! $1: $2" >&2
printf '%s -- %s: %s\n' "$(date -Iseconds)" "$1" "$2" >"$FAIL_MARKER"
command -v notify-send &>/dev/null && timeout 5 notify-send --app-name=pkgsync --urgency=critical "$1" "$2"
notify_gotify "$1" "$2"
}
# Best-effort, nunca bloquea ni hace fallar el caller: mismo espíritu que el
# timeout de notify-send de arriba, pero via HTTPS -- llega aunque D-Bus/la
# sesión gráfica ya esté cayéndose (caso ExecStop en apagado) o no haya
# sesión gráfica en absoluto.
notify_gotify() {
[[ -f "$GOTIFY_ENV" ]] || return 0
local GOTIFY_URL GOTIFY_TOKEN
# shellcheck disable=SC1090
source "$GOTIFY_ENV"
[[ -n "${GOTIFY_URL:-}" && -n "${GOTIFY_TOKEN:-}" ]] || return 0
timeout 10 curl -fsS --max-time 8 \
-F "title=$1" -F "message=$2" -F "priority=8" \
"${GOTIFY_URL%/}/message?token=${GOTIFY_TOKEN}" >/dev/null 2>&1
}
# --- 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 [[ ! -f "$GOTIFY_ENV" ]]; then
mkdir -p "$(dirname "$GOTIFY_ENV")"
cat >"$GOTIFY_ENV" <<'EOF'
# Token de aplicación Gotify para los avisos de fallo de pkgsync (push.josevi.ovh).
# Crea una Application nueva en la UI de Gotify y pega su token aquí.
# Fichero local-only: pkgsync nunca lo lee/escribe salvo aquí, no viaja al repo.
#GOTIFY_URL="https://push.josevi.ovh"
#GOTIFY_TOKEN="pon-aqui-el-token"
EOF
chmod 600 "$GOTIFY_ENV"
echo ">> scaffold de $GOTIFY_ENV creado (comentado, edítalo a mano para activar avisos por Gotify)"
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
notify_fail "pkgsync: push falló" "no existe el clon $REPO_DIR, corre 'pkgsync bootstrap'"
return 0
fi
local work
work="$(mktemp -d)"
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"
local patch_failed=0
patch_array_file "$pc" PACMAN_COMMON "$remove_pacman" "$add_pacman" || patch_failed=1
patch_array_file "$pc" AUR_COMMON "$remove_aur" "$add_aur" || patch_failed=1
patch_array_file "$pc" FLATPAK_COMMON "$remove_flatpak" "$add_flatpak" || patch_failed=1
rm -rf "$work"
# No commitear nunca un parcheo a medias/fallido -- así fue como el fichero
# acabó vacío en origin/main una vez (2026-08-27): el parcheo falló pero el
# script siguió y commiteó igualmente lo que hubiera en disco. El fichero
# de trabajo no se toca cuando patch_array_file falla (solo escribe si
# encuentra el array), así que abortar aquí es seguro: el próximo intento
# vuelve a partir del mismo estado.
if (( patch_failed )); then
notify_fail "pkgsync: push falló" "no se pudo parchear packages-common.sh (arrays no encontrados), reviso sin commitear -- $LOG"
return 1
fi
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"
rm -f "$FAIL_MARKER"
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"
rm -f "$FAIL_MARKER"
else
notify_fail "pkgsync: push falló" "commit local OK pero 'git 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) ==="
# Red de seguridad: si un push anterior falló (típicamente en ExecStop
# durante el apagado, con la sesión/D-Bus ya cayéndose y su propio
# notify-send sin garantía de mostrarse), avisar aquí sí o sí -- pull corre
# al arrancar sesión, con el escritorio ya completo.
if [[ -f "$FAIL_MARKER" ]]; then
notify_fail "pkgsync: hay un fallo pendiente de revisar" "$(cat "$FAIL_MARKER")"
fi
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")"
if timeout 30 git clone "$REPO_URL" "$REPO_DIR"; then
echo "clon inicial OK"
else
notify_fail "pkgsync: pull falló" "no se pudo clonar $REPO_URL, revisa $LOG"
fi
else
if timeout 30 git -C "$REPO_DIR" pull --ff-only origin main; then
echo "git pull OK"
else
# ls-remote de arriba ya confirmó que hay red -- esto es un conflicto
# real (fast-forward imposible), no un simple "sin conexión".
notify_fail "pkgsync: pull falló" "'git pull --ff-only' falló (conflicto local, revisa $REPO_DIR a mano); 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)"
{ 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 hardcodeados en kde/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)"
rm -rf "$work"
}
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 "$@"
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=pkgsync: trae y compara el estado de paquetes declarado al iniciar sesión (no instala/desinstala nada)
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=%h/.local/bin/pkgsync.sh pull
TimeoutStartSec=60
[Install]
WantedBy=default.target
+14
View File
@@ -0,0 +1,14 @@
[Unit]
Description=pkgsync: captura paquetes instalados y sube el estado a arch-setup al cerrar sesión/apagar
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
ExecStop=%h/.local/bin/pkgsync.sh push
TimeoutStopSec=30
[Install]
WantedBy=default.target
+151 -15
View File
@@ -1,37 +1,75 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Paquetes comunes a GNOME y KDE. Lo sourcea cada <de>/setup.sh. # Paquetes base, sourceados por kde/setup.sh (KDE Plasma es el único DE que
# instala este repo -- ver README "Decisiones").
# AUR-first: lo que está en repos oficiales va por pacman, el resto por paru. # AUR-first: lo que está en repos oficiales va por pacman, el resto por paru.
# --- Repos oficiales (pacman) --------------------------------------------- # --- Repos oficiales (pacman) ---------------------------------------------
# Sincronizado con la instalación real de la máquina maestra. Si un paquete no
# está presente aquí, se elimina del instalador.
PACMAN_COMMON=( PACMAN_COMMON=(
# base / shell / CLI moderno # base / shell / CLI moderno
zsh starship fzf ripgrep bat eza btop zoxide fd tldr fastfetch 7zip vim usbutils zsh starship fzf ripgrep bat eza btop zoxide fd tldr fastfetch 7zip vim usbutils
# dev toolchain (Podman en vez de Docker + capa de compat) # dev toolchain (Podman en vez de Docker + capa de compat)
git base-devel podman podman-docker buildah git base-devel podman podman-docker buildah
nodejs npm python python-pip rustup nodejs npm python python-pip rustup github-cli
# sync de dotfiles (repo arch-configs) -- en repos oficiales desde 2.x, ya no hace falta AUR # sync de dotfiles (repo arch-configs) -- en repos oficiales desde 2.x
chezmoi chezmoi
# apps de escritorio que SÍ están en extra # apps de escritorio que SÍ están en extra
bitwarden dbeaver obsidian gimp syncthing remmina freerdp apostrophe guvcview kvirc vlc vlc-plugin-ffmpeg bitwarden dbeaver gimp syncthing remmina freerdp apostrophe guvcview vlc vlc-plugin-ffmpeg
# diccionarios (corrector ortográfico en apps GTK/Qt) # diccionarios (corrector ortográfico en apps GTK/Qt)
hunspell-en_us hunspell-es_es hunspell-en_us hunspell-es_es
# fuentes / iconos compartidos # fuentes / iconos compartidos
ttf-jetbrains-mono-nerd ttf-firacode-nerd noto-fonts noto-fonts-emoji ttf-jetbrains-mono-nerd ttf-firacode-nerd noto-fonts noto-fonts-emoji
papirus-icon-theme
# red / utilidades base # red / utilidades base
networkmanager flatpak openssh networkmanager-openvpn openvpn tailscale wpa_supplicant networkmanager flatpak openssh networkmanager-openvpn openvpn tailscale wpa_supplicant
# audio (PipeWire reemplaza a PulseAudio) # audio (PipeWire reemplaza a PulseAudio)
pipewire pipewire-alsa pipewire-jack pipewire-pulse wireplumber libpulse pipewire pipewire-alsa pipewire-jack pipewire-pulse wireplumber libpulse
gst-plugin-pipewire gst-plugins-good gst-plugins-bad gst-libav gst-plugin-pipewire gst-plugins-good gst-plugins-bad gst-libav
# bluetooth: stack + daemon comunes. La UI va por DE: GNOME la trae vía # bluetooth: stack + daemon comunes; la UI (bluedevil) se instala explícito
# gnome-control-center (depende de gnome-bluetooth); KDE NO, porque usamos un # en kde/setup.sh.
# Plasma curado, así que bluedevil se instala explícito en kde/setup.sh.
bluez bluez-utils bluez bluez-utils
# hardware: mismo par de máquinas físicas (ver PROJECT_CONTEXT.md) # hardware: mismo par de máquinas físicas (ver PROJECT_CONTEXT.md)
amd-ucode sof-firmware btrfs-progs zram-generator cpupower power-profiles-daemon amd-ucode sof-firmware btrfs-progs zram-generator cpupower power-profiles-daemon
ibus wl-clipboard wmctrl ibus wl-clipboard wmctrl smartmontools
# gvfs backend para montar móviles Android por MTP (Dolphin/Nautilus) # gvfs backend para montar móviles Android por MTP (Dolphin/Nautilus)
gvfs-mtp gvfs-mtp
# kernels: último + LTS como fallback ante actualizaciones rotas
linux linux-lts linux-lts-headers linux-firmware
# rollback de actualizaciones atómicas sobre btrfs. inotify-tools es
# dependencia OPCIONAL de grub-btrfs (pacman --needed no la instala sola)
# pero grub-btrfsd la necesita para arrancar -- sin ella el daemon muere
# con exit code 1 y el menú de GRUB nunca ve los snapshots (visto en real
# en Vampire-HM80: paquetes instalados pero grub-btrfsd en 'failed').
snapper snap-pac grub-btrfs inotify-tools
# bootloader (antes hardcodeado en kde/setup.sh, install_bootloader()
# también los instala explícito -- duplicado inofensivo con --needed, pero
# ahora viven aquí para que pkgsync pueda declararlos/protegerlos)
grub efibootmgr
# drivers gráficos open-source (antes hardcodeados en kde/setup.sh;
# mismo hardware físico en ambas máquinas, ver PROJECT_CONTEXT.md)
mesa vulkan-icd-loader vulkan-radeon vulkan-intel intel-media-driver
base
podman-compose
sudo
telegram-desktop
nano
android-tools
lzip
waydroid
glab
opencode
sshfs
isoimagewriter
)
# --- Flatpak ---------------------------------------------------------------
# Aplicaciones que en la máquina maestra están instaladas como Flatpak.
# Antes algunas de estas vivían en AUR (p.ej. heroic-games-launcher-bin,
# karere); ahora se gestionan desde Flathub.
FLATPAK_COMMON=(
com.heroicgameslauncher.hgl
io.github.tobagin.karere
org.gnome.gitlab.cheywood.Buffer
) )
# --- AUR (paru) ------------------------------------------------------------ # --- AUR (paru) ------------------------------------------------------------
@@ -41,24 +79,38 @@ PACMAN_COMMON=(
# con el instalador nativo oficial, función install_claude_code() más abajo. # con el instalador nativo oficial, función install_claude_code() más abajo.
AUR_COMMON=( AUR_COMMON=(
zen-browser-bin zen-browser-bin
visual-studio-code-bin
vscodium-bin vscodium-bin
davmail
betterbird-bin betterbird-bin
onlyoffice-bin onlyoffice-bin
spotify
ungoogled-chromium-bin ungoogled-chromium-bin
karere
ventoy-bin ventoy-bin
woeusb-ng woeusb-ng
bibata-cursor-theme-bin bibata-cursor-theme-bin
googledot-cursor-theme
pamac-all pamac-all
yay yay
heroic-games-launcher-bin # herramientas adicionales presentes en la máquina maestra
autofirma debtap gotify-desktop stably-orca-bin
codexbar-cli
metapac
paru
spotifast-bin
) )
# --- helpers usados por los setup.sh -------------------------------------- # --- helpers usados por los setup.sh --------------------------------------
# Ejecuta un paso NO crítico: si falla, lo registra en FAILED_STEPS y
# CONTINÚA en vez de abortar todo el script (set -e no debe tirar una
# instalación de 1h por un mirror caído o un D-Bus que falta en el chroot).
FAILED_STEPS=()
run_optional() {
local desc="$1"; shift
echo ">> $desc"
if ! "$@"; then
FAILED_STEPS+=("$desc")
echo "!! Falló (no crítico, revisa a mano luego): $desc" >&2
fi
}
install_pacman() { pacman -S --needed --noconfirm "$@"; } install_pacman() { pacman -S --needed --noconfirm "$@"; }
# paru se construye como usuario normal (makepkg no corre como root). # paru se construye como usuario normal (makepkg no corre como root).
@@ -128,3 +180,87 @@ install_helpwire() {
install_claude_code() { install_claude_code() {
as_user "curl -fsSL https://claude.ai/install.sh | bash" as_user "curl -fsSL https://claude.ai/install.sh | bash"
} }
# Instala aplicaciones desde Flathub. Se usa para apps que en la máquina
# maestra están como Flatpak, aunque antes estuvieran en AUR.
install_flatpaks() {
echo ">> Instalando Flatpaks comunes..."
if ! command -v flatpak &>/dev/null; then
echo "!! flatpak no está disponible; omitiendo Flatpaks." >&2
return 0
fi
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
local app failed=()
for app in "$@"; do
flatpak install -y --noninteractive flathub "$app" || failed+=("$app")
done
((${#failed[@]})) && echo "!! Flatpak falló (revisa a mano): ${failed[*]}" >&2
return 0
}
# pkgsync: sincroniza qué paquetes hay instalados (pacman+AUR+flatpak) entre
# las dos máquinas, igual que chezmoi sincroniza los dotfiles. Instala metapac
# (motor de diff/aplicación), despliega pkgsync.sh y sus dos servicios de
# usuario, y deja el group file de metapac listo. Ver pkgsync/ en este repo.
install_pkgsync() {
echo ">> Instalando pkgsync (metapac + servicios de sincronización de paquetes)..."
install_aur metapac
as_user "mkdir -p ~/.local/bin ~/.config/systemd/user ~/.config/metapac/groups"
as_user "cp '$REPO_DIR/pkgsync/pkgsync.sh' ~/.local/bin/pkgsync.sh && chmod +x ~/.local/bin/pkgsync.sh"
as_user "cp '$REPO_DIR/pkgsync/systemd/'*.service ~/.config/systemd/user/"
as_user "~/.local/bin/pkgsync.sh bootstrap"
as_user "systemctl --user enable pkgsync-push.service pkgsync-pull.service"
# ya lo activa el run_once de chezmoi, pero por si este DE se instala algún
# día sin chezmoi: idempotente, no hace nada si ya está activo.
loginctl enable-linger "$INSTALL_USER" 2>/dev/null || true
}
# Configura snapper + snap-pac + grub-btrfs para rollback de actualizaciones.
# snap-pac crea snapshots automaticos antes/después de cada 'pacman -Syu'.
# grub-btrfs añade las entradas de snapshots al menú de GRUB.
configure_btrfs_rollback() {
echo ">> Configurando rollback btrfs (snapper + snap-pac + grub-btrfs)..."
# Asegurar que / es btrfs; si no, no hacemos nada.
if ! findmnt -nro FSTYPE / | grep -q btrfs; then
echo "!! Advertencia: / no es btrfs; omitiendo configuración de snapper." >&2
return 0
fi
# Crear configuración de snapper para / si no existe.
if [[ ! -f /etc/snapper/configs/root ]]; then
snapper --no-dbus -c root create-config /
fi
# Solo queremos snapshots automáticos de pacman (snap-pac), no por tiempo.
# Esto evita que se acumulen snapshots innecesarios y se coma espacio.
# ALLOW_GROUPS=wheel: snapper tiene su propia lista de permitidos aparte de
# los permisos de fichero de /.snapshots -- sin esto, "snapper -c root
# list" da "Sin permisos" para cualquiera que no sea root, aunque el chmod
# de abajo esté bien (visto en real en Vampire-HM80).
snapper --no-dbus -c root set-config \
TIMELINE_CREATE=no \
TIMELINE_LIMIT_HOURLY=0 \
TIMELINE_LIMIT_DAILY=0 \
TIMELINE_LIMIT_WEEKLY=0 \
TIMELINE_LIMIT_MONTHLY=0 \
TIMELINE_LIMIT_YEARLY=0 \
NUMBER_LIMIT=10 \
NUMBER_LIMIT_IMPORTANT=10 \
ALLOW_GROUPS=wheel
# Permisos del directorio de snapshots en sí (necesario además de
# ALLOW_GROUPS de arriba, snapper comprueba las dos cosas).
chmod 750 /.snapshots
chown :wheel /.snapshots 2>/dev/null || true
# Habilitar el daemon que regenera el menú de GRUB cuando cambian los snapshots.
systemctl enable grub-btrfsd
# Regenerar menú de GRUB con las entradas de snapshots.
grub-mkconfig -o /boot/grub/grub.cfg
}