Para iniciar un servicio automáticamente en Yocto, configura INIT_MANAGER = "systemd" en tu distro, escribe una receta que herede la systemd bbclass, e incluye WantedBy=multi-user.target en la sección [Install] de tu archivo de servicio. Si falta alguna de estas tres piezas, tu servicio falla silenciosamente en el inicio.
Pero hacerlo bien implica más que esas tres líneas. Necesitas entender cómo la variable INIT_MANAGER de Yocto interactúa con DISTRO_FEATURES, cómo escribir archivos de servicio para targets embebidos, y cómo evitar los errores que atrapan incluso a desarrolladores experimentados.
Esta guía cubre todo desde añadir servicios systemd hasta 7 errores comunes, basada en Yocto Scarthgap 5.0 LTS (systemd 255).
Habilitar systemd con INIT_MANAGER
Yocto usa sysvinit como sistema init por defecto. Necesitas cambiar explícitamente a systemd.
Enfoque INIT_MANAGER (Zeus 3.0+)
Añade una sola línea a conf/local.conf o conf/distro/mydistro.conf.
# conf/local.conf
INIT_MANAGER = "systemd"
Esta sola línea carga init-manager-systemd.inc, que configura automáticamente lo siguiente.
# Contenido de init-manager-systemd.inc (rama Scarthgap)
DISTRO_FEATURES:append = " systemd usrmerge"
DISTRO_FEATURES_BACKFILL_CONSIDERED:append = " sysvinit"
VIRTUAL-RUNTIME_init_manager ??= "systemd"
VIRTUAL-RUNTIME_initscripts ??= "systemd-compat-units"
VIRTUAL-RUNTIME_login_manager ??= "shadow-base"
VIRTUAL-RUNTIME_dev_manager ??= "systemd"
ROOT_HOME ?= "/root"
El enfoque legacy (Pre-Zeus)
Antes de Zeus, tenías que configurar manualmente las variables DISTRO_FEATURES y VIRTUAL-RUNTIME_* en local.conf. Esto sigue funcionando pero el enfoque de una línea con INIT_MANAGER es más simple.
Verificar la configuración
# Verificar que systemd esté en DISTRO_FEATURES
bitbake-getvar DISTRO_FEATURES | grep systemd
Escribir una receta de servicio systemd
Para añadir un servicio systemd en Yocto, escribe una receta que herede la bbclass systemd.
Ejemplo de receta completa
# meta-mylayer/recipes-apps/myapp/myapp_1.0.bb
SUMMARY = "My custom application"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://myapp.sh \
file://myapp.service"
S = "${WORKDIR}"
inherit systemd
SYSTEMD_SERVICE:${PN} = "myapp.service"
SYSTEMD_AUTO_ENABLE:${PN} = "enable"
do_install() {
install -d ${D}${bindir}
install -m 0755 ${WORKDIR}/myapp.sh ${D}${bindir}/myapp
install -d ${D}${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
}
FILES:${PN} += "${systemd_system_unitdir}/myapp.service"
Variables clave
| Variable | Propósito | Valor predeterminado |
|---|---|---|
SYSTEMD_SERVICE:${PN} | Nombre(s) de archivo(s) de servicio a gestionar | ${PN}.service |
SYSTEMD_AUTO_ENABLE:${PN} | Si se habilita automáticamente | enable |
SYSTEMD_PACKAGES | Paquetes a gestionar con systemd | ${PN} |
Lo que inherit systemd hace automáticamente:
- Genera un script
postinstque ejecutasystemctl enable - Crea enlaces simbólicos bajo
multi-user.target.wants/ - Genera un script
prermpara la eliminación del paquete
Proteger tu receta para systemd
Para prevenir errores de compilación en distribuciones sin systemd, usa REQUIRED_DISTRO_FEATURES.
REQUIRED_DISTRO_FEATURES = "systemd"
Esto hace que la receta se salte cuando DISTRO_FEATURES no incluye systemd. Si otra receta depende de ella, se genera un error de compilación, facilitando rastrear la causa raíz.
Añadir a una receta existente vía bbappend
Si necesitas añadir un servicio systemd a una receta existente, usa un archivo bbappend. Consulta el Patrón 3 en la guía práctica de bbappend para los detalles. Ten en cuenta que inherit systemd no puede usarse en archivos .bbappend — la receta base .bb ya debe heredar la clase systemd.
Diseño de archivos de servicio para embebidos
Cómo escribes el archivo de servicio afecta directamente la fiabilidad del arranque. Estos son los puntos clave para Linux embebido.
Estructura básica
# myapp.service
[Unit]
Description=My Application Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/myapp
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Sección [Unit]
After= controla el orden de arranque. Un error común con servicios que dependen de la red es usar After=network.target.
- network.target — indica que los dispositivos de red han sido configurados. No garantiza que se haya asignado una dirección IP
- network-online.target — indica que la red es utilizable. Usa esto cuando tu servicio necesite hacer llamadas API o conexiones de red
Sección [Service]
Type= especifica cómo se inicia el proceso.
| Tipo | Caso de uso |
|---|---|
simple | Proceso en primer plano (predeterminado) |
forking | Daemon que se bifurca al fondo |
oneshot | Script que se ejecuta una vez y termina |
Para embebidos, configura Restart=on-failure con RestartSec=5 (reintentar después de 5 segundos) para habilitar la recuperación automática de caídas.
Permisos
Instala los archivos de servicio con 0644. Añadir permisos de ejecución (0755) puede hacer que systemd emita advertencias.
# Correcto
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
# Incorrecto (permiso de ejecución es innecesario)
install -m 0755 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
Recetas con Timer y Socket Activation
Las fortalezas de systemd van más allá de los servicios. Los timers gestionan tareas periódicas, y la activación por socket habilita el inicio bajo demanda.
Unidades Timer
Usa timers de systemd en lugar de cron para mantener las dependencias y logs integrados con systemd.
# myapp.timer
[Unit]
Description=Run myapp periodically
[Timer]
OnBootSec=1min
OnUnitActiveSec=1h
Persistent=true
[Install]
WantedBy=timers.target
# myapp.service (activado por el timer)
[Unit]
Description=My Application Task
[Service]
Type=oneshot
ExecStart=/usr/bin/myapp --run-task
En la receta, lista ambos archivos en SYSTEMD_SERVICE.
SRC_URI = "file://myapp.sh \
file://myapp.service \
file://myapp.timer"
SYSTEMD_SERVICE:${PN} = "myapp.service myapp.timer"
do_install() {
install -d ${D}${bindir}
install -m 0755 ${WORKDIR}/myapp.sh ${D}${bindir}/myapp
install -d ${D}${systemd_system_unitdir}
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
install -m 0644 ${WORKDIR}/myapp.timer ${D}${systemd_system_unitdir}/
}
FILES:${PN} += "${systemd_system_unitdir}/myapp.service \
${systemd_system_unitdir}/myapp.timer"
Activación por socket
La activación por socket retrasa el inicio del servicio hasta que llega una conexión real. Esto es efectivo para dispositivos embebidos con recursos limitados.
# myapp.socket
[Unit]
Description=My Application Socket
[Socket]
ListenStream=8080
Accept=no
[Install]
WantedBy=sockets.target
# myapp.service (iniciado por el socket)
[Unit]
Description=My Application
Requires=myapp.socket
[Service]
Type=simple
ExecStart=/usr/bin/myapp
La sintaxis de la receta es la misma que para los timers. Lista tanto el servicio como el socket en SYSTEMD_SERVICE.
SYSTEMD_SERVICE:${PN} = "myapp.service myapp.socket"
7 errores comunes y soluciones
Estos son los problemas más frecuentes reportados en las listas de correo y foros de Yocto.
1. systemd no está en DISTRO_FEATURES
El error más común. Aunque tengas inherit systemd en tu receta, si la configuración de tu distribución no tiene systemd habilitado, nada funciona.
# Verificar
bitbake-getvar DISTRO_FEATURES | grep systemd
# Solución: añadir a conf/local.conf
INIT_MANAGER = "systemd"
2. Falta WantedBy en la sección [Install]
Si el archivo de servicio no tiene sección [Install] o le falta WantedBy=, systemctl enable no hace nada. No se crea ningún enlace simbólico, y el servicio no se iniciará automáticamente.
# Obligatorio
[Install]
WantedBy=multi-user.target
3. Falta :$ en SYSTEMD_SERVICE
# Incorrecto (sin calificador de paquete)
SYSTEMD_SERVICE = "myapp.service"
# Correcto
SYSTEMD_SERVICE:${PN} = "myapp.service"
SYSTEMD_SERVICE es una variable por paquete. Sin :${PN}, la bbclass systemd no puede generar correctamente el script postinst.
4. Instalar en /etc/systemd/system/
# Incorrecto (/etc/ es para overrides del usuario)
install -d ${D}/etc/systemd/system/
# Correcto
install -d ${D}${systemd_system_unitdir}
# → /usr/lib/systemd/system/
/etc/systemd/system/ es donde los usuarios hacen overrides locales de unidades. Las unidades instaladas por paquetes pertenecen a ${systemd_system_unitdir} (/usr/lib/systemd/system/).
5. Esperar disponibilidad de red con network.target
# network.target: indica que los dispositivos de red están configurados
# ≠ la red está realmente utilizable
# Cuando necesitas conectividad de red real
[Unit]
After=network-online.target
Wants=network-online.target
network.target solo significa que las interfaces han sido configuradas. No garantiza la asignación de IP ni la resolución DNS.
6. Permisos de ejecución en archivos de servicio
# Incorrecto (systemd puede emitir advertencias)
install -m 0755 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
# Correcto
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
Los archivos de servicio son archivos de configuración, no ejecutables. 0644 es el permiso correcto.
7. Logs de journald perdidos al reiniciar
Por defecto, journald almacena los logs en RAM (/run/log/journal/). Los logs desaparecen al reiniciar.
Para persistir los logs, crea el directorio /var/log/journal/ en tu receta. Puedes controlar la retención con journald.conf.
do_install:append() {
install -d ${D}${localstatedir}/log/journal
}
FILES:${PN} += "${localstatedir}/log/journal"
Para dispositivos embebidos, considera la frecuencia de escritura en Flash. Las escrituras frecuentes de logs acortan la vida útil del Flash. Establece límites con SystemMaxUse= y MaxRetentionSec= en journald.conf.
FAQ
¿Puedo usar systemd y sysvinit juntos en Yocto?
Sí. Scarthgap maneja sysvinit como un elemento de DISTRO_FEATURES_BACKFILL_CONSIDERED, así que ambos pueden coexistir. Sin embargo, INIT_MANAGER = "systemd" deshabilita los initscripts de sysvinit por defecto. Si necesitas scripts sysvinit específicos, añade DISTRO_FEATURES:append = " sysvinit" explícitamente.
¿Cómo verifico si mi servicio está habilitado en el target?
Ejecuta systemctl is-enabled myapp.service en el dispositivo. Si devuelve enabled, el enlace simbólico bajo multi-user.target.wants/ existe. Si devuelve static, el servicio no tiene sección [Install] y no puede habilitarse.
¿Cuál es la diferencia entre systemd_system_unitdir y /etc/systemd/system/?
${systemd_system_unitdir} se resuelve a /usr/lib/systemd/system/ — aquí es donde los paquetes instalan archivos de unidad. /etc/systemd/system/ es para overrides del administrador local. Las unidades en /etc/ tienen precedencia sobre las de /usr/lib/. En recetas Yocto, siempre usa ${systemd_system_unitdir}.
¿Puedo usar inherit systemd en un archivo bbappend?
No. La directiva inherit solo funciona en archivos .bb. Si la receta base ya tiene inherit systemd, tu bbappend puede configurar SYSTEMD_SERVICE:${PN} y añadir archivos de servicio. Si la receta base no hereda systemd, necesitarás un enfoque diferente — consulta la guía de bbappend para más detalles.
¿Cómo depuro un servicio que no arranca?
En el target, ejecuta systemctl status myapp.service y journalctl -u myapp.service para ver los logs. Durante la compilación, verifica que el script postinst se generó con bitbake -e myapp | grep postinst. La guía de depuración de errores cubre estas herramientas en profundidad.
¿El valor predeterminado de SYSTEMD_AUTO_ENABLE es "enable"?
Sí. Si no configuras SYSTEMD_AUTO_ENABLE:${PN}, el valor predeterminado es enable. Configúralo a disable si quieres que el servicio se instale pero no se inicie automáticamente en el arranque.
¿Cómo añado un servicio systemd a un rootfs de solo lectura?
Con un rootfs de solo lectura, systemctl enable durante postinst no funcionará porque /etc/ no es escribible. En su lugar, crea el enlace simbólico previamente en do_install() vinculando tu archivo de servicio a ${D}${sysconfdir}/systemd/system/multi-user.target.wants/. Este es un patrón común para targets con actualización OTA donde el rootfs es inmutable.
Conclusión
Hacer que los servicios systemd funcionen en Yocto requiere tres cosas.
- Configuración de distribución: Habilitar con
INIT_MANAGER = "systemd" - Receta:
inherit systemdy configurarSYSTEMD_SERVICE:${PN} - Archivo de servicio: Incluir
WantedBy=multi-user.targeten la sección[Install]
La activación por timer y socket te permite integrar tareas periódicas e inicio bajo demanda en systemd también.
La mayoría de los errores se reducen a configuración faltante. Cuando las cosas no funcionan, empieza verificando DISTRO_FEATURES con bitbake-getvar y ejecutando systemctl status myapp en el dispositivo. La guía de depuración de errores de compilación cubre estas herramientas en detalle.
Para una inmersión más profunda en recetas systemd y desarrollo de recetas Yocto, estos libros son referencias sólidas.
Artículos relacionados:
- Cómo escribir recetas Yocto: Guía de archivos .bb
- Yocto bbappend: Guía práctica con 5 patrones
- Primeros pasos con Yocto: Compila tu primera imagen Linux
- Errores de compilación en Yocto: Guía de depuración por tarea
- Raspberry Pi 5 con Yocto Scarthgap: Guía práctica
- Actualización OTA en Yocto: SWUpdate vs RAUC vs Mender
- Guía de optimización de velocidad de compilación Yocto
- Guía de creación de capas personalizadas en Yocto