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
- Qué es systemd, unidades y dónde viven
- Anatomía de un
.service, yoneshotfrente asimple - Anatomía de un
.timer OnCalendary cómo validarloPersistent=true: lo que cron no puede hacer- Cron frente a timers: la tabla honesta
- Comandos del día a día
- Endurecer una unidad
- Servicios de larga duración escritos en Bash
- Unidades de usuario y
loginctl enable-linger - Migrar una entrada de cron paso a paso
- Aplicación: el toolkit sobre systemd
- 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.
- Anatomía de un
.service, y oneshot frente a simple
.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=journalEn [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.
- Anatomía de un
.timer
.timerUn 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.targetLas 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.
OnCalendar y cómo validarlo
OnCalendar y cómo validarloLa 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.
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.
Persistent=true: lo que cron no puede hacer
Persistent=true: lo que cron no puede hacerEscenario 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.
- 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.
- 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.
- 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 memoriaCon 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.
- 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 0Dos 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.
- Unidades de usuario y
loginctl enable-linger
loginctl enable-lingerNo 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.timerEs 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 veloz —linger 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.
- 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.targetSuccessExitStatus=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.
- 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 todayCompara 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.serviceen vez del.timer. Sin recarga, editas la unidad y no pasa nada; yenable veloz-informe.serviceno programa nada, porque se activa el timer. Type=simpleen una tarea que termina. La unidad aparece como fallida cada vez que funciona bien. Usaoneshot.- Redirecciones, comodines o rutas relativas en
ExecStart. No hay shell:>,|y$VARse 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. sleeplargo en un servicio, oRestart=alwayssobre un fallo real. Lo primero hace quesystemctl stopacabe enSIGKILL(trocea la espera); lo segundo oculta el problema y llena el journal (usaon-failureconStartLimitBurst).- Consejo: valida siempre con
systemd-analyze calendarantes de instalar un horario, y con--iterations=5para 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.targetRequiresMountsFor= 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
- ¿Qué es Bash?
- Configurando tu Entorno
- Navegación Básica en la Línea de Comandos
- Entendiendo el Shell
- Encontrar Ayuda: man, help y --help
Módulo 2: Comandos Básicos de Bash
- Operaciones con Archivos y Directorios
- Comandos de Procesamiento de Texto
- Permisos y Propiedad de Archivos
- Redirección y Tuberías
- Comodines y Expansión de Rutas
- Historial y Atajos de Teclado
Módulo 3: Fundamentos de Scripting
- Creando y Ejecutando un Script
- Variables y Constantes
- Operadores Básicos
- Sentencias Condicionales
- Argumentos y Entrada del Usuario
- Comillas, Expansión y Sustitución
Módulo 4: Scripting Intermedio
- Bucles en Bash
- Funciones en Bash
- Arrays y Arrays Asociativos
- Manipulación de Cadenas
- La Sentencia case y los Menús Interactivos
- Aritmética y Cálculos Numéricos
Módulo 5: Técnicas Avanzadas de Scripting
- Operaciones Avanzadas con Archivos
- Gestión de Procesos
- Manejo de Errores y Depuración
- Expresiones Regulares
- Entrada/Salida Avanzada: Descriptores y Here-Documents
- Scripts Modulares y Librerías Reutilizables
Módulo 6: Trabajando con Herramientas Externas
Módulo 7: Automatización y Programación
- Trabajos Cron
- Automatizando Tareas
- Scripts de Respaldo y Restauración
- Monitoreo y Registro
- Servicios y Temporizadores con systemd
- Automatización Remota con SSH
Módulo 8: Mejores Prácticas y Optimización
- Escribiendo Código Legible
- Optimizando Scripts en Bash
- Consideraciones de Seguridad
- Control de Versiones con Git
- Análisis Estático con ShellCheck y shfmt
- Pruebas Automatizadas con Bats
- Portabilidad: POSIX sh frente a Bashismos
