Actualiza el software de tu máquina desde la consola. Un paso por gestor de paquetes.
$ up
==> dnf
$ sudo dnf5 check-upgrade --refresh
$ sudo dnf5 upgrade -y
...
==> flatpak_sys
$ sudo flatpak update --system -y --noninteractive
Nothing to do.
== resumen ==
dnf ok
flatpak_sys nada que hacer
flatpak_user nada que hacer
npm_global FALLO (rc=1)
El paso que falla no cancela los demás, y lo ves en una línea en vez de en 2000.
365 líneas de bash. Cero dependencias. Cero configuración. Linux (dnf · apt · pacman · zypper · apk) y macOS. Bash 3.2+.
Un envoltorio honesto sobre los gestores que ya tienes instalados. No inventa nada: los lanza con los argumentos correctos, traduce sus códigos de salida, y te dice al final qué funcionó y qué no.
- No es un gestor de paquetes. No instala, no busca, no desinstala.
- No actualiza firmware por su cuenta (ver Firmware).
- No hace saltos de versión de la distro (
dnf system-upgrade,do-release-upgrade, macOS mayor). Eso se hace a mano, mirando la pantalla. - No se auto-actualiza. Una herramienta que se rompe a sí misma a mitad de ejecución te deja sin actualizador.
- No tiene fichero de configuración. Si necesitas una web para explicar tu config, la config está mal.
- No soporta Windows nativo. Con WSL es Linux y funciona.
git clone https://github.com/blasdecrespo/up.git
ln -s "$PWD/up/up" ~/.local/bin/upRequisitos: bash 3.2 o superior. Nada más. Si tienes GNU timeout (cualquier Linux,
o brew install coreutils en macOS) se usa; si no, avisa y sigue sin él.
up # todos los pasos por defecto que existan en esta máquina
up -n # dry-run: imprime los comandos exactos, no ejecuta, no pide contraseña
up -l # lista los pasos y cuáles existen aquí
up dnf flatpak_user # solo esos pasos
up firmware # paso opt-in
up -t 600 # timeout por comando, en segundos (por defecto 1800)
up -h # ayuda| Paso | Binario | Root | Defecto | Comando |
|---|---|---|---|---|
dnf |
dnf5 |
sí | sí | check-upgrade --refresh, y si hay algo, upgrade -y |
apt |
apt-get |
sí | sí | update + upgrade -y --force-confold |
pacman |
pacman |
sí | sí | -Syu --noconfirm |
zypper |
zypper |
sí | sí | --non-interactive update |
apk |
apk |
sí | sí | -U upgrade |
macos |
softwareupdate |
sí | sí | --install --recommended |
brew |
brew |
no | sí | update + upgrade |
snap |
snap |
sí | sí | refresh |
flatpak_sys |
flatpak |
sí | sí | update --system -y --noninteractive |
flatpak_user |
flatpak |
no | sí | update --user -y --noninteractive |
npm_global |
npm |
depende | sí | Reinstala cada global a @latest |
pipx |
pipx |
no | sí | upgrade-all |
rustup |
rustup |
no | sí | update |
cargo |
cargo-install-update |
no | sí | cargo install-update -a |
gh_ext |
gh |
no | sí | extension upgrade --all |
firmware |
fwupdmgr |
no | no | Solo informa |
Sin argumentos, up corre solo los pasos cuyo binario existe aquí. Un gestor que
no tienes instalado no es una omisión que reportar: es ruido. Si lo pides por nombre y
no está, entonces sí se te dice: AUSENTE.
0 si todo fue bien. Si no, el número de pasos que fallaron.
| Estado | Significado |
|---|---|
ok |
Actualizado |
nada que hacer |
Ya estaba al día |
AUSENTE (bin) |
Lo pediste por nombre y ese binario no está aquí |
SIN PERMISOS |
El paso necesita root y no lo conseguiste (cuenta como fallo) |
TIMEOUT (Ns) |
Un comando del paso pasó del límite y fue matado. El límite es por comando, no por paso: npm_global lanza un npm install por paquete y cada uno tiene sus N segundos |
FALLO (rc=N) |
El gestor devolvió error. N es su código real |
Un actualizador que corre con root y no se ha probado no es una herramienta: es una apuesta. Esto es lo que se ejecutó de verdad, y lo que no.
| Paso | Probado | Dónde |
|---|---|---|
apt |
✅ ejecución real | contenedor debian:stable (Debian 13) |
pacman |
✅ ejecución real | contenedor archlinux:latest |
zypper |
✅ actualización real aplicada | contenedor opensuse/tumbleweed |
apk |
✅ actualización real aplicada | contenedor alpine:3.22 |
pipx |
✅ ejecución real | contenedor debian:stable |
rustup |
✅ ejecución real | contenedor rust:slim |
gh_ext |
✅ ejecución real | host Fedora 43 |
firmware |
✅ ejecución real | host Fedora 43 (detectó un dbx pendiente) |
flatpak_user |
✅ ejecución real | host Fedora 43 |
dnf |
✅ actualización real aplicada | host Fedora 43: transacción de 1419 paquetes (kernel, glibc, systemd) |
npm_global |
solo el camino "sin globales instalados" | |
cargo |
nombre del binario verificado en el Cargo.toml del crate |
|
flatpak_sys |
❌ | requiere root interactivo |
snap |
❌ | requiere systemd |
brew |
❌ | sin máquina con brew |
macos |
❌ | sin máquina Darwin. softwareupdate no se ha ejecutado nunca |
Portabilidad, verificada igual:
- Bash 3.2.57 (la versión que trae macOS desde 2007): parsea y ejecuta el script.
Contenedor
bash:3.2, ejecución real del pasoapk. - Sin GNU
timeout(Alpine sin coreutils, y macOS de fábrica): avisa porstderry sigue funcionando sin límite de tiempo. - Máquina sin ningún gestor conocido: sale con código 1 y un mensaje, no con un error de bash.
Si usas macOS, el paso macos es código no probado corriendo como root. Empieza por
up -n y decide tú.
No hay detección de distribución. No hay if [[ $ID == fedora ]]. La tabla de pasos
es la única estructura de datos, y la detección es "¿existe el binario?". Añadir un
sistema operativo es añadir filas, no ramas.
PASOS=(
"apt|apt-get|1|1" # nombre | binario | puede_necesitar_root | por_defecto
)Consecuencia directa: el mismo script en Fedora corre dnf, en Debian corre apt, y
en Alpine corre apk, sin una sola condicional de plataforma. Si el modelo de datos
es el correcto, los casos especiales desaparecen.
Lo que copié de topgrade
Cuatro cosas, y solo cuatro:
- Detección por existencia del binario, no por configuración.
- Fallo parcial no aborta. Un paso roto no cancela los otros.
- Elevación una vez al principio, con keep-alive. Si no, la credencial caduca a los 5 minutos y te pregunta a mitad de la tanda.
- Resumen final tabulado. No lees 2000 líneas de scroll para saber qué falló.
Todo esto son problemas reales, documentados en sus propios issues:
| Problema | Aquí |
|---|---|
| Salta pasos en silencio (#530) | AUSENTE, SIN PERMISOS y nada que hacer son estados distintos |
| Se cuelga y hay que matarlo (#288, #563, #380) | timeout -k 10 por paso y stdin cerrado |
Asume que 0 = ok para todos los gestores |
Cada paso traduce su código de salida |
| Un paso rompe al siguiente (#1039) | Los pasos son independientes |
| Config TOML de 524 líneas de ejemplo (#1472) | Sin config |
| Se auto-actualiza y se rompe (#959) | No se toca a sí mismo |
| 174 pasos = 170 issues abiertos permanentes | 16 pasos, y probados uno a uno |
sudo, o doas si no hay sudo, o nada si ya eres root (contenedores, su -).
Se aplica al comando concreto, nunca al proceso entero:
sudo up # ❌ NO. Correrías npm -g y cargo como root.
up # ✅ Sí. Cada paso eleva solo lo que necesita.brew se niega a correr como root, y tiene razón. Por eso su fila dice 0.
Esto es la mitad del trabajo de un meta-updater, y es donde casi todos fallan:
dnf5 check-upgrade→100significa hay actualizaciones,0significa nada que hacer. Contrato scriptable sin parsear texto. Lo uso para no lanzar unupgradevacío ni imprimir un paso inútil.zypper→100/101significan actualizaciones aplicadas, no error.106es un repositorio que no se pudo leer: molesto, no fatal.fwupdmgr get-updates→2= nada que hacer,3= nada soportado.fwupdmgr refresh --force→ falla si los metadatos son recientes. Se ignora.dnf5 needs-restarting -r→1= hace falta reiniciar. Cualquier otro código es un error suyo, y un error no es motivo para decirte que reinicies.
Un meta-updater que trate cualquier código distinto de 0 como error te va a mentir
todos los días.
Tres decisiones del mismo principio:
apt-get upgrade, nodist-upgrade.dist-upgradedesinstala paquetes para resolver conflictos.zypper update, nodup.duppuede cambiarte medio sistema. (Aun así,updatepuede sustituir paquetes: en las pruebas, Tumbleweed cambiólibcurl-mini4porlibcurl4. Es su resolutor, no el script.)flatpak --noninteractive. No es solo para automatizar: le impide proponerte desinstalar runtimes EOL. Para limpiar,flatpak uninstall --unused, aparte.
Y --force-confold en apt: ante un fichero de configuración que tú modificaste, se
queda con el tuyo en vez de abrir un diálogo que cuelga la tanda entera.
npm update -g está roto de facto: no actualiza los globales por el caret implícito de
semver (npm#17209), y en su día llegó a
borrarlos todos (npm/cli#3175).
Lo que sí funciona: enumerar npm root -g y reinstalar cada paquete a @latest, uno a
uno. Sin jq, sin parsear JSON: se leen los directorios (con soporte para @scope/pkg).
El root es dinámico: si el directorio global es tuyo (npm de brew, o prefix en $HOME)
no usa sudo; si es /usr/local/lib/node_modules de root, sí.
up firmware es opt-in y solo informa. Refresca metadatos, lista lo disponible y te
imprime el comando para aplicarlo:
Hay firmware disponible. Aplícalo tú, enchufado a la corriente:
fwupdmgr update
Un flasheo interrumpido convierte el portátil en un ladrillo. fwupd además exige
corriente alterna y umbral de batería, y falla en portátiles con límite de carga aunque
estén enchufados. Eso no va en un barrido diario desatendido.
Al terminar, avisa si hay kernel o servicios actualizados que siguen corriendo en su
versión vieja: dnf5 needs-restarting -r en Fedora, /var/run/reboot-required en
Debian y Ubuntu.
No reinicia nada. Te lo dice.
Un paso son dos cosas: una fila en la tabla y una función.
PASOS=(
...
"xbps|xbps-install|1|1" # nombre | binario | root | por_defecto
)
# Contrato: 0 = actualizado, 2 = no había nada que hacer, otro = fallo.
paso_xbps() {
run_root xbps-install -Suy
}run imprime el comando, respeta el dry-run, aplica el timeout y cierra stdin.
run_root hace lo mismo elevando. Eso es todo: no hay registro, ni plugin API, ni
interfaz que implementar.
Pruébalo antes de mandarlo. En contenedor cuesta un minuto:
podman run --rm -v "$PWD/up:/usr/local/bin/up:ro" docker.io/library/debian:stable up -nMIT. Ver LICENSE.