es en

Respaldo automático periódico de los paquetes instalados en el SO

Publicado el 2025-12-08 en Software

Hace un tiempo me pasé a yadm para gestionar mis dotfiles. Una de las características que más me gustan es su soporte para el arranque (bootstrap). He estado añadiendo operaciones de configuración idempotentes.

Mi última incorporación es un script que crea archivos de copia de seguridad con todos los paquetes instalados en el sistema operativo. En caso de desastre, si tengo que reinstalar el sistema operativo, solo tengo que pasarle al gestor de paquetes la última lista respaldada para ese host. Para la copia de seguridad de los datos estoy trabajando en una configuración aparte, ese será tema para un futuro artículo.

En cuanto al script, es sencillo y está adaptado a mis sistemas. Solo es compatible con APT (uso Debian en mis servidores sin interfaz gráfica) y pacman (uso Arch Linux en mi ordenador de sobremesa), pero añadir DNF u otros gestores de paquetes debería ser bastante fácil. Lo escribí en fish shell, porque ahora es mi shell preferida.

Puedes ver el commit en mis dotfiles para ver todos los cambios, pero los reproduciré a continuación tal y como estaban en el momento de escribir este artículo.

El script principal lo he puesto en ~/.local/bin/backup_installed_packages_list.fish:

#!/usr/bin/env fish

function get_hostname
    if type -q hostname
        hostname
    else if type -q hostnamectl
        hostnamectl --static
    else if test -f /etc/hostname
        read -l host_from_file < /etc/hostname
        echo $host_from_file | string trim
    else
        echo "Error: cannot reliably get hostname" >&2
        exit 2
    end
end

set current_hostname (get_hostname)
set current_date (date +%Y-%m-%d)
set backup_dir "$HOME/Backups/$current_hostname/packages"
set temp_file (mktemp)

function save_if_changed -a input_file -a suffix
    set -l target_file "$backup_dir/$current_date$suffix.txt"

    # regex matches YYYY-MM-DD followed optionally by suffix, ending in .txt
    set -l pattern
    if test -z "$suffix"
        set pattern '^\d{4}-\d{2}-\d{2}\.txt$'
    else
        set pattern "^\d{4}-\d{2}-\d{2}$suffix\.txt\$"
    end

    # find the most recent backup file that matches the specific pattern
    set -l latest_backup_name (ls -1 $backup_dir 2>/dev/null | string match -r $pattern | sort | tail -n 1)

    if test -n "$latest_backup_name"
        set -l latest_backup_path "$backup_dir/$latest_backup_name"

        if cmp -s $input_file $latest_backup_path
            echo "No changes in package list compared to $latest_backup_path. Skipping."
            rm $input_file
            return
        end
    end

    # if we reach here, either no backup exists or content is different
    mv $input_file $target_file
    echo "Installed packages backup saved: $target_file"
    notify-send \
        --urgency=normal \
        --wait \
        --icon=backup \
        "New backup of installed OS packages: $target_file"
end

mkdir -p $backup_dir

if type -q apt
    # https://www.debian.org/doc/manuals/debian-reference/ch10.en.html#_backup_and_recovery_policy
    dpkg --get-selections > $temp_file
    save_if_changed $temp_file ""
else if type -q pacman
    # https://wiki.archlinux.org/title/Migrate_installation_to_new_hardware#List_of_installed_packages
    pacman -Qqen > $temp_file
    save_if_changed $temp_file ""
    pacman -Qqem > $temp_file
    save_if_changed $temp_file "_aur"
else
    echo "Error: Neither apt nor pacman found." >&2
    rm $temp_file
    exit 1
end

Creé la función de ayuda get_hostname porque mi instalación de Arch Linux no tenía disponible el comando hostname, solo hostnamectl.

notify-send es muy útil para recibir notificaciones de escritorio, lo he estado usando con frecuencia en mis nuevos scripts, sobre todo en los que se ejecutan en segundo plano como este. Ten en cuenta que requiere tener instalado libnotify (libnotify-bin en instalaciones basadas en Debian). Yo lo instalo como un paquete común en mi configuración de arranque de yadm.

Luego defino las unidades de servicio y temporizador de systemd, en ~/.config/systemd/user/backup-installed-packages.service y ~/.config/systemd/user/backup-installed-packages.timer:

[Unit]
Description=Backup list of installed OS packages
After=graphical-session.target

[Service]
Type=oneshot
ExecStart=%h/.local/bin/backup_installed_packages_list.fish
StandardOutput=journal
StandardError=journal
[Unit]
Description=Timer to backup list of installed OS packages

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Y después activa el temporizador:

$ systemctl --user daemon-reload
$ systemctl --user enable --now backup-installed-packages.timer

Activo el temporizador de systemd en mi archivo de arranque de yadm.