From 53a4a7eb884fcd670e3ddc8cb86197ca1744cff8 Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:41:16 +0100 Subject: [PATCH] feat: better updater --- configuration.nix | 21 ++++- readme.md | 7 ++ update-launcher.sh | 77 +++++++++++++++++ update-monitor.sh | 200 +++++++++++++++++++++++++++++++++++++++++++++ update.sh | 153 ++++++++++++++++++++++++++-------- 5 files changed, 424 insertions(+), 34 deletions(-) create mode 100755 update-launcher.sh create mode 100755 update-monitor.sh mode change 100644 => 100755 update.sh diff --git a/configuration.nix b/configuration.nix index 41537c6..82d044b 100644 --- a/configuration.nix +++ b/configuration.nix @@ -4,6 +4,18 @@ let username = "usuario"; learningml-desktop = pkgs.callPackage ./pkgs/learningml-desktop.nix { }; andaredConnectScript = pkgs.writeShellScriptBin "andared-connect" (builtins.readFile ./andared-connect.sh); + labUpdateMonitor = pkgs.writeShellScriptBin "lab-update-monitor" (builtins.readFile ./update-monitor.sh); + labUpdateLauncher = pkgs.writeShellScriptBin "lab-update-launcher" (builtins.readFile ./update-launcher.sh); + labUpdateDesktop = pkgs.makeDesktopItem { + name = "lab-updates"; + desktopName = "Actualizaciones del laboratorio"; + genericName = "Registro y lanzador de actualizaciones"; + comment = "Sigue la actualizacion activa, revisa registros o lanza una nueva actualizacion"; + exec = "lab-update-launcher"; + icon = "system-software-update"; + terminal = false; + categories = [ "System" "Settings" ]; + }; in { imports = @@ -222,7 +234,14 @@ in learningml-desktop wireshark andaredConnectScript + labUpdateMonitor + labUpdateLauncher + labUpdateDesktop xdg-user-dirs + libnotify + util-linux + kdePackages.kdialog + kdePackages.konsole libreoffice-qt hunspell hunspellDicts.es_ES @@ -334,7 +353,7 @@ in }; systemd.services.lab-updater = { description = "actualizaciones automáticas del sistema"; - path = with pkgs; [ git nix coreutils kdePackages.kdialog libnotify config.system.build.nixos-rebuild ]; + path = with pkgs; [ git nix coreutils systemd util-linux libnotify config.system.build.nixos-rebuild ]; serviceConfig = { Type = "oneshot"; ExecStart = "/etc/nixos/update.sh"; diff --git a/readme.md b/readme.md index 7ab0dc6..a2b9d17 100644 --- a/readme.md +++ b/readme.md @@ -28,3 +28,10 @@ El sistema instalado deja preparada una conexión de NetworkManager para `Andare - En Plasma, basta con abrir el selector de redes, pulsar `Andared_Corporativo` e introducir las credenciales del usuario. - Alternativamente, desde terminal se puede usar `andared-connect` para que `nmcli` pida las credenciales de forma interactiva. - Si `TTLS` no funciona en un centro concreto, prueba `andared-connect peap`. + +## actualizaciones + +- Se puede seguir lanzando la actualización manual con `su -c /etc/nixos/update.sh`. El script ahora guarda un registro en `/var/log/lab-updates` y envía la notificación final a la sesión activa de KDE. +- En el menú de aplicaciones de Plasma aparece `Actualizaciones del laboratorio`. +- Si ya hay una actualización en marcha, el lanzador se engancha automáticamente a su registro activo. +- Si no hay ninguna en curso, el lanzador permite iniciar una nueva actualización y seguir el registro, ver el último registro o abrir la carpeta con el historial. diff --git a/update-launcher.sh b/update-launcher.sh new file mode 100755 index 0000000..71f769a --- /dev/null +++ b/update-launcher.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +set -euo pipefail + +monitor_bin="${LAB_UPDATE_MONITOR_BIN:-lab-update-monitor}" + +pick_terminal() { + local candidate + + for candidate in konsole xterm; do + if command -v "$candidate" >/dev/null 2>&1; then + printf '%s\n' "$candidate" + return 0 + fi + done + + return 1 +} + +open_terminal() { + local terminal="$1" + shift + + case "$terminal" in + konsole) + exec "$terminal" --hold -e "$@" + ;; + xterm) + exec "$terminal" -hold -e "$@" + ;; + esac +} + +main() { + local terminal choice log_dir + + terminal="$(pick_terminal)" || { + if command -v kdialog >/dev/null 2>&1; then + kdialog --error "No se ha encontrado ningun terminal compatible para abrir los registros." + else + echo "No se ha encontrado ningun terminal compatible para abrir los registros." >&2 + fi + exit 1 + } + + if "$monitor_bin" --has-active-update; then + open_terminal "$terminal" "$monitor_bin" --watch + fi + + if command -v kdialog >/dev/null 2>&1; then + choice="$( + kdialog \ + --title "Actualizaciones del laboratorio" \ + --menu "Que quieres hacer?" \ + run "Ejecutar actualizacion y seguir registro" \ + watch "Ver el ultimo registro" \ + folder "Abrir la carpeta de registros" + )" || exit 0 + else + choice="run" + fi + + case "$choice" in + run) + open_terminal "$terminal" "$monitor_bin" --run + ;; + watch) + open_terminal "$terminal" "$monitor_bin" --watch + ;; + folder) + log_dir="$("$monitor_bin" --print-log-dir)" + exec xdg-open "$log_dir" + ;; + esac +} + +main "$@" diff --git a/update-monitor.sh b/update-monitor.sh new file mode 100755 index 0000000..a0ca5ae --- /dev/null +++ b/update-monitor.sh @@ -0,0 +1,200 @@ +#!/bin/bash + +set -euo pipefail + +log_dir="" +active_log_file="" +active_pid_file="" +latest_log_link="" + +init_paths() { + if [ -d /var/log/lab-updates ]; then + log_dir="/var/log/lab-updates" + else + log_dir="${XDG_STATE_HOME:-$HOME/.local/state}/lab-updates" + fi + + active_log_file="$log_dir/active.log" + active_pid_file="$log_dir/active.pid" + latest_log_link="$log_dir/latest.log" +} + +is_pid_alive() { + local pid="${1:-}" + [ -n "$pid" ] && [ -d "/proc/$pid" ] +} + +active_pid() { + if [ -r "$active_pid_file" ]; then + head -n 1 "$active_pid_file" 2>/dev/null || true + fi +} + +active_log() { + local log="" + + if [ -r "$active_log_file" ]; then + log="$(head -n 1 "$active_log_file" 2>/dev/null || true)" + if [ -n "$log" ] && [ -e "$log" ]; then + printf '%s\n' "$log" + return 0 + fi + fi + + return 1 +} + +latest_log() { + local log="" + + if [ -L "$latest_log_link" ] && [ -e "$latest_log_link" ]; then + log="$(readlink -f "$latest_log_link" 2>/dev/null || true)" + fi + + if [ -z "$log" ] || [ ! -e "$log" ]; then + log="$(ls -1t "$log_dir"/update-*.log 2>/dev/null | head -n 1 || true)" + fi + + if [ -n "$log" ] && [ -e "$log" ]; then + printf '%s\n' "$log" + return 0 + fi + + return 1 +} + +has_active_update() { + local pid + + pid="$(active_pid)" + is_pid_alive "$pid" +} + +watch_log() { + local log="" + local follow=0 + + if has_active_update; then + log="$(active_log || true)" + follow=1 + fi + + if [ -z "$log" ]; then + log="$(latest_log || true)" + fi + + if [ -z "$log" ]; then + echo "Todavia no hay registros de actualizaciones." + exit 1 + fi + + if [ "$follow" -eq 1 ]; then + echo "Siguiendo registro: $log" + tail -n 200 -F "$log" + else + echo "Mostrando el ultimo registro: $log" + tail -n 200 "$log" + fi +} + +start_update_and_watch() { + local before_latest="" + local start_ts="" + local pkexec_pid="" + local log="" + + if has_active_update; then + watch_log + return 0 + fi + + if ! command -v pkexec >/dev/null 2>&1; then + echo "No se ha encontrado pkexec para iniciar la actualizacion." >&2 + exit 1 + fi + + before_latest="$(latest_log || true)" + start_ts="$(date +%s)" + + echo "Solicitando permisos de administrador para iniciar la actualizacion..." + pkexec /etc/nixos/update.sh >/dev/null 2>&1 & + pkexec_pid="$!" + + for _ in $(seq 1 180); do + if has_active_update; then + log="$(active_log || true)" + [ -n "$log" ] && break + fi + + log="$(latest_log || true)" + if [ -n "$log" ] && [ "$log" != "$before_latest" ]; then + break + fi + + if [ -n "$log" ] && [ "$(stat -c %Y "$log" 2>/dev/null || echo 0)" -ge "$start_ts" ]; then + break + fi + + if [ ! -d "/proc/$pkexec_pid" ]; then + break + fi + + sleep 1 + done + + if [ -z "$log" ]; then + wait "$pkexec_pid" || true + echo "No se ha podido encontrar el registro de la actualizacion." + exit 1 + fi + + echo "Siguiendo registro: $log" + tail -n 200 -F "$log" +} + +usage() { + cat <<'EOF' +Uso: lab-update-monitor [--watch|--run|--run-or-watch|--has-active-update|--print-log-dir] + + --watch Sigue la actualizacion en curso o el ultimo registro disponible. + --run Inicia una actualizacion con permisos de administrador y sigue su registro. + --run-or-watch Si hay una actualizacion activa, se engancha a ella; si no, inicia una. + --has-active-update Sale con codigo 0 si hay una actualizacion activa. + --print-log-dir Muestra la carpeta de registros. +EOF +} + +main() { + init_paths + + case "${1:---run-or-watch}" in + --watch) + watch_log + ;; + --run) + start_update_and_watch + ;; + --run-or-watch) + if has_active_update; then + watch_log + else + start_update_and_watch + fi + ;; + --has-active-update) + has_active_update + ;; + --print-log-dir) + printf '%s\n' "$log_dir" + ;; + --help|-h) + usage + ;; + *) + usage >&2 + exit 1 + ;; + esac +} + +main "$@" diff --git a/update.sh b/update.sh old mode 100644 new mode 100755 index fce9605..21fed44 --- a/update.sh +++ b/update.sh @@ -4,16 +4,6 @@ set -euo pipefail cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# When running from a systemd timer/cron, set display variables -# so that kdialog/notify-send can reach the X11 session. -if [ -z "${DISPLAY:-}" ]; then - export DISPLAY=":0" - dbus_addr="$(systemctl --user show-environment 2>/dev/null | grep '^DBUS_SESSION_BUS_ADDRESS=' | cut -d= -f2-)" || true - if [ -n "${dbus_addr:-}" ]; then - export DBUS_SESSION_BUS_ADDRESS="$dbus_addr" - fi -fi - force_update=0 for arg in "$@"; do case "$arg" in @@ -27,43 +17,140 @@ for arg in "$@"; do esac done -timestamp="$(date +%Y%m%d-%H%M%S)" log_dir="/var/log/lab-updates" mkdir -p "$log_dir" 2>/dev/null || log_dir="${XDG_STATE_HOME:-$HOME/.local/state}/lab-updates" mkdir -p "$log_dir" +lock_file="$log_dir/update.lock" +active_log_file="$log_dir/active.log" +active_pid_file="$log_dir/active.pid" +latest_log_link="$log_dir/latest.log" + +timestamp="$(date +%Y%m%d-%H%M%S)" log_file="$log_dir/update-$timestamp.log" +touch "$log_file" +chmod 0644 "$log_file" + +exec 9>"$lock_file" +if ! flock -n 9; then + current_log="" + if [ -r "$active_log_file" ]; then + current_log="$(head -n 1 "$active_log_file" 2>/dev/null || true)" + fi + + echo "Ya hay otra actualización en marcha." + if [ -n "$current_log" ]; then + echo "Registro activo: $current_log" + fi + exit 0 +fi + +printf '%s\n' "$log_file" > "$active_log_file" +printf '%s\n' "$$" > "$active_pid_file" +ln -sfn "$log_file" "$latest_log_link" +chmod 0644 "$active_log_file" "$active_pid_file" + exec > >(tee -a "$log_file") 2>&1 +cleanup() { + local code="$1" + + if [ -r "$active_pid_file" ] && [ "$(head -n 1 "$active_pid_file" 2>/dev/null || true)" = "$$" ]; then + rm -f "$active_log_file" "$active_pid_file" + fi + + if [ "$code" -ne 0 ]; then + notify "Actualizacion fallida" "La actualizacion ha fallado (codigo $code)." error + fi + + exit "$code" +} + +desktop_user="" +desktop_uid="" +desktop_display="" + +discover_desktop_session() { + local session uid user state type remote class display + + while read -r session uid user _; do + [ -n "$session" ] || continue + + state="$(loginctl show-session "$session" -p State --value 2>/dev/null || true)" + type="$(loginctl show-session "$session" -p Type --value 2>/dev/null || true)" + remote="$(loginctl show-session "$session" -p Remote --value 2>/dev/null || true)" + class="$(loginctl show-session "$session" -p Class --value 2>/dev/null || true)" + display="$(loginctl show-session "$session" -p Display --value 2>/dev/null || true)" + + [ "$state" = "active" ] || continue + [ "$remote" = "no" ] || continue + [ "$class" = "user" ] || continue + + case "$type" in + x11|wayland) + desktop_user="$user" + desktop_uid="$uid" + desktop_display="$display" + return 0 + ;; + esac + done < <(loginctl list-sessions --no-legend 2>/dev/null || true) + + return 1 +} + +run_in_desktop_session() { + if [ -z "$desktop_user" ] || [ -z "$desktop_uid" ]; then + discover_desktop_session || return 1 + fi + + local -a env_vars=( + "XDG_RUNTIME_DIR=/run/user/$desktop_uid" + "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$desktop_uid/bus" + "PATH=$PATH" + ) + + if [ -n "$desktop_display" ]; then + env_vars+=("DISPLAY=$desktop_display") + fi + + runuser -u "$desktop_user" -- env "${env_vars[@]}" "$@" +} + notify() { local title="$1" local message="$2" local mode="${3:-info}" + local full_message urgency - if command -v kdialog >/dev/null 2>&1 && { [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; }; then - case "$mode" in - reboot) - if kdialog \ - --title "$title" \ - --yes-label "Reiniciar ahora" \ - --no-label "Más tarde" \ - --yesno "$message\n\nRegistro: $log_file"; then - systemctl reboot - fi - ;; - error) - kdialog --title "$title" --error "$message\n\nRegistro:\n$log_file" - ;; - *) - kdialog --title "$title" --msgbox "$message\n\nRegistro: $log_file" - ;; - esac - elif command -v notify-send >/dev/null 2>&1; then - notify-send -t 0 --urgency=critical "$title" "$message\nRegistro: $log_file" + printf -v full_message '%s\n\nRegistro: %s' "$message" "$log_file" + + case "$mode" in + error) + urgency="critical" + ;; + *) + urgency="normal" + ;; + esac + + if command -v notify-send >/dev/null 2>&1; then + run_in_desktop_session \ + notify-send \ + --app-name="Actualizaciones del laboratorio" \ + --icon="system-software-update" \ + --urgency="$urgency" \ + -t 0 \ + "$title" \ + "$full_message" \ + && return 0 fi + + printf '%s\n%s\n' "$title" "$full_message" >&2 } -trap 'code=$?; [ "$code" -eq 0 ] || notify "Actualizacion fallida" "La actualizacion ha fallado (codigo $code)." error' EXIT +trap 'cleanup "$?"' EXIT +trap 'exit 130' INT TERM echo "Actualizando el sistema..." echo "Guardando registro en: $log_file" @@ -85,4 +172,4 @@ else fi nixos-rebuild switch --flake path:.#nixos -notify "Actualizacion completada" "La actualizacion del sistema ha terminado correctamente." reboot +notify "Actualizacion completada" "La actualizacion del sistema ha terminado correctamente. Reinicia el equipo cuando te venga bien."