El toolkit está completo y programado, pero cron empieza a quedarse corto. No sabe esperar a que la red esté lista antes de lanzar el vigilante. No recupera la ejecución del informe si el servidor estaba apagado a las 06:30. No limita la memoria de una tarea que se desmande, ni aísla su acceso al sistema de ficheros. Y su registro está disperso en cuatro logs que hay que consultar uno a uno. systemd resuelve las cuatro cosas, a cambio de más ficheros y más sintaxis. Esta lección enseña lo justo de systemd que necesita un script de Bash: cómo se declara un servicio, cómo se programa con un temporizador, y cuándo migrar de cron merece la pena y cuándo no.

Contenido

  1. Qué es systemd, unidades y dónde viven
  2. Anatomía de un .service, y oneshot frente a simple
  3. Anatomía de un .timer
  4. OnCalendar y cómo validarlo
  5. Persistent=true: lo que cron no puede hacer
  6. Cron frente a timers: la tabla honesta
  7. Comandos del día a día
  8. Endurecer una unidad
  9. Servicios de larga duración escritos en Bash
  10. Unidades de usuario y loginctl enable-linger
  11. Migrar una entrada de cron paso a paso
  12. Aplicación: el toolkit sobre systemd

  1. Qué es systemd, unidades y dónde viven

systemd es el proceso número 1 de casi todas las distribuciones actuales: el primero que arranca el núcleo y el que pone en marcha todo lo demás. Su trabajo es gestionar unidades —servicios, montajes, sockets, temporizadores— y las dependencias entre ellas: qué debe estar listo antes de qué. Un script de Bash se lo encuentra por tres caminos: los timers como alternativa moderna a cron, los servicios cuando algo debe correr continuamente y reiniciarse si muere, y la consulta —ya has usado systemctl is-active en 06-03 y journalctl -u cron en 07-01 sin llamarlo systemd—. No hace falta dominarlo: con las quince directivas de esta lección cubres el 95 % de lo que necesita un script de operaciones. Una unidad es un fichero de texto con formato INI. Los tipos que nos interesan son .service (un proceso que se ejecuta), .timer (cuándo se activa otra unidad) y .target (un punto de sincronización, como network-online.target). Y tres ubicaciones, con precedencia de menor a mayor:

Ruta Quién manda ahí Uso
/usr/lib/systemd/system/ Los paquetes de la distribución No editar: una actualización lo sobrescribe
/etc/systemd/system/ El administrador Aquí van tus unidades; gana sobre la anterior
~/.config/systemd/user/ Cada usuario, las suyas Unidades de usuario, sin sudo (apartado 10)

La regla es la de 05-06: lo que trae el sistema no se toca, lo tuyo va en /etc. Si necesitas cambiar una sola directiva de una unidad del sistema, systemctl edit nombre.service crea un fragmento en …/nombre.service.d/override.conf que se superpone sin sustituir el original.

  1. Anatomía de un .service, y oneshot frente a simple

# /etc/systemd/system/veloz-informe.service
[Unit]
Description=Informe diario de envios de Veloz Envios
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=veloz
Group=veloz
WorkingDirectory=/home/veloz/veloz-ops
EnvironmentFile=-/home/veloz/veloz-ops/etc/veloz-ops.conf
Environment=LC_ALL=C
ExecStart=/home/veloz/veloz-ops/bin/informe-diario.sh
TimeoutStartSec=900
StandardOutput=journal
StandardError=journal

En [Unit] van metadatos y dependencias: Description es lo que verás en systemctl status, y After=network-online.target significa «no arranques antes de que la red esté lista», con Wants= para activarla si no lo estaba —esto es exactamente lo que cron no sabe hacer con @reboot (07-01)—. En [Service] va cómo se ejecuta. Type=oneshot para un proceso que hace su trabajo y termina (el bloque siguiente); User/Group para no correr como root si no hace falta; WorkingDirectory fija el directorio de trabajo, aunque igualmente usarás rutas absolutas; EnvironmentFile=-/ruta carga pares CLAVE=valor como entorno y el guion inicial evita que falle si el fichero no existe; Environment= define una variable suelta y se repite tantas veces como haga falta; TimeoutStartSec es el timeout de 07-02 integrado; y StandardOutput/StandardError deciden dónde va la salida, con journal como valor por defecto hoy. Dos avisos sobre ExecStart. La ruta debe ser absoluta siempre: systemd no tiene PATH que valga y fallará con «No such file or directory» aunque el script esté en el tuyo. Y no hay shell: ExecStart=/bin/mi.sh > /tmp/salida.log no redirige nada, porque el > se le pasa al script como argumento literal; si necesitas construcciones de shell, invócalas explícitamente con bash -c '…'. En la práctica no hace falta, porque StandardOutput=journal ya captura la salida. Y falta la sección [Install], que dice qué pasa al hacer enable: en un servicio disparado por un timer no se pone, porque no queremos que arranque al iniciar el sistema sino cuando lo diga el temporizador.

