El problema: #
En mi trabajo, tenemos un ordenador que funciona a modo de quiosco en una zona de paso en el área reservada para trabajadores. El objetivo de ese equipo es que cualquier miembro del personal pueda acceder de forma cómoda al ERP de la empresa, con una cuenta que tiene lo básico que cualquier trabajador pueda consultar (horarios, turnos, suplencias, tareas pendientes, contactos, etc.)
Ese ordenador, que facilitaba el equipo de IT de mi empresa del que yo no formo parte, era un ordenador antiguo de 2012 con 4Gb de RAM y un Windows 10 congelado que tardaba 15 minutos en arrancar. El pobre chico que lo enciende y lo apaga estaba harto de toda la liturgia del evento (encender, esperar, credenciales del sistema, esperar, credenciales del ERP, esperar….) así que intenté echar una mano y pedí al equipo de IT que me dejaran trastear el equipo.
Había que automatizar todo lo posible, la única cosa que no quería automatizar era la introducción de las credenciales del ERP, ya que dejarlas almacenadas en un dispositivo público no parecía la mejor idea.
La solución: #
Arranque automático: #
Aquí el primer problema, accedí a la BIOS para programar el arranque automático, pero el RTC de la BIOS de solo me permitía configurar la hora y el día del mes. Yo quería configurar de lunes a viernes… y a ser posible elegir los meses, pero eso era imposible.
Inmediatamente pensé en la opción que creía más profesional. Montar un pequeño servidor con un script para programar un WOL (Wake On Lan). Esa opción la tuve que descartar porque no contaba con autorización.
Al final opté por encenderlo cada día mediante la opción RTC de la BIOS, independientemente que fuera laboral o festivo.
Apagado automático: #
Entonces… ¿el ordenador se enciende siempre? Si, no he encontrado una opción para poder configurarlo de forma cómoda, así que la solución ha sido crear un script en Python que automáticamente apague el ordenador si no es un día laborable o si se ejecuta el script fuera del horario laboral. La idea es que el mismo script se ejecute dos veces al día, una al arrancar el sistema y otra a la hora de cierre de la oficina.
Además, lanzará un prompt en la sesión gráfica, preguntando al usuario si quiere abortar el apagado automático. Si no se responde en los segundos configurados en el script, se apagará automáticamente por omisión,
Advertencia: Este script y la configuración de systemd está diseñado para uso personal del autor y para fines educativos. Usar bajo propio riesgo y cuenta.
Se recomienda revisar manualmente para adaptarlo a tus necesidades y configuraciones. En especial los días y meses laborables, así como la hora de cierre.
##############################################################################
# Licensed under the GNU GPL v3 (https://www.gnu.org/licenses/gpl-3.0.html)
# Filename: auto_shutdown.py
# Version: 1.0
# Author: Jordi Fàbregas
# Contributors:
##############################################################################
'''
**WARNING**
This script is designed to run on Debian KDE workstations. It may work on other
Linux systems with systemd, but it has not been tested and is NOT RECOMMENDED
FOR SERVERS OR MULTI-USER SYSTEMS
This script is designed to be executed with cron or systemd to automatically
shut down the system after working hours.
Alternatively, it can be executed at startup to check if it is a working
weekday or working month. This is designed to troubleshoot some BIOS systems
that can’t define a calendar schedule for automatic power on, by scheduling an
automatic power off.
'''
import sys
import datetime
import subprocess
WORKING_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
CLOSED_MONTHS = ['July', 'August']
SHUTDOWN_AFTER_TIME = "14:10"
KDE_PROMPT = True # Prompt with kdialog to abort before a timeout
KDE_PROMPT_TIMEOUT = 30 # Time in seconds to abort automatic shutdown
# Get current weekday, time and month
def get_current_time():
'''
Get the current weekday, time and month.
Input: (None)
Output: Tuple with:
- String: Weekday name (e.g. 'Monday')
- datetime.time: Current time
- String: Month name (e.g. 'January')
'''
now = datetime.datetime.now()
day = now.strftime("%A") # Monday, Tuesday, etc.
current_time = now.time() # 0-23:59:59
month = now.strftime("%B") # January, February, etc.
return day, current_time, month
def shutdown_system():
'''
Immediately powers off the system.
Input: (None)
Output: (None)
Side effects: Calls 'systemctl poweroff' to shut down the system.
'''
try:
print("System shutdown...", file=sys.stdout)
subprocess.run(["systemctl", "poweroff"], check=True)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
def prompt_shutdown_kde():
'''
Shows a kdialog prompt to confirm shutdown.
If the user cancels, aborts shutdown.
If the user does not respond within timeout, proceeds to shutdown.
'''
print("Out of working hours. Displaying shutdown prompt...")
try:
result = subprocess.run(
[
"kdialog",
"--yesno",
f"El sistema s'apagarà en {KDE_PROMPT_TIMEOUT} segons.\n\n"
"Desitja cancel·lar?"
],
timeout=KDE_PROMPT_TIMEOUT
)
if result.returncode == 0:
print("Shutdown canceled by user.", file=sys.stdout)
else:
print(
f"Scheduling shutdown in {KDE_PROMPT_TIMEOUT} seconds...",
file=sys.stdout
)
shutdown_system()
except subprocess.TimeoutExpired:
print("User did not respond. Shutdown...", file=sys.stdout)
shutdown_system()
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
def main():
'''
Checks if the conditions to shut down the system are met and executes
the shutdown if appropriate.
Input: (None)
Output: (None)
Side effects:
- Calls prompt_shutdown_kde() to display a shutdown prompt
if KDE_PROMPT is True.
- Calls shutdown_system() to power off the system if the conditions
are met.
'''
# Convert shutdown time string to datetime.time
shutdown_hour = datetime.datetime.strptime(
SHUTDOWN_AFTER_TIME, "%H:%M"
).time()
# Get current time data
day, current_time, month = get_current_time()
print(f"Weekday: {day}")
print(f"Current time: {current_time}")
print(f"Month: {month}")
# Check if shutdown conditions are met
should_shutdown = (
month in CLOSED_MONTHS or
day not in WORKING_DAYS or
current_time >= shutdown_hour
)
if should_shutdown:
if KDE_PROMPT:
prompt_shutdown_kde()
else:
shutdown_system()
else:
print(
"Within working hours, no shutdown scheduled.",
file=sys.stdout
)
if __name__ == '__main__':
main()
Ejecución automática del script: #
Para la ejecución automática del script de autoapagado decidí utilizar systemd en vez de cron. Principalmente porque me permite integrar mejor con el entorno gráfico de KDE y la sesión activa del usuario.
Es la primera implementación que hago con systemd, por lo que es posible que contenga algún error o posible optimización.
~/.config/systemd/user/auto-shutdown.service
[Unit]
Description=Auto Shutdown Script
After=graphical-session.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/auto-shutdown.py
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/%u/.Xauthority
StandardOutput=append:/home/%u/.local/share/auto-shutdown.log
StandardError=append:/home/%u/.local/share/auto-shutdown.err
[Install]
WantedBy=default.target
~/.config/systemd/user/auto-shutdown.timer
[Unit]
Description=Run Auto Shutdown Script at 14:15
[Timer]
OnCalendar=*-*-* 14:15:00
Persistent=true
[Install]
WantedBy=timers.target
Finalmente ejecutaremos en la terminal las ordenes para que systemd cargue los scripts al iniciar y a la hora determinada en el timer.
# Recarga los unit files del usuario
systemctl --user daemon-reload
# Habilita el timer al inicio de sesión
systemctl --user enable auto-shutdown.timer
# Arranca el timer ahora
systemctl --user start auto-shutdown.timer