From 2850a4fc7328e59e3f032c0e71b2d6746308dd4a Mon Sep 17 00:00:00 2001 From: alejandrogs73 Date: Sun, 27 Sep 2026 21:48:36 +0200 Subject: [PATCH] =?UTF-8?q?Automontaje=20de=20USB,=20suspender=20con=20bat?= =?UTF-8?q?er=C3=ADa=20agotada,=20bloqueo=20por=20inactividad=20y=20TLP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - automontaje (servicio de runit, root): monta memorias USB y tarjetas SD en /mnt/ETIQUETA o /mnt/sdXY y las desmonta al sacarlas, con notificación. FAT/exFAT/NTFS (ntfs3 del kernel) a nombre del usuario; los discos internos (ID_BUS distinto de usb) no se tocan. - bateria: al 3 % sin cargador avisa y, si en 10 s sigue descargando, suspende con sudo -n zzz (sudoers.d/zzz: solo zzz sin argumentos, sin contraseña, para wheel). - .xinitrc: xss-lock bloquea con slock tras 30 min de inactividad y la pantalla se apaga un minuto después. - install.sh: xss-lock, tlp y tlp-rdw; servicios tlp y automontaje; los sudoers se validan con visudo y se instalan con modo 440. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016UdJsHrbkmzhoCb6TthP2m --- README.md | 19 ++++-- home/.local/bin/bateria | 13 +++- home/.xinitrc | 6 ++ install.sh | 16 +++-- sistema/etc/sudoers.d/zzz | 3 + sistema/etc/sv/automontaje/run | 3 + sistema/usr/local/bin/automontaje | 98 +++++++++++++++++++++++++++++++ 7 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 sistema/etc/sudoers.d/zzz create mode 100755 sistema/etc/sv/automontaje/run create mode 100755 sistema/usr/local/bin/automontaje diff --git a/README.md b/README.md index 0b43e31..096bb12 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,11 @@ Hace seis pasos, que también se pueden lanzar por separado subclaves de autenticación. `.bashrc` ya apunta `SSH_AUTH_SOCK` al agente. Si `~/.gnupg` no existe todavía, no hace nada. 6. `sistema`: copia `sistema/` en `/`, activa los servicios de runit (dbus, - elogind, polkitd, NetworkManager, bluetoothd, acpid, chronyd), quita - dhcpcd y wpa_supplicant (NetworkManager ya gestiona la red) y añade el - usuario a los grupos audio, video, input, network y bluetooth. + elogind, polkitd, NetworkManager, bluetoothd, acpid, chronyd, tlp, + automontaje), quita dhcpcd y wpa_supplicant (NetworkManager ya gestiona la + red) y añade el usuario a los grupos audio, video, input, network y + bluetooth. Los archivos de `sudoers.d` se validan con `visudo` y se + instalan con modo 440. Después hay que cerrar sesión y volver a entrar (por los grupos) y lanzar `startx`. @@ -109,7 +111,16 @@ y muestran una notificación. - Al entrar en tty1 se lanza `startx` solo (`.bash_profile`); en las demás tty no. - `.xinitrc` arranca también `gammastep` (luz cálida de 20:00 a 8:00, con - transición de una hora) y `bateria` (avisa al 15 % y, en rojo, al 5 %). + transición de una hora) y `bateria` (avisa al 15 % y, en rojo, al 5 %; al + 3 % suspende con `sudo -n zzz`, permitido sin contraseña en + `sistema/etc/sudoers.d/zzz`). +- Tras 30 minutos sin tocar nada se bloquea con slock (`xss-lock`) y un + minuto después se apaga la pantalla. +- Memorias USB y tarjetas SD: el servicio `automontaje` (runit, como root) + las monta en `/mnt/ETIQUETA`, o en `/mnt/sdXY` si no tienen etiqueta, y + las desmonta al sacarlas, con una notificación. FAT, exFAT y NTFS quedan a + nombre del usuario; los discos internos no se tocan. Antes de sacar una + memoria en la que se ha escrito, `sync` (o `sudo umount /mnt/...`). - Carpetas del usuario en inglés y sin tildes (`user-dirs.dirs`): Documents, Downloads e Images. diff --git a/home/.local/bin/bateria b/home/.local/bin/bateria index a399c56..055c24d 100755 --- a/home/.local/bin/bateria +++ b/home/.local/bin/bateria @@ -1,10 +1,13 @@ #!/bin/sh # Avisa con una notificación cuando la batería baja del 15 % y del 5 % -# mientras se descarga (una vez por umbral; al enchufar el cargador se -# reinicia). Se lanza desde .xinitrc y se queda comprobando cada minuto. +# mientras se descarga, y al 3 % suspende (con slock, como al cerrar la +# tapa) en vez de dejar que se apague de golpe. Cada umbral actúa una vez; +# al enchufar el cargador se reinicia. Se lanza desde .xinitrc y comprueba +# cada minuto. Suspender usa "sudo -n zzz" (regla en sistema/etc/sudoers.d). BAJA=15 CRITICA=5 +SUSPENDER=3 bat= for b in /sys/class/power_supply/BAT*; do @@ -18,6 +21,12 @@ while :; do estado=$(cat "$bat/status") if [ "$estado" != Discharging ]; then avisado=100 + elif [ "$nivel" -le "$SUSPENDER" ] && [ "$avisado" -gt "$SUSPENDER" ]; then + notify-send -u critical -h string:x-dunst-stack-tag:bateria \ + -i battery-empty "Batería agotada" "Queda un $nivel %: suspendiendo en 10 s" + avisado=$SUSPENDER + sleep 10 + [ "$(cat "$bat/status")" = Discharging ] && sudo -n zzz elif [ "$nivel" -le "$CRITICA" ] && [ "$avisado" -gt "$CRITICA" ]; then notify-send -u critical -h string:x-dunst-stack-tag:bateria \ -i battery-empty "Batería crítica" "Queda un $nivel %: enchufa el cargador ya" diff --git a/home/.xinitrc b/home/.xinitrc index aaab947..3c73f27 100644 --- a/home/.xinitrc +++ b/home/.xinitrc @@ -34,4 +34,10 @@ clipmenud & gammastep & bateria & +# Bloquear con slock tras 30 minutos sin tocar nada, y apagar la pantalla un +# minuto después. Al suspender ya bloquea el hook de zzz (--ignore-sleep). +xset s 1800 1800 +xset dpms 0 0 1860 +xss-lock --ignore-sleep -- slock & + exec dwm diff --git a/install.sh b/install.sh index b29e62d..e6f4736 100755 --- a/install.sh +++ b/install.sh @@ -17,9 +17,9 @@ PAQUETES=" libXrender-devel xorgproto imlib2-devel zlib-devel libxcrypt-devel xorg xinit setxkbmap xrandr dbus elogind polkit - picom dunst libnotify feh maim xclip gammastep + picom dunst libnotify feh maim xclip gammastep xss-lock pipewire wireplumber libspa-bluetooth alsa-pipewire rtkit pavucontrol - NetworkManager bluez blueman acpid chrony + NetworkManager bluez blueman acpid chrony tlp tlp-rdw font-firacode nerd-fonts-symbols-ttf papirus-icon-theme papirus-folders sassc gtk-engine-murrine gnome-themes-extra qt5ct qt6ct @@ -32,7 +32,7 @@ SUCKLESS="dwm st dmenu slstatus slock scroll clipmenu" # Tema GTK Everforest, en una versión fija para que siempre salga igual GTK_TEMA_REPO=https://github.com/Fausto-Korpsvart/Everforest-GTK-Theme GTK_TEMA_COMMIT=9b8be4d6648ae9eaae3dd550105081f8c9054825 -SERVICIOS="dbus elogind polkitd NetworkManager bluetoothd acpid chronyd" +SERVICIOS="dbus elogind polkitd NetworkManager bluetoothd acpid chronyd tlp automontaje" # NetworkManager gestiona la red él solo; estos servicios se pelean con él. SERVICIOS_FUERA="dhcpcd wpa_supplicant" GRUPOS="audio video input network bluetooth" @@ -159,7 +159,15 @@ sistema() { cd "$DIR/sistema" find . -type f | while read -r f; do f=${f#.} - sudo install -D -m "$(stat -c %a ".$f")" ".$f" "$f" + case $f in + /etc/sudoers.d/*) + # git no guarda el modo 440 que exige sudo; y un sudoers + # roto deja sin sudo, así que se valida antes de copiarlo. + visudo -cf ".$f" >/dev/null || die "sudoers inválido: $f" + sudo install -D -m 440 ".$f" "$f" + ;; + *) sudo install -D -m "$(stat -c %a ".$f")" ".$f" "$f" ;; + esac echo " $f" done cd "$DIR" diff --git a/sistema/etc/sudoers.d/zzz b/sistema/etc/sudoers.d/zzz new file mode 100644 index 0000000..f6410a6 --- /dev/null +++ b/sistema/etc/sudoers.d/zzz @@ -0,0 +1,3 @@ +# El script bateria suspende con "sudo -n zzz" cuando la batería llega al +# límite. Solo zzz sin argumentos, sin contraseña, para los de wheel. +%wheel ALL=(root) NOPASSWD: /usr/bin/zzz "" diff --git a/sistema/etc/sv/automontaje/run b/sistema/etc/sv/automontaje/run new file mode 100755 index 0000000..446a65d --- /dev/null +++ b/sistema/etc/sv/automontaje/run @@ -0,0 +1,3 @@ +#!/bin/sh +exec 2>&1 +exec /usr/local/bin/automontaje diff --git a/sistema/usr/local/bin/automontaje b/sistema/usr/local/bin/automontaje new file mode 100755 index 0000000..532a4f3 --- /dev/null +++ b/sistema/usr/local/bin/automontaje @@ -0,0 +1,98 @@ +#!/bin/sh +# Monta solas las memorias USB y tarjetas SD en /mnt/ETIQUETA (o /mnt/sdXY +# si no tienen etiqueta) y las desmonta al sacarlas, avisando con una +# notificación al usuario de la sesión gráfica. Lo lanza runit como root +# (servicio automontaje). Los discos internos no se tocan. + +# Usuario dueño de la sesión de dwm, o el primero normal (UID 1000). +usuario() { + pid=$(pgrep -xo dwm) && stat -c %u "/proc/$pid" || echo 1000 +} + +# avisar TÍTULO TEXTO: notificación en la sesión gráfica, si la hay +avisar() { + pid=$(pgrep -xo dwm) || return 0 + nombre=$(getent passwd "$(stat -c %u "/proc/$pid")" | cut -d: -f1) + entorno=$(tr '\0' '\n' <"/proc/$pid/environ" | + grep -E '^(DISPLAY|DBUS_SESSION_BUS_ADDRESS|XDG_RUNTIME_DIR)=') + # shellcheck disable=SC2086 + env $entorno chpst -u "$nombre" notify-send -i drive-removable-media \ + "$1" "$2" >/dev/null 2>&1 & +} + +montar() { + dev=$1 tipo=$2 etiqueta=$3 + findmnt -rn -S "$dev" >/dev/null && return + + dir=/mnt/$(printf %s "$etiqueta" | tr -c 'A-Za-z0-9._-' _) + if [ "$dir" = /mnt/ ] || mountpoint -q "$dir" || + { [ -d "$dir" ] && [ -n "$(ls -A "$dir")" ]; }; then + dir=/mnt/${dev#/dev/} + fi + + # Los sistemas de archivos de Windows no guardan dueños: se montan a + # nombre del usuario para que pueda escribir sin sudo. + uid=$(usuario) + gid=$(getent passwd "$uid" | cut -d: -f4) + opts=nosuid,nodev,noatime + case $tipo in + vfat) opts=$opts,uid=$uid,gid=$gid,umask=022,utf8,flush ;; + exfat) opts=$opts,uid=$uid,gid=$gid,umask=022 ;; + ntfs) tipo=ntfs3 opts=$opts,uid=$uid,gid=$gid,umask=022 ;; + esac + + mkdir -p "$dir" + if mount -t "$tipo" -o "$opts" "$dev" "$dir"; then + avisar "Memoria montada" "$dir" + else + rmdir "$dir" 2>/dev/null + avisar "No se pudo montar" "$dev ($tipo)" + fi +} + +desmontar() { + dir=$(findmnt -rn -o TARGET -S "$1") || return 0 + case $dir in /mnt/*) ;; *) return ;; esac + umount -l "$dir" && rmdir "$dir" 2>/dev/null + avisar "Memoria desconectada" "$dir" +} + +limpiar() { + accion='' dev='' bus='' uso='' tipo='' etiqueta='' +} + +# Lee eventos de udev (bloques KEY=VALOR separados por una línea vacía). +procesar() { + limpiar + while IFS= read -r linea; do + case $linea in + ACTION=*) accion=${linea#*=} ;; + DEVNAME=*) dev=${linea#*=} ;; + ID_BUS=*) bus=${linea#*=} ;; + ID_FS_USAGE=*) uso=${linea#*=} ;; + ID_FS_TYPE=*) tipo=${linea#*=} ;; + ID_FS_LABEL=*) etiqueta=${linea#*=} ;; + '') + case $bus:$dev in usb:* | *:/dev/mmcblk*) ;; *) dev='' ;; esac + if [ -n "$dev" ]; then + case $accion in + add | change) [ "$uso" = filesystem ] && + montar "$dev" "$tipo" "$etiqueta" ;; + remove) desmontar "$dev" ;; + esac + fi + limpiar + ;; + esac + done +} + +{ + # Primero lo que ya está conectado, luego los eventos nuevos + for d in /sys/class/block/*; do + echo ACTION=add + udevadm info --query=property --path="$d" + echo + done + exec udevadm monitor --udev --property --subsystem-match=block +} | procesar