Type=oneshot frente a Type=simple

Esta elección confunde a todo el mundo, y equivocarse produce síntomas raros:

Type=simple Type=oneshot
Para qué Procesos de larga duración Tareas que terminan
Se considera «arrancado» En cuanto lanza el proceso Cuando el proceso termina con éxito
Estado tras terminar failed (murió inesperadamente) inactive (dead), que es lo correcto
Con timers Mal Sí, es lo normal (admite varios ExecStart)

Tus tareas de operaciones son todas oneshot: hacen su trabajo y salen. Un Type=simple para ellas haría que systemctl status mostrara la unidad como fallida cada vez que termina correctamente; al revés, un servicio de larga duración declarado oneshot dejaría a systemd esperando eternamente a que «arranque». RemainAfterExit=yes es un complemento de oneshot que deja la unidad como active tras terminar, para tareas que establecen un estado (montar algo, aplicar una configuración), no para las periódicas.

  1. Anatomía de un .timer

Un timer activa otra unidad en un momento dado, y la relación es por nombre: veloz-informe.timer dispara veloz-informe.service sin declararlo (se puede cambiar con Unit=).

# /etc/systemd/system/veloz-informe.timer
[Unit]
Description=Programa el informe diario de Veloz Envios a las 06:30
[Timer]
OnCalendar=*-*-* 06:30:00
Persistent=true
RandomizedDelaySec=120
AccuracySec=1s
[Install]
WantedBy=timers.target

Las directivas de [Timer] vienen en dos familias. La de reloj es OnCalendar=, que dispara en momentos concretos como cron. Las relativas son OnBootSec= (tanto tiempo después de arrancar el sistema), OnStartupSec= (después de arrancar systemd), OnUnitActiveSec= (después de la última ejecución) y OnUnitInactiveSec= (después de que terminara). A eso se suman Persistent=true (apartado 5), RandomizedDelaySec= y AccuracySec=. OnUnitActiveSec merece atención porque resuelve algo que cron hace mal. OnBootSec=5min más OnUnitActiveSec=5min significa «cinco minutos después de arrancar, y luego cada cinco minutos desde que terminó la anterior». Con cron, */5 dispara según el reloj aunque la ejecución previa siga viva; aquí el intervalo se cuenta desde la ejecución real, así que no hay solapamiento por diseño y el flock deja de ser imprescindible —aunque conviene mantenerlo, porque protege también de las ejecuciones a mano—. RandomizedDelaySec es la respuesta al problema de @daily: si veinte servidores tienen el mismo timer, el retraso aleatorio reparte la carga. Y AccuracySec permite a systemd agrupar activaciones para ahorrar energía; por defecto es un minuto, así que si necesitas puntualidad ponlo a 1s explícitamente. Este es también el gran ausente de cron: la granularidad. OnUnitActiveSec=30s es perfectamente válido; en cron, «cada 30 segundos» no existe.

  1. OnCalendar y cómo validarlo

La sintaxis es DíaDeSemana Año-Mes-Día Hora:Minuto:Segundo, con * para «cualquiera» y /N para pasos:

Expresión Significado En cron
*-*-* 06:30:00 Todos los días a las 06:30 30 6 * * *
daily / hourly Medianoche / cada hora en punto @daily / @hourly
*-*-* *:0/5:00 (o *:0/5) Cada 5 minutos */5 * * * *
Mon..Fri *-*-* 09:00:00 / *-*-01 04:00:00 Laborables a las 09:00 / el día 1 de cada mes 0 9 * * 1-5 / 0 4 1 * *
Mon *-*-01..07 05:00:00 El primer lunes de cada mes (no se puede expresar)

Y aquí viene una de las mejores razones para usar timers: puedes comprobar la expresión antes de instalarla con systemd-analyze calendar '*-*-* 06:30:00' --iterations=3.

Normalized form: *-*-* 06:30:00
    Next elapse: Tue 2026-08-04 06:30:00 CEST  (in 11h 59min)

