Compare commits
56
Commits
9bf3fda9a6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c353e7287 | ||
|
|
9ad957f4a0 | ||
|
|
2c288e1aec | ||
|
|
da076af6ad | ||
|
|
9a9f6d9931 | ||
|
|
923da6d375 | ||
|
|
f86e424491 | ||
|
|
a8b4e3f51d | ||
|
|
226b439684 | ||
|
|
68e30e9feb | ||
|
|
5b33bd26f9 | ||
|
|
7a8693de8a | ||
|
|
df9cf52acf | ||
|
|
4f2c3e937b | ||
|
|
56c924d0a6 | ||
|
|
1994f7b030 | ||
|
|
56742247ce | ||
|
|
64bb53eadf | ||
|
|
598e8b52c8 | ||
|
|
8575c4ffa3 | ||
|
|
f3cdc3525d | ||
|
|
25beb525bc | ||
|
|
179affc994 | ||
|
|
3a95ae12e9 | ||
|
|
f190e739f9 | ||
|
|
d891ca5dd4 | ||
|
|
a2ae1da768 | ||
|
|
5fda5aaaf6 | ||
|
|
9f0d0f1179 | ||
|
|
8d1a839578 | ||
|
|
d411593149 | ||
|
|
dc30383bf7 | ||
|
|
41d64de461 | ||
|
|
b740ed5db5 | ||
|
|
d1077f0547 | ||
|
|
c074095d68 | ||
|
|
f19b6f0f93 | ||
|
|
f87c2c08ce | ||
|
|
e8e5b2060b | ||
|
|
4cb89eb536 | ||
|
|
0ce0deb8ec | ||
|
|
8d5f0f1546 | ||
|
|
cf63f01ecd | ||
|
|
2761070876 | ||
|
|
178768a5ad | ||
|
|
5400813069 | ||
|
|
0440b7e093 | ||
|
|
e5e38551a3 | ||
|
|
37a88eeb2a | ||
|
|
1ee91d59c2 | ||
|
|
f5f50db6e6 | ||
|
|
45dd2e34e2 | ||
|
|
72a8f4a3b9 | ||
|
|
a535a126b5 | ||
|
|
221228d901 | ||
|
|
092c8c4c67 |
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(sed -i 's#/configs\\\\.git#/arch-configs.git#g; s#repo configs#repo arch-configs#g' gnome/setup.sh kde/setup.sh)",
|
||||
"Bash(sed -i 's#josevi/configs#josevi/arch-configs#g; s#repo `configs`#repo `arch-configs`#g; s#`configs/`#`arch-configs/`#g; s#`configs`#`arch-configs`#g; s#clonar `configs`#clonar `arch-configs`#g' README.md INSTALL.md)",
|
||||
"Bash(git commit -m ' *)",
|
||||
"Bash(git push *)",
|
||||
"Bash(command -v starship)",
|
||||
"Bash(echo \"exit: $?\")",
|
||||
"Bash(fc-list)",
|
||||
"Read(//home/VampireJSV/**)",
|
||||
"Read(//home/linuxbrew/.linuxbrew/bin/**)",
|
||||
"Read(//home/VampireJSV/.local/bin/**)",
|
||||
"Read(//home/VampireJSV/.config/**)",
|
||||
"Bash(chezmoi managed *)",
|
||||
"Bash(chezmoi source-path *)",
|
||||
"Bash(chezmoi status *)",
|
||||
"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 *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
+10
-7
@@ -1,7 +1,7 @@
|
||||
# Procedimiento de instalación
|
||||
|
||||
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)
|
||||
|
||||
@@ -29,8 +29,11 @@ Boot desde el USB. Entras como `root` automáticamente.
|
||||
lsblk
|
||||
```
|
||||
|
||||
Confirma que el NVMe es `nvme0n1` y el SSD SATA de 1TB es `sda`. Si no, edita los
|
||||
JSON antes de seguir.
|
||||
Los discos se autodetectan por NO removible (`RM=0`): el USB instalador (`sdb`)
|
||||
queda fuera y da igual si el disco de sistema es `nvme0n1` o un SSD PCIe que sale
|
||||
como `sda`. Confirma con `lsblk` que solo tus discos reales tienen `RM` a `0`. El
|
||||
1º (nvme si existe) es el de sistema, el 2º (si hay) va a `/home`. Para forzarlo:
|
||||
`gen-disk-config.py BASE.json /dev/SISTEMA [/dev/HOME]`.
|
||||
|
||||
## 4. Clonar y lanzar
|
||||
|
||||
@@ -38,7 +41,7 @@ JSON antes de seguir.
|
||||
pacman -Sy --noconfirm git
|
||||
git clone https://gitea.josevi.ovh/josevi/arch-setup
|
||||
cd arch-setup
|
||||
./gnome/setup.sh # o ./kde/setup.sh
|
||||
./kde/setup.sh
|
||||
```
|
||||
|
||||
## 5. Responder los prompts
|
||||
@@ -57,9 +60,9 @@ y **email** de git, y la **contraseña** del usuario. A partir de ahí va solo:
|
||||
reboot
|
||||
```
|
||||
|
||||
Quita el USB. En el **primer login gráfico** se aplica el tema Catppuccin,
|
||||
iconos, fuentes, dock (GNOME) y el teclado `us-intl`. Ese oneshot se
|
||||
autodestruye; el segundo login arranca limpio.
|
||||
Quita el USB. El tema (Layan + iconos Tela + cursores Layan-white) y el resto
|
||||
del look ya vienen aplicados por `kde/setup.sh` y chezmoi; solo falta el
|
||||
primer login gráfico normal.
|
||||
|
||||
## 7. Segunda máquina
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
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.
|
||||
Dos variantes de escritorio: **GNOME** y **KDE Plasma**.
|
||||
Escritorio: **KDE Plasma** (único, sin GNOME — ver "Decisiones").
|
||||
|
||||
```
|
||||
arch-setup/
|
||||
├── gnome/ archinstall-config.json + setup.sh (GNOME, gdm)
|
||||
├── kde/ archinstall-config.json + setup.sh (KDE Plasma, sddm)
|
||||
├── shared/ packages-common.sh (paquetes comunes a ambos)
|
||||
├── shared/ packages-common.sh (paquetes base)
|
||||
├── pkgsync/ pkgsync.sh + systemd/ (sync de paquetes entre máquinas)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
@@ -20,14 +20,14 @@ arch-setup/
|
||||
pacman -Sy --noconfirm git
|
||||
git clone https://gitea.josevi.ovh/josevi/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`
|
||||
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
|
||||
en `/mnt`.
|
||||
1. `archinstall --silent` con `kde/archinstall-config.json` → instala base +
|
||||
KDE Plasma en `/mnt`.
|
||||
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
|
||||
`paru`), aplica el aspecto y ejecuta `chezmoi init --apply` apuntando a
|
||||
@@ -40,20 +40,45 @@ privado), nombre y email de git. Después, sin más intervención:
|
||||
`paru` (se bootstrapea solo). Sin Flatpak salvo que lo añadas.
|
||||
- **Contenedores**: Podman + `podman-docker` (capa de compat), no Docker.
|
||||
- **Navegador**: Zen Browser. También ungoogled-chromium.
|
||||
- **Aspecto**: Catppuccin Mocha + iconos Papirus-Dark + JetBrains Mono Nerd en
|
||||
ambos escritorios (coherencia GNOME/KDE). GNOME añade Dash-to-Dock y
|
||||
extensiones (AppIndicator, Blur my Shell, Caffeine, Clipboard Indicator, Vitals).
|
||||
- **Dotfiles**: `chezmoi` apuntando a un repo separado (`arch-configs`).
|
||||
- **Aspecto**: JetBrains Mono Nerd + tema global Layan + iconos Tela + cursores
|
||||
Layan-white (paquetes AUR, ver `kde/setup.sh`) + splash Animated-Abstract-Cube;
|
||||
look capturado del portátil con `shared/capture-kde.sh` y aplicado vía chezmoi.
|
||||
- **Solo KDE Plasma, sin GNOME**: hubo variante GNOME en su día; se retiró
|
||||
porque en el uso real KDE es lo único que se usa. Si algún día hiciera falta
|
||||
recuperarla, está en el historial de git (commit anterior a la retirada).
|
||||
- **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
|
||||
|
||||
- **Se borran DOS discos** (`"wipe": true` en ambos). Recrea el esquema actual:
|
||||
- `/dev/nvme0n1` → ESP 512M (`/boot/efi`) + `/boot` ext4 1G + `/` btrfs (resto)
|
||||
- `/dev/sda` → ext4 todo el disco en `/home` (**ojo: esto destruye el HDD de datos**)
|
||||
- **Se borran los discos FIJOS detectados** (`"wipe": true`). Los discos se
|
||||
autodetectan filtrando por NO removible (`RM=0` en `lsblk`), así el USB
|
||||
instalador (`sdb`, removible) queda fuera y da igual que el SSD sea `nvme0n1`
|
||||
o un SSD PCIe que enumera como `sda`. Esquema:
|
||||
- **Disco de sistema** (1º; nvme si existe) → ESP 512M (`/boot/efi`) + `/boot`
|
||||
ext4 1G + `/` btrfs (resto)
|
||||
- **2º disco fijo** (si hay) → ext4 todo el disco en `/home` (**ojo: destruye
|
||||
el disco de datos**). Si solo hay 1 disco, `/home` vive dentro del `/` btrfs.
|
||||
- swap = zram (`"swap": true`), sin partición de swap
|
||||
- bootloader **GRUB** (coherente con ESP en `/boot/efi` + `/boot` separado)
|
||||
|
||||
Verifica los nombres con `lsblk`; si tus discos no son `nvme0n1`/`sda`, edítalos.
|
||||
Verifica con `lsblk` qué discos quedan como fijos. Para forzar nombres a mano:
|
||||
`gen-disk-config.py BASE.json /dev/SISTEMA [/dev/HOME]`.
|
||||
- **Esquema de particiones de archinstall** cambia entre versiones, y el
|
||||
particionado manual multi-disco es lo más sensible. Si archinstall se queja del
|
||||
`disk_config`, abre `archinstall` interactivo, define los discos a mano, usa
|
||||
@@ -64,6 +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,
|
||||
Podman, nodejs/python/rustup, Bitwarden, DBeaver, Obsidian, GIMP, Syncthing,
|
||||
Remmina+FreeRDP, Apostrophe, Zen Browser, VSCode, DavMail, Betterbird, OnlyOffice,
|
||||
Spotify, ungoogled-chromium, Input Remapper, HelpWire (vía `.deb`→`debtap`).
|
||||
GNOME añade gnome-calculator/tweaks; KDE añade kcalc/ark/spectacle.
|
||||
Remmina+FreeRDP, Apostrophe, guvcview, KVIrc, Zen Browser, VSCode,
|
||||
Betterbird, OnlyOffice, Spotify, ungoogled-chromium, Karere, HelpWire (vía
|
||||
`.deb`→`debtap`). KDE añade 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.
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
-144
@@ -1,144 +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
|
||||
|
||||
echo ">> Servicios..."
|
||||
systemctl enable gdm
|
||||
systemctl enable NetworkManager
|
||||
systemctl enable "syncthing@$INSTALL_USER.service"
|
||||
|
||||
# --- 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) --------------------------
|
||||
echo ">> chezmoi init --apply (repo arch-configs)..."
|
||||
install_aur chezmoi
|
||||
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."
|
||||
@@ -4,7 +4,8 @@
|
||||
"bootloader": "Grub",
|
||||
"hostname": "arch-kde",
|
||||
"kernels": [
|
||||
"linux"
|
||||
"linux",
|
||||
"linux-lts"
|
||||
],
|
||||
"ntp": true,
|
||||
"swap": true,
|
||||
|
||||
+78
-73
@@ -3,11 +3,11 @@
|
||||
#
|
||||
# ./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
|
||||
|
||||
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DE="kde"
|
||||
SETUP_PHASE="${SETUP_PHASE:-host}"
|
||||
GITEA="https://gitea.josevi.ovh/josevi"
|
||||
|
||||
@@ -24,7 +24,7 @@ if [[ "$SETUP_PHASE" == "host" ]]; then
|
||||
|
||||
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
|
||||
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)..."
|
||||
archinstall --config /tmp/archinstall-merged.json --silent
|
||||
@@ -34,7 +34,7 @@ if [[ "$SETUP_PHASE" == "host" ]]; then
|
||||
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"
|
||||
bash "/root/arch-setup/kde/setup.sh"
|
||||
|
||||
echo ">> Listo. Quita el USB y reinicia: reboot"
|
||||
exit 0
|
||||
@@ -44,6 +44,9 @@ fi
|
||||
# FASE 2 (dentro del chroot)
|
||||
# ---------------------------------------------------------------------------
|
||||
: "${INSTALL_USER:?falta INSTALL_USER}"
|
||||
# Log de toda la fase chroot (pacman/paru/chezmoi) dentro del sistema nuevo, así
|
||||
# sobrevive al reinicio. archinstall ya tiene su propio log aparte.
|
||||
exec > >(tee -a /var/log/arch-setup.log) 2>&1
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/../shared/packages-common.sh"
|
||||
|
||||
echo ">> Creando usuario $INSTALL_USER..."
|
||||
@@ -54,91 +57,93 @@ echo "$INSTALL_USER ALL=(ALL) NOPASSWD: ALL" >/etc/sudoers.d/99-setup-nopasswd
|
||||
|
||||
echo ">> KDE Plasma mínimo + apps oficiales (pacman)..."
|
||||
# Plasma curado en vez del grupo 'plasma' completo (que mete discover, kwrited,
|
||||
# khelpcenter, plasma-systemmonitor, drkonqi, juegos kde, etc.). archinstall solo
|
||||
# instala la base; el escritorio y los drivers gráficos open-source van aquí.
|
||||
# khelpcenter, drkonqi, juegos kde, etc.). archinstall solo instala la base;
|
||||
# el escritorio y los drivers gráficos open-source van aquí.
|
||||
# plasma-systemmonitor SÍ se instala explícito (se usa a diario), pese a que el
|
||||
# grupo 'plasma' completo se evita por el resto del bloat.
|
||||
install_pacman "${PACMAN_COMMON[@]}" \
|
||||
plasma-desktop sddm plasma-pa plasma-nm powerdevil kscreen systemsettings \
|
||||
kwallet-pam xdg-desktop-portal-kde breeze-gtk kde-gtk-config \
|
||||
konsole dolphin kate ark spectacle kcalc gwenview okular \
|
||||
mesa vulkan-icd-loader vulkan-radeon vulkan-intel intel-media-driver
|
||||
plasma-desktop sddm sddm-kcm plasma-pa plasma-nm bluedevil powerdevil kscreen systemsettings kinfocenter \
|
||||
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 \
|
||||
plasma-systemmonitor \
|
||||
adwaita-fonts \
|
||||
packagekit-qt6
|
||||
# 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)..."
|
||||
install_bootloader
|
||||
|
||||
echo ">> Bootstrapeando paru y paquetes AUR..."
|
||||
bootstrap_paru
|
||||
install_aur "${AUR_COMMON[@]}" catppuccin-gtk-theme-mocha
|
||||
install_helpwire
|
||||
# Solo plasmoides que EXISTEN en AUR. Los de KDE Store/GitHub (gnome-pager, kMenu,
|
||||
# kvitals, windowtitlereborn, commandoutput, codexbar) NO están en AUR: viajan
|
||||
# como dotfiles vía chezmoi (~/.local/share/plasma/plasmoids), capturados con
|
||||
# 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[@]}" \
|
||||
kwin-effects-glass-git tela-icon-theme \
|
||||
plasma6-applets-appgrid plasma6-wallpapers-smart-video-wallpaper-reborn pamac-tray-plasma-git \
|
||||
yin-yang kwin-decoration-sierra-breeze-enhanced-git
|
||||
run_optional "install_helpwire" install_helpwire
|
||||
run_optional "install_claude_code" install_claude_code
|
||||
|
||||
echo ">> Servicios..."
|
||||
systemctl enable sddm
|
||||
systemctl enable NetworkManager
|
||||
systemctl enable "syncthing@$INSTALL_USER.service"
|
||||
run_optional "install_flatpaks" install_flatpaks "${FLATPAK_COMMON[@]}"
|
||||
|
||||
# --- Aspecto: se aplica en el PRIMER login gráfico ------------------------
|
||||
# ponytail: el global theme de Plasma necesita sesión; lo aplica un oneshot
|
||||
# de autostart. Base Breeze Dark + esquema de color Catppuccin Mocha + Papirus.
|
||||
# upgrade path: layer del look-and-feel completo de catppuccin/kde si lo quieres.
|
||||
USER_HOME="/home/$INSTALL_USER"
|
||||
install -d -o "$INSTALL_USER" -g "$INSTALL_USER" \
|
||||
"$USER_HOME/.local/bin" "$USER_HOME/.config/autostart" \
|
||||
"$USER_HOME/.local/share/color-schemes"
|
||||
enable_services() {
|
||||
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
|
||||
}
|
||||
run_optional "enable_services" enable_services
|
||||
|
||||
cat >"$USER_HOME/.local/bin/firstboot-theme.sh" <<'EOS'
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
# El esquema CatppuccinMochaBlue.colors lo provee chezmoi (~/.local/share/color-schemes)
|
||||
# Teclado: US internacional con teclas muertas
|
||||
kwriteconfig6 --file kxkbrc --group Layout --key LayoutList "us"
|
||||
kwriteconfig6 --file kxkbrc --group Layout --key VariantList "intl"
|
||||
kwriteconfig6 --file kxkbrc --group Layout --key Use "true"
|
||||
plasma-apply-colorscheme CatppuccinMochaBlue 2>/dev/null || true
|
||||
plasma-apply-desktoptheme breeze-dark 2>/dev/null || true
|
||||
plasma-changeicons Papirus-Dark 2>/dev/null || true
|
||||
# Apps GTK (GIMP, etc.) con tema Catppuccin + iconos Papirus
|
||||
for v in 3.0 4.0; do
|
||||
mkdir -p "$HOME/.config/gtk-$v"
|
||||
cat >"$HOME/.config/gtk-$v/settings.ini" <<INI
|
||||
[Settings]
|
||||
gtk-theme-name=catppuccin-mocha-blue-standard+default
|
||||
gtk-icon-theme-name=Papirus-Dark
|
||||
gtk-application-prefer-dark-theme=true
|
||||
INI
|
||||
done
|
||||
# Fuentes
|
||||
kwriteconfig6 --file kdeglobals --group General --key font "JetBrains Mono Nerd Font,11,-1,5,50,0,0,0,0,0"
|
||||
kwriteconfig6 --file kdeglobals --group General --key fixed "JetBrains Mono Nerd Font Mono,12,-1,5,50,0,0,0,0,0"
|
||||
# El panel por defecto de Plasma clava Discover (no instalado en el setup mínimo)
|
||||
# -> icono en blanco que da error. Fijamos lanzadores limpios en el gestor de tareas.
|
||||
A="$HOME/.config/plasma-org.kde.plasma.desktop-appletsrc"
|
||||
grp=$(grep -B6 "plugin=org.kde.plasma.icontasks" "$A" 2>/dev/null | grep -oE "Containments\]\[[0-9]+\]\[Applets\]\[[0-9]+" | tail -1) || true
|
||||
if [[ -n "$grp" ]]; then
|
||||
c=$(grep -oE "Containments\]\[[0-9]+" <<<"$grp" | grep -oE "[0-9]+")
|
||||
a=$(grep -oE "Applets\]\[[0-9]+" <<<"$grp" | grep -oE "[0-9]+")
|
||||
kwriteconfig6 --file plasma-org.kde.plasma.desktop-appletsrc \
|
||||
--group Containments --group "$c" --group Applets --group "$a" \
|
||||
--group Configuration --group General \
|
||||
--key launchers "applications:org.kde.dolphin.desktop,applications:org.kde.konsole.desktop"
|
||||
fi
|
||||
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 (KDE)
|
||||
Exec=$USER_HOME/.local/bin/firstboot-theme.sh
|
||||
EOS
|
||||
chown -R "$INSTALL_USER:$INSTALL_USER" "$USER_HOME/.local" "$USER_HOME/.config"
|
||||
# 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 ya se instaló arriba (PACMAN_COMMON, repos oficiales desde 2.x).
|
||||
echo ">> chezmoi init --apply (repo arch-configs)..."
|
||||
install_aur chezmoi
|
||||
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() {
|
||||
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
|
||||
|
||||
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."
|
||||
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# Captura la personalización de KDE de ESTA máquina (el portátil, ya a tu gusto)
|
||||
# para replicarla como instalación base: dotfiles -> chezmoi, paquetes -> setup.sh.
|
||||
#
|
||||
# ./shared/capture-kde.sh [DIR_SALIDA] (por defecto: ./kde-capture)
|
||||
#
|
||||
# IMPORTANTE: cierra la sesión de Plasma y ejecútalo desde una TTY (Ctrl+Alt+F3).
|
||||
# Plasma reescribe sus rc al salir/recargar; capturar con la sesión viva pilla
|
||||
# ficheros a medias.
|
||||
set -euo pipefail
|
||||
|
||||
OUT="${1:-$PWD/kde-capture}"
|
||||
CFG="$HOME/.config"
|
||||
SHARE="$HOME/.local/share"
|
||||
|
||||
# Allowlist: solo lo que es portable a otra máquina. Se EXCLUDE a propósito lo
|
||||
# hardware-specific (kscreen = layout de monitores) porque la base es single
|
||||
# monitor y en multimonitor rompe. Ver README.
|
||||
config_files=(
|
||||
kdeglobals # tema, colores, fuentes, widget style
|
||||
kwinrc # decoración, efectos, blur, botones de ventana
|
||||
kxkbrc # teclado (us+intl)
|
||||
plasmarc plasmashellrc
|
||||
plasma-org.kde.plasma.desktop-appletsrc # paneles/dock/widgets (1 pantalla)
|
||||
konsolerc dolphinrc katerc
|
||||
kscreenlockerrc ksplashrc
|
||||
Kvantum # tema Qt (dir)
|
||||
gtk-3.0/settings.ini gtk-4.0/settings.ini # apps GTK
|
||||
)
|
||||
share_dirs=(
|
||||
color-schemes # esquemas de color propios
|
||||
konsole # perfiles de Konsole
|
||||
plasma/look-and-feel # global theme si lo hiciste tú
|
||||
plasma/plasmoids # widgets de KDE Store/GitHub (no están en AUR); el panel
|
||||
# los referencia por id, así que SIN esto quedan huecos vacíos
|
||||
)
|
||||
|
||||
mkdir -p "$OUT/config" "$OUT/share"
|
||||
# cp --parents conserva la ruta relativa; por eso el subshell hace cd al origen.
|
||||
( cd "$CFG" && for f in "${config_files[@]}"; do [[ -e $f ]] && cp -r --parents "$f" "$OUT/config/"; done ) || true
|
||||
( cd "$SHARE" && for d in "${share_dirs[@]}"; do [[ -e $d ]] && cp -r --parents "$d" "$OUT/share/"; done ) || true
|
||||
|
||||
# Paquetes: nativos explícitos y AUR por separado, para meterlos en
|
||||
# packages-common.sh / setup.sh.
|
||||
pacman -Qqen >"$OUT/packages-native.txt" # repos oficiales, instalados a mano
|
||||
pacman -Qqm >"$OUT/packages-aur.txt" # foráneos (AUR)
|
||||
|
||||
cat >"$OUT/README.txt" <<EOF
|
||||
Captura KDE de $(uname -n) el $(date -I).
|
||||
|
||||
config/, share/ -> revisar y meter en arch-configs con: chezmoi add <ruta>
|
||||
OJO share/plasma/plasmoids/: widgets de KDE Store/GitHub que NO están en AUR.
|
||||
Van como dotfiles (chezmoi), NO como paquete. chezmoi los replica en el sistema
|
||||
nuevo y el panel los coge por id. Si un id no casa con el del appletsrc, edita
|
||||
el "Id" del metadata.json del plasmoide para que coincida.
|
||||
packages-native.txt / packages-aur.txt -> diff contra packages-common.sh y
|
||||
kde/setup.sh; añade los que falten.
|
||||
|
||||
EXCLUIDO a propósito (hardware-specific, NO replicar en la base single-monitor):
|
||||
~/.config/kscreen* y ~/.local/share/kscreen/ (layout de monitores)
|
||||
powermanagementprofilesrc puede traer perfiles batería/AC del portátil: revisa.
|
||||
EOF
|
||||
|
||||
echo ">> Capturado en: $OUT"
|
||||
echo " $(find "$OUT/config" "$OUT/share" -type f | wc -l) ficheros de config"
|
||||
echo " nativos: $(wc -l <"$OUT/packages-native.txt") AUR: $(wc -l <"$OUT/packages-aur.txt")"
|
||||
echo " lee $OUT/README.txt"
|
||||
+82
-18
@@ -5,22 +5,41 @@
|
||||
# (250G/1TB): por eso se calcula en tiempo de instalación con los propios modelos
|
||||
# de archinstall (válido ante cambios de schema entre versiones).
|
||||
#
|
||||
# gen-disk-config.py BASE.json [NVME_DEV] [SDA_DEV] > merged.json
|
||||
# gen-disk-config.py BASE.json [SYS_DEV] [HOME_DEV] > merged.json
|
||||
#
|
||||
# Layout (replica el hardware): NVMe = ESP 512M + /boot 1G ext4 + / btrfs (resto);
|
||||
# SDA = /home ext4 (todo). swap = zram (lo pone "swap": true del base).
|
||||
import json, sys
|
||||
# Los discos se autodetectan: no se puede contar discos para decidir el layout
|
||||
# porque el USB instalador tambien aparece como disco (p.ej. sdb). Filtramos por
|
||||
# NO removible (RM=0): el USB es removible y queda fuera; el ISO de la VM es 'rom'
|
||||
# y tampoco cuenta. Asi da igual que el disco de sistema sea nvme0n1 o un SSD
|
||||
# PCIe que enumera como sda. Pasa SYS_DEV/HOME_DEV para forzar nombres a mano.
|
||||
#
|
||||
# Layout (replica el hardware): disco de sistema = ESP 512M + /boot 1G ext4 +
|
||||
# / btrfs (resto); segundo disco = /home ext4 (todo). swap = zram ("swap": true
|
||||
# del base). Si solo hay 1 disco (portatil), /home queda dentro del / btrfs.
|
||||
import json, subprocess, sys
|
||||
from pathlib import Path
|
||||
from archinstall.lib.disk.device_handler import device_handler
|
||||
from archinstall.lib.models.device import (
|
||||
# archinstall solo existe en el entorno live; el --selftest corre en cualquier
|
||||
# sitio porque pick_fixed no lo necesita. ponytail: se importa al usar main().
|
||||
try:
|
||||
from archinstall.lib.disk.device_handler import device_handler
|
||||
from archinstall.lib.models.device import (
|
||||
DiskLayoutConfiguration, DiskLayoutType, DeviceModification,
|
||||
PartitionModification, ModificationStatus, PartitionType,
|
||||
FilesystemType, PartitionFlag, Size, Unit,
|
||||
)
|
||||
)
|
||||
except ImportError:
|
||||
device_handler = None
|
||||
|
||||
MiB = 1024 ** 2
|
||||
END_MARGIN = MiB # deja 1 MiB al final para la cabecera GPT de respaldo
|
||||
|
||||
def usable_end(total):
|
||||
# Final alineado a la rejilla de 1 MiB. Los discos reales tienen tamanos que
|
||||
# no son multiplo de MiB (p.ej. 250_059_350_016 B), asi que terminar en
|
||||
# total-END_MARGIN deja la particion fuera de rejilla y parted la marca como
|
||||
# "misaligned". Redondeamos el final hacia abajo a MiB (se pierden <2 MiB).
|
||||
return ((int(total) - END_MARGIN) // MiB) * MiB
|
||||
|
||||
def part(status, ptype, start_b, len_b, ss, fs=None, mnt=None, flags=()):
|
||||
return PartitionModification(
|
||||
status=status, type=ptype,
|
||||
@@ -36,7 +55,7 @@ def nvme_mods(dev):
|
||||
esp_start, esp_len = 1 * MiB, 511 * MiB # 1 -> 512 MiB
|
||||
boot_start, boot_len = 512 * MiB, 1024 * MiB # 512 -> 1536 MiB
|
||||
root_start = boot_start + boot_len # 1536 MiB
|
||||
root_len = total - root_start - END_MARGIN
|
||||
root_len = usable_end(total) - root_start
|
||||
return DeviceModification(device=dev, wipe=True, partitions=[
|
||||
part(ModificationStatus.CREATE, PartitionType.PRIMARY, esp_start, esp_len, ss,
|
||||
FilesystemType.FAT32, "/boot/efi", (PartitionFlag.BOOT, PartitionFlag.ESP)),
|
||||
@@ -52,26 +71,71 @@ def sda_mods(dev):
|
||||
start = 1 * MiB
|
||||
return DeviceModification(device=dev, wipe=True, partitions=[
|
||||
part(ModificationStatus.CREATE, PartitionType.PRIMARY, start,
|
||||
total - start - END_MARGIN, ss, FilesystemType.EXT4, "/home"),
|
||||
usable_end(total) - start, ss, FilesystemType.EXT4, "/home"),
|
||||
])
|
||||
|
||||
def main():
|
||||
base_path = sys.argv[1]
|
||||
nvme = sys.argv[2] if len(sys.argv) > 2 else "/dev/nvme0n1"
|
||||
sda = sys.argv[3] if len(sys.argv) > 3 else "/dev/sda"
|
||||
def pick_fixed(lsblk_out):
|
||||
# Discos NO removibles, en orden de kernel. Excluye el USB instalador (RM=1)
|
||||
# y el ISO de la VM (TYPE=rom). nvme va antes que sda si ambos existen.
|
||||
disks = []
|
||||
for line in lsblk_out.splitlines():
|
||||
name, rm, typ = line.split()
|
||||
if typ == "disk" and rm == "0":
|
||||
disks.append(f"/dev/{name}")
|
||||
return sorted(disks, key=lambda d: (0 if "nvme" in d else 1, d))
|
||||
|
||||
dev_nvme = device_handler.get_device(Path(nvme))
|
||||
dev_sda = device_handler.get_device(Path(sda))
|
||||
if not dev_nvme or not dev_sda:
|
||||
sys.exit(f"No encuentro los discos {nvme} / {sda} (revisa lsblk)")
|
||||
def fixed_disks():
|
||||
return pick_fixed(subprocess.check_output(
|
||||
["lsblk", "-dnro", "NAME,RM,TYPE"], text=True))
|
||||
|
||||
def main():
|
||||
if device_handler is None:
|
||||
sys.exit("archinstall no disponible (¿estás en el entorno live?)")
|
||||
base_path = sys.argv[1]
|
||||
override = sys.argv[2:] # SYS_DEV [HOME_DEV] para forzar a mano
|
||||
disks = override if override else fixed_disks()
|
||||
if not disks:
|
||||
sys.exit("No encuentro ningun disco fijo no removible (revisa lsblk)")
|
||||
|
||||
dev_sys = device_handler.get_device(Path(disks[0]))
|
||||
if not dev_sys:
|
||||
sys.exit(f"No encuentro el disco de sistema {disks[0]} (revisa lsblk)")
|
||||
|
||||
# >=2 discos fijos -> sistema + /home aparte. 1 disco (portatil) -> todo
|
||||
# en el de sistema; /home vive dentro del btrfs raiz.
|
||||
mods = [nvme_mods(dev_sys)]
|
||||
if len(disks) > 1:
|
||||
dev_home = device_handler.get_device(Path(disks[1]))
|
||||
if dev_home:
|
||||
mods.append(sda_mods(dev_home))
|
||||
|
||||
layout = DiskLayoutConfiguration(
|
||||
config_type=DiskLayoutType.Manual,
|
||||
device_modifications=[nvme_mods(dev_nvme), sda_mods(dev_sda)],
|
||||
device_modifications=mods,
|
||||
)
|
||||
cfg = json.loads(Path(base_path).read_text())
|
||||
cfg["disk_config"] = layout.json()
|
||||
json.dump(cfg, sys.stdout, indent=2)
|
||||
|
||||
def _selftest():
|
||||
# portatil: SSD PCIe = sda (fijo), USB instalador = sdb (RM=1), ISO = sr0
|
||||
laptop = "sda 0 disk\nsdb 1 disk\nsr0 1 rom"
|
||||
assert pick_fixed(laptop) == ["/dev/sda"], pick_fixed(laptop)
|
||||
# hardware/VM: nvme = sistema, sda = /home; nvme primero aunque ordene despues
|
||||
two = "sda 0 disk\nnvme0n1 0 disk"
|
||||
assert pick_fixed(two) == ["/dev/nvme0n1", "/dev/sda"], pick_fixed(two)
|
||||
# solo USB conectado -> sin discos fijos (main aborta)
|
||||
assert pick_fixed("sdb 1 disk\nsr0 1 rom") == []
|
||||
# final alineado: disco "feo" -> end multiplo de MiB, dentro del disco
|
||||
real = 250_059_350_016 # SSD 250G tipico, no multiplo de MiB
|
||||
e = usable_end(real)
|
||||
assert e % MiB == 0 and 0 < e <= real - END_MARGIN, e
|
||||
# disco MiB-exacto (VM 40G) -> pierde justo END_MARGIN, nada mas
|
||||
assert usable_end(40 * 1024 * MiB) == 40 * 1024 * MiB - MiB
|
||||
print("ok")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if sys.argv[1:2] == ["--selftest"]:
|
||||
_selftest()
|
||||
else:
|
||||
main()
|
||||
|
||||
Executable → Regular
+196
-15
@@ -1,42 +1,127 @@
|
||||
#!/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.
|
||||
|
||||
# --- 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=(
|
||||
# base / shell / CLI moderno
|
||||
zsh starship fzf ripgrep bat eza btop zoxide fd tldr
|
||||
zsh starship fzf ripgrep bat eza btop zoxide fd tldr fastfetch 7zip vim usbutils
|
||||
# dev toolchain (Podman en vez de Docker + capa de compat)
|
||||
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
|
||||
chezmoi
|
||||
# apps de escritorio que SÍ están en extra
|
||||
bitwarden dbeaver obsidian gimp syncthing remmina freerdp apostrophe
|
||||
bitwarden dbeaver gimp syncthing remmina freerdp apostrophe guvcview vlc vlc-plugin-ffmpeg
|
||||
# diccionarios (corrector ortográfico en apps GTK/Qt)
|
||||
hunspell-en_us hunspell-es_es
|
||||
# fuentes / iconos compartidos
|
||||
ttf-jetbrains-mono-nerd ttf-firacode-nerd noto-fonts noto-fonts-emoji
|
||||
papirus-icon-theme
|
||||
# red / utilidades base
|
||||
networkmanager flatpak openssh
|
||||
networkmanager flatpak openssh networkmanager-openvpn openvpn tailscale wpa_supplicant
|
||||
# audio (PipeWire reemplaza a PulseAudio)
|
||||
pipewire pipewire-alsa pipewire-jack pipewire-pulse wireplumber libpulse
|
||||
gst-plugin-pipewire gst-plugins-good gst-plugins-bad gst-libav
|
||||
# bluetooth: stack + daemon comunes; la UI (bluedevil) se instala explícito
|
||||
# en kde/setup.sh.
|
||||
bluez bluez-utils
|
||||
# hardware: mismo par de máquinas físicas (ver PROJECT_CONTEXT.md)
|
||||
amd-ucode sof-firmware btrfs-progs zram-generator cpupower power-profiles-daemon
|
||||
ibus wl-clipboard wmctrl smartmontools
|
||||
# gvfs backend para montar móviles Android por MTP (Dolphin/Nautilus)
|
||||
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) ------------------------------------------------------------
|
||||
# helpwire: es un .deb; no hay paquete AUR estable -> se convierte con debtap
|
||||
# (lo gestiona aparte la función install_helpwire de setup.sh).
|
||||
# claude-code: NO va por AUR (se desincroniza del release real); se instala
|
||||
# con el instalador nativo oficial, función install_claude_code() más abajo.
|
||||
AUR_COMMON=(
|
||||
zen-browser-bin
|
||||
visual-studio-code-bin
|
||||
davmail
|
||||
vscodium-bin
|
||||
betterbird-bin
|
||||
onlyoffice-bin
|
||||
spotify
|
||||
ungoogled-chromium-bin
|
||||
input-remapper-git
|
||||
ventoy-bin
|
||||
woeusb-ng
|
||||
bibata-cursor-theme-bin
|
||||
pamac-all
|
||||
yay
|
||||
# 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 --------------------------------------
|
||||
|
||||
# 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 "$@"; }
|
||||
|
||||
# paru se construye como usuario normal (makepkg no corre como root)
|
||||
as_user() { sudo -u "$INSTALL_USER" bash -lc "$*"; }
|
||||
# paru se construye como usuario normal (makepkg no corre como root).
|
||||
# RUSTUP_HOME/CARGO_HOME explícitos: bajo el fakeroot de makepkg, HOME pasa a
|
||||
# /root y rustup intenta crear /root/.rustup (permission denied, uid real != root).
|
||||
# rustup respeta RUSTUP_HOME antes que HOME, así que apuntarlo al home del usuario
|
||||
# arregla los paquetes Rust (p.ej. karere).
|
||||
as_user() {
|
||||
sudo -u "$INSTALL_USER" bash -lc \
|
||||
"export RUSTUP_HOME=\$HOME/.rustup CARGO_HOME=\$HOME/.cargo; $*"
|
||||
}
|
||||
|
||||
bootstrap_paru() {
|
||||
command -v paru &>/dev/null && return 0
|
||||
@@ -52,9 +137,14 @@ bootstrap_paru() {
|
||||
}
|
||||
|
||||
install_aur() {
|
||||
# ponytail: paru en una sola llamada; si un PKGBUILD falla, falla el lote.
|
||||
# upgrade path: instalar uno a uno con un bucle si necesitas tolerancia a fallos.
|
||||
as_user "paru -S --needed --noconfirm $*"
|
||||
# Uno a uno: que un PKGBUILD roto (p.ej. karere) no se lleve por delante el
|
||||
# resto del lote ni el chezmoi init posterior. Avisa de los que fallen.
|
||||
local p failed=()
|
||||
for p in "$@"; do
|
||||
as_user "paru -S --needed --noconfirm $p" || failed+=("$p")
|
||||
done
|
||||
((${#failed[@]})) && echo "!! AUR falló (revisa a mano): ${failed[*]}" >&2
|
||||
return 0
|
||||
}
|
||||
|
||||
# archinstall 4.3 instala el paquete grub pero NO ejecuta grub-install con
|
||||
@@ -83,3 +173,94 @@ install_helpwire() {
|
||||
install -Dm644 "$hw/helpwire-operator.desktop" /usr/share/applications/helpwire-operator.desktop
|
||||
install -Dm644 "$hw/helpwire-operator_128.png" /usr/share/icons/hicolor/128x128/apps/helpwire-operator.png
|
||||
}
|
||||
|
||||
# Claude Code: instalador nativo oficial (no AUR, se autoactualiza). Instala
|
||||
# en ~/.local/share/claude con symlink en ~/.local/bin/claude; no necesita
|
||||
# root ni flags especiales para correr sin interacción.
|
||||
install_claude_code() {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user