Te dice si la expresión es válida, cómo la interpreta y cuándo se ejecutaría; con --iterations muestra las siguientes veces, que es la forma definitiva de comprobar que «cada cinco minutos» significa lo que crees. Cron no ofrece nada parecido: allí la única forma de comprobarlo es esperar. OnCalendar acepta además sufijos de zona horaria (Mon *-*-* 03:00:00 UTC), lo que resuelve limpiamente el problema del cambio de hora que viste en 07-01.

  1. Persistent=true: lo que cron no puede hacer

Escenario real: el respaldo está programado a las 03:15 y el servidor pasa la noche apagado por mantenimiento, arrancando a las 09:00. Con cron, la ejecución de las 03:15 simplemente no ocurrió, y nadie lo notará hasta que haga falta ese respaldo. Persistent=true cambia eso: systemd guarda en disco cuándo se ejecutó por última vez cada timer y, al arrancar, comprueba si se saltó alguna activación; si es así, la lanza inmediatamente. Es la funcionalidad de anacron (07-01) integrada, sin herramienta aparte y con granularidad completa. Debe estar puesto en toda tarea cuya ejecución importe —informes, respaldos, limpiezas— y debe estar quitado en las que solo tienen sentido en su momento: una comprobación de estado de hace ocho horas no aporta nada al arrancar. Combínalo con RandomizedDelaySec para que las tareas atrasadas no se lancen todas a la vez.

  1. Cron frente a timers: la tabla honesta

Criterio cron Timer de systemd
Complejidad para empezar Una línea Dos ficheros y tres órdenes
Validar el horario / granularidad No se puede / un minuto systemd-analyze calendar / un segundo
Ejecuciones perdidas Se pierden Persistent=true las recupera
Dependencias (red, montajes) No las conoce After=, Wants=, Requires=
Registro Donde tú redirijas, un fichero por tarea Journal centralizado, journalctl -u
Solapamiento Hay que poner flock No se solapa por diseño
Entorno Mínimo y sorprendente Explícito con Environment/EnvironmentFile
Aislamiento y límites No MemoryMax, ProtectSystem, PrivateTmp
Portabilidad Cualquier Unix Solo sistemas con systemd

La conclusión sensata: cron para lo simple y portátil, timers para lo que importa. Una limpieza de temporales sigue estando bien en cron; un respaldo que no debe perderse, una tarea que necesita la red o un servicio que debe reiniciarse solo piden un timer. No hay obligación de migrarlo todo: convivir es normal.

  1. Comandos del día a día

Después de crear o modificar cualquier unidad hay que recargar con sudo systemctl daemon-reload; olvidarlo es el error número uno con systemd.

Orden Qué hace
systemctl enable --now x.timer / start x.service Activa el timer y lo arranca ya / ejecuta la tarea ahora, a mano
systemctl status x.service / list-timers --all Estado y últimas líneas / todos los timers con próxima y última ejecución
systemctl cat x.timer / systemd-analyze verify x.service La unidad tal como la ve systemd / errores de sintaxis
journalctl -u veloz-informe --since today -p err Los errores de hoy de esa tarea

list-timers es la vista que sustituye a crontab -l, y da bastante más información:

NEXT                         LEFT      LAST                         PASSED  UNIT
Mon 2026-08-03 18:35:00 CEST 4min 12s  Mon 2026-08-03 18:30:00 CEST 47s ago veloz-vigilante.timer
Tue 2026-08-04 03:15:00 CEST 8h 44min  Mon 2026-08-03 03:15:00 CEST 15h ago veloz-respaldo.timer

De un vistazo sabes qué viene, cuándo fue lo último y si algo lleva demasiado sin ejecutarse: ese LAST es justo el dato que check_respaldo (07-04) tenía que deducir mirando directorios. Y systemctl start merece un elogio aparte, porque prueba la tarea exactamente en las condiciones en que se ejecutará —mismo usuario, mismo entorno, mismos límites—: es el env -i de 07-01, pero de verdad.

  1. Endurecer una unidad

Una ventaja que cron no puede ofrecer: limitar lo que la tarea puede hacer aunque tenga un fallo.

NoNewPrivileges=true          # no puede escalar privilegios ni con setuid
ProtectSystem=strict          # todo el sistema de ficheros en solo lectura...
ReadWritePaths=/respaldos /home/veloz/veloz-ops/logs   # ...salvo estas rutas
ProtectHome=read-only         # los /home de otros usuarios, solo lectura
PrivateTmp=true               # un /tmp propio y aislado; MemoryMax=512M pone tope de memoria

Con esto, un fallo en tu script de respaldo no puede escribir fuera de /respaldos y logs/, y PrivateTmp elimina toda una familia de ataques por ficheros temporales predecibles: defensa en profundidad casi gratis, con las implicaciones completas en 08-03. Empieza suave (ProtectSystem=full, PrivateTmp=true, NoNewPrivileges=true) y endurece después probando con systemctl start: si la tarea deja de funcionar, ya sabes qué ruta falta en ReadWritePaths.

  1. Servicios de larga duración escritos en Bash

A veces sí quieres un proceso permanente: un vigilante que comprueba cada 10 segundos, o algo que consume una cola. La unidad pasa a Type=simple y añade Restart=on-failure (relanza si termina con código distinto de cero; always relanza incluso tras una salida limpia), RestartSec=10 para esperar entre intentos, y StartLimitBurst=5 con StartLimitIntervalSec=300 para evitar el bucle infinito: si falla cinco veces en cinco minutos, systemd se rinde y deja la unidad en failed, que es lo correcto porque un reinicio que no arregla nada solo oculta el problema. Restart= no arregla un script mal escrito: reiniciar cada 10 segundos algo que falla por una ruta mal puesta solo llena el journal. Debe además atender a SIGTERM, que es lo que envía systemctl stop, retomando el trap de 05-03:

SEGUIR=1
termina() { veloz_log_info "SIGTERM recibido; terminando ordenadamente"; SEGUIR=0; }
trap termina TERM INT
while (( SEGUIR )); do
    procesa_lote || veloz_log_warn "lote fallido; continuo"
    for (( i = 0; i < 10 && SEGUIR; i++ )); do sleep 1; done
done
veloz_log_info "salida limpia"; exit 0

Dos detalles lo hacen funcionar. La bandera SEGUIR permite terminar el trabajo en curso antes de salir, en lugar de morir a la mitad dejando un fichero corrupto. Y la espera se trocea en sleep de un segundo en lugar de sleep 10, porque Bash no interrumpe un sleep largo hasta que termina: con sleep 10, systemctl stop tardaría hasta diez segundos en surtir efecto y systemd acabaría enviando SIGKILL. Si el script no responde a SIGTERM, systemd espera TimeoutStopSec (90 s por defecto) y luego mata a lo bruto, con toda la corrupción que eso pueda causar.

  1. Unidades de usuario y loginctl enable-linger

No todo necesita sudo: cada usuario tiene su instancia de systemd y sus unidades en ~/.config/systemd/user/.

mkdir -p ~/.config/systemd/user      # copiar ahi las unidades, sin User= ni Group=
systemctl --user daemon-reload && systemctl --user enable --now veloz-informe.timer

Es igual con --user añadido y sin User=/Group=, ya que corre como tú; la ventaja es que no necesitas privilegios y la unidad vive en tu $HOME, versionable con el toolkit. Hay una trampa: por defecto, la instancia de usuario muere cuando cierras la sesión, así que tus timers dejan de dispararse. La solución es sudo loginctl enable-linger velozlinger es «permanecer»—, que mantiene la instancia de ese usuario aunque no tenga sesión abierta; sin ella, una unidad de usuario funciona mientras estás conectado y misteriosamente deja de funcionar cuando te vas. Para tareas de servidor lo habitual sigue siendo unidades del sistema con User=veloz.

  1. Migrar una entrada de cron paso a paso

Tomemos la línea del vigilante (*/5 * * * * flock -n … vigilante.sh >> … 2>&1) en seis pasos. Paso 1: el servicio, donde va todo lo que no sea el horario; es el del apartado 2 cambiando estas directivas.

# veloz-vigilante.service
After=network-online.target veloz-api.service
ExecStart=/home/veloz/veloz-ops/bin/vigilante.sh
TimeoutStartSec=120
SuccessExitStatus=1

# veloz-vigilante.timer
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
AccuracySec=10s
[Install]
WantedBy=timers.target

SuccessExitStatus=1 es un ajuste fino: el vigilante devuelve 1 cuando hay algún WARNING, y no queremos que systemd marque la unidad como fallida por un aviso; la redirección al log desaparece porque la salida va al journal. Paso 2: el timer, donde se elige OnUnitActiveSec en lugar de OnCalendar=*:0/5 para que el intervalo se cuente desde que terminó la ejecución anterior y nunca se solapen, sin Persistent=true porque una comprobación atrasada no interesa. Paso 3: validar con systemd-analyze verify …{service,timer}. Paso 4: recargar y probar a mano con daemon-reload, systemctl start veloz-vigilante.service y journalctl -u veloz-vigilante -n 30 --no-pager. Paso 5: activar con systemctl enable --now veloz-vigilante.timer y comprobar con list-timers. Paso 6: quitar la línea de cron —se olvida constantemente, y el resultado es la tarea ejecutándose dos veces—: coméntala en lugar de borrarla, con la fecha y el motivo.

  1. Aplicación: el toolkit sobre systemd

Unidad Programación Persistent Por qué
veloz-informe.timer OnCalendar=*-*-* 06:30:00 true El informe debe existir aunque el servidor arrancara tarde
veloz-respaldo.timer OnCalendar=*-*-* 03:15:00 true Un respaldo perdido es el peor de los fallos
veloz-vigilante.timer OnUnitActiveSec=5min (no) Una comprobación atrasada no aporta nada
sudo systemctl daemon-reload
sudo systemctl enable --now veloz-{informe,respaldo,vigilante}.timer
systemctl list-timers 'veloz-*'; journalctl -u veloz-respaldo --since today

Compara lo ganado frente al crontab de 07-01: el informe se recupera si el servidor estuvo apagado, el vigilante espera a la red y no se solapa nunca, los tres logs se consultan con la misma orden filtrando por unidad, list-timers muestra el estado de todo de un vistazo, y systemctl start prueba cada tarea en condiciones idénticas a las reales.

Errores Comunes y Consejos

  • Olvidar daemon-reload, o activar el .service en vez del .timer. Sin recarga, editas la unidad y no pasa nada; y enable veloz-informe.service no programa nada, porque se activa el timer.
  • Type=simple en una tarea que termina. La unidad aparece como fallida cada vez que funciona bien. Usa oneshot.
  • Redirecciones, comodines o rutas relativas en ExecStart. No hay shell: >, | y $VAR se pasan literalmente, y una ruta relativa falla siempre.
  • Dejar la línea de cron tras migrar, o una unidad de usuario sin enable-linger. Lo primero ejecuta la tarea dos veces; lo segundo funciona mientras tienes sesión y deja de funcionar al desconectar.
  • sleep largo en un servicio, o Restart=always sobre un fallo real. Lo primero hace que systemctl stop acabe en SIGKILL (trocea la espera); lo segundo oculta el problema y llena el journal (usa on-failure con StartLimitBurst).
  • Consejo: valida siempre con systemd-analyze calendar antes de instalar un horario, y con --iterations=5 para ver las próximas cinco ejecuciones. Descubrir a la semana que tu «cada 5 minutos» era «a las 5 de la mañana» duele.

Ejercicios

Ejercicio 1. Escribe el par .service + .timer para respaldo.sh: debe ejecutarse a las 03:15 como usuario veloz, esperar a que los sistemas de ficheros estén montados, recuperar la ejecución si el servidor estuvo apagado, no tardar más de una hora, y solo poder escribir en /respaldos y en el directorio de logs.

Ejercicio 2. Escribe una función veloz_estado_timer que reciba el nombre de un timer y devuelva 0 si está activo y su última ejecución fue correcta, 1 si lleva más tiempo del esperado sin ejecutarse, y 2 si el servicio asociado está en estado fallido.

Soluciones

Solución 1.

# veloz-respaldo.service
[Unit]
Description=Respaldo diario de datos y configuracion de Veloz Envios
RequiresMountsFor=/respaldos /srv/veloz/datos
[Service]
Type=oneshot
User=veloz
Group=veloz
ExecStart=/home/veloz/veloz-ops/bin/respaldo.sh
TimeoutStartSec=3600
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/respaldos /home/veloz/veloz-ops/logs

# veloz-respaldo.timer
[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target

RequiresMountsFor= es más preciso que un After=local-fs.target genérico: garantiza que esos puntos de montaje concretos estén disponibles, que es lo que un respaldo necesita —si /respaldos es un disco externo o un NFS que aún no ha montado, el script escribiría en el directorio vacío del sistema local sin darse cuenta—. Y ProtectHome=read-only permite leer ~/veloz-ops pero no escribir: por eso ReadWritePaths incluye explícitamente el directorio de logs.

Solución 2.

# veloz_estado_timer <nombre.timer> [horas_maximas]
# Codigos: 0 ok | 1 lleva demasiado sin ejecutarse | 2 el servicio fallo | 3 no existe
veloz_estado_timer() {
    local timer="${1:?falta el timer}" maximo="${2:-26}" servicio="${1%.timer}.service"
    systemctl list-timers --all --no-legend "$timer" | grep -q . ||
        { veloz_log_error "$timer no existe o no esta activo"; return 3; }
    [[ $(systemctl is-failed "$servicio") == failed ]] &&
        { veloz_log_error "$servicio esta en estado fallido"; return 2; }
    local segundos ultima=$(systemctl show "$servicio" -p ExecMainExitTimestamp --value)
    [[ -z $ultima ]] && { veloz_log_warn "$servicio no se ha ejecutado nunca"; return 1; }
    segundos=$(( $(date +%s) - $(date -d "$ultima" +%s) ))
    (( segundos > maximo * 3600 )) &&
        { veloz_log_warn "$timer sin ejecutarse desde hace $(( segundos / 3600 ))h"; return 1; }
    veloz_log_info "$timer correcto (ultima ejecucion hace $(( segundos / 60 )) min)"
}

La pieza interesante es systemctl show -p PROPIEDAD --value, que es la forma legible por máquina de consultar systemd: systemctl status está pensado para humanos y su formato cambia entre versiones, mientras que show devuelve el valor pelado y es estable. ExecMainExitTimestamp da la fecha en un formato que date -d entiende, así que la resta en segundos desde época (04-06) da directamente la antigüedad. is-failed distingue el fallo real de «nunca se ejecutó», que merecen respuestas distintas. Esta función es la comprobación que faltaba en vigilante.sh (07-04): vigilar que las propias tareas programadas siguen funcionando.

Conclusión

systemd gestiona unidades, y a un script de Bash le interesan dos: el .service (cómo se ejecuta) y el .timer (cuándo). Van en /etc/systemd/system/ o, las de usuario, en ~/.config/systemd/user/ con loginctl enable-linger para sobrevivir al cierre de sesión. En el servicio, Type=oneshot para tareas que terminan y simple para las que no, ExecStart con ruta absoluta y sin construcciones de shell, User/Group para no correr como root, EnvironmentFile=- para cargar la configuración, TimeoutStartSec como el timeout integrado y StandardOutput=journal para centralizar el registro. En el timer, OnCalendar para horarios de reloj —validable con systemd-analyze calendar, algo que cron no ofrece—, la familia OnBootSec/OnUnitActiveSec para intervalos relativos que no se solapan por diseño, Persistent=true para recuperar lo perdido con el servidor apagado, y RandomizedDelaySec para repartir la carga. El día a día son seis órdenes: daemon-reload tras cada cambio (el olvido más común), enable --now sobre el timer y no sobre el servicio, start para probar la tarea en condiciones idénticas a las reales, status y journalctl -u para ver qué pasó, y list-timers como sustituto muy mejorado de crontab -l. Añade el endurecimiento casi gratuito de NoNewPrivileges, PrivateTmp, ProtectSystem y ReadWritePaths, y recuerda que en un servicio de larga duración Restart=on-failure con StartLimitBurst no arregla un script mal escrito, y que hay que atender a SIGTERM con un trap y troceando los sleep. Al migrar de cron, seis pasos y uno que se olvida siempre: quitar la línea del crontab, o la tarea se ejecutará dos veces. Con esto, las cuatro tareas del toolkit corren solas, se recuperan de un apagado, esperan a la red y comparten registro. Pero todo esto pasa en un solo servidor, y en producción hay tres —srv-veloz-01, srv-veloz-02 y srv-veloz-03—: comprobar el estado de la flota entrando a mano en cada uno no es automatización. En 07-06 damos el salto: ssh desde el punto de vista de un script, autenticación por clave y sus riesgos, ~/.ssh/config con reutilización de conexión, las opciones sin las que un ssh dentro de un bucle se come la entrada estándar, bloques remotos con here-document, y recorridos de la flota en paralelo con manejo de errores individual. Nace flota.sh.

Curso de Programación en Bash

Módulo 1: Introducción a Bash

Módulo 2: Comandos Básicos de Bash

Módulo 3: Fundamentos de Scripting

Módulo 4: Scripting Intermedio

Módulo 5: Técnicas Avanzadas de Scripting

Módulo 6: Trabajando con Herramientas Externas

Módulo 7: Automatización y Programación

Módulo 8: Mejores Prácticas y Optimización

Módulo 9: Proyectos del Mundo Real

© Copyright 2026. Todos los derechos reservados