macOS: creando tus propios daemons como un profesional

Los launchAgents son dependientes de un usuario. Así, es necesario que macOS inicie una cuenta de usuario para que los launchAgents de ese usuario se pongan en marcha e inicien los procesos a los que hacen referencia. Estos procesos se ejecutan en segundo plano y son capaces de interactuar con el interfaz del sistema, cosa que los daemons no pueden hacer. A los daemons no se les permite conectar con el servidor de ventanas[note]por lo que no tienen interfaz[/note] y no dependen de un usuario sino que se arrancan con el propio sistema.

Ambos dos, como hemos visto antes, están gestionados por launchd que obliga a ejecutar esos procesos bajo unos parámetros que están almacenados en unos archivos plist en tres ubicaciones del disco duro concretas: una de ellas específica del sistema, una segunda para todos los usuarios del ordenador y una tercera[note]que pueden ser varias[/note] por cada usuario que tiene una cuenta en el Mac.

Así nos vamos a encontrar con dos carpetas por cada uno de estos niveles, almacenadas en la correspondiente Biblioteca: éstas carpeta LaunchAgents y LaunchDaemons y están ubicadas en:

  • Para el sistema en /System/Library/
  • Para todos los usuarios en /Library/
  • Para cada usuario en ~/Library/

Por defecto, ninguna aplicación debería poder acceder o instalar ni daemons ni launchAgents en la Librería del Sistema como medida de seguridad. De hecho, los permisos para ambas carpetas para lectura y escritura solo pertenecen al sistema, mientras que wheel y el resto de los usuarios pueden leerlas.

Los archivos plist, por otra parte, no son más que archivos de configuración con una estructura XML concreta, destinada a pasar una serie de parámetros o instrucciones a realizar por aplicaciones. Al ser archivos de texto, es sencillo poder escribir nuestros propios servicios o modificar los existentes[note]siempre sabiendo lo que tenemos entre manos[/note]. Lo más importante que debes saber al respecto de estos archivos plist es que hacen referencia a las aplicaciones que van a ejecutar, lo que es muy interesante sobre todo para hacer un seguimiento forense de seguridad.

Cuando inspeccionamos un plist para ambos tipos de carpeta, podemos encontrar un <string> que suele hacer referencia a la aplicación[note]que no tiene por qué ser una aplicación “de escritorio” tal como la conocemos[/note] que se arrancará al inicio o de una forma puntual  se ejecutará como un proceso más que podemos controlar en el Monitor de Actividad.

Muchas aplicaciones que instalamos crean este tipo de archivos plist, especialmente aquellas que gestionan o sobrepasan servicios del sistema, como por ejemplo drivers para hardware específico[note]ratones, teclados, servicios de red, aplicaciones que monitorizan el comportamiento de macOS o esperan órdenes del mismo, etc.)[/note] y cuando desinstalamos éstas aplicaciones, generalmente estos archivos plist se suelen quedar atrás. Adicionalmente, el malware suele utilizar este tipo de archivos para lanzar launchAgents y que las aplicaciones dependientes del malware se ejecuten a plazos regulares para recopilar información y enviarla a los servidores que la recogen o para recibir órdenes de los mismos. Es por esto porque Apple limita el acceso al sistema y a sus partes desde hace algunas versiones de macOS con rootless, para evitar que se instalen este tipo de archivos de forma automática, aunque es factible que puedan instalarse como archivos de usuario, que no del sistema, haciendo su asquerosa labor en segundo plano.

Es, por esto, una buena idea mantener controlados los plist que se instalan en las correspondientes carpetas y adicionalmente, controlar a las aplicaciones a las que referencian en busca no solo de malware, sino de información para la completa desinstalación de un driver o de una aplicación. Especialmente críticas son las propias carpetas de usuario y la librería general para todos los usuarios, que serán posiblemente el destino para esos archivos plist.

También es el caso de que te puedes encontrar en los ítems de arranque con un daemon o aplicación que se ejecuta cada vez que arrancas tu usuario: la retiras utilizando el interfaz pero al volver a arrancar el ordenador o a iniciar sesión vuelve a estar allí, y aparentemente no hay forma de eliminarla: el proceso es buscar el archivo plist correspondiente[note]cuyo nombre debería identificar fácilmente el daemon o aplicación al que hace referencia el ítem de arranque dentro del archivo de texto[/note], recuperar la ruta para luego poder eliminarla junto con el archivo plist.

Hay diferentes maneras de mantener controladas estas carpetas, pero para una gestión inmediata de las mismas, es factible utilizar las Acciones de Carpeta para que generen un aviso cuando se añada un nuevo plist a a cada una de ellas. La forma de realizar esto es muy sencilla; navega por la estructura de archivos hasta las correspondientes carpetas de la lista:

  • /Library/LaunchAgents
  • /Library/LaunchDaemons
  • /System/Library/LaunchAgents
  • /System/Library/LaunchDaemons
  • ~/Library/LaunchAgents

Invoca el menú contextual para acceder a los Servicios y selecciona la opción Configuración de Acciones de Carpeta. Añade una acción del tipo “add-new ítem alert.scpt” y así, cuando se añada algún tipo de archivo a esas carpetas (deberás repetir esta acción para cada una de ellas) se te notificará con un cuadro de diálogo en el que se te indica en qué carpeta se ha añadido y si quieres acceder a la misma para verificar el contenido de la carpeta e inspeccionar el archivo.

Construyendo daemons

En ocasiones trabajamos con aplicaciones que son tan críticas que deben estar siempre en marcha en ordenadores no atendidos, pero puede ocurrir que las podemos cerrar por error, o se cierran inesperadamente y hay que volverlas a arrancar manualmente lo que es un problema porque a lo mejor estamos lejos del Mac. Vamos a solucionar este problema. Ten en cuenta que el sistema de daemons está pensado para aplicaciones que se cargan antes de iniciar la sesión de usuario para aplicaciones del sistema que se ejecutan en segundo plano y nosotros vamos a forzar un poco su uso.

La idea es que cuando la aplicación se cierre, sea por la circunstancia que sea, se vuelva a abrir automáticamente y se mantenga activa. Así que para empezar abre TextEdit y crea un documento en modo texto puro (usa ⌘⇧T para saltar entre el modo de texto enriquecido y texto puro) y añade al documento este texto, ya que vamos a crear un daemon para esta tarea. launchd se encargará de lanzar la aplicación[note]Revisa cada 10 segundos si una aplicación está activa[/note] si por cualquier motivo esta cerrada, aunque hay algunos puntos específicos que trataremos después.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>user.launchkeep.stickies</string>
  <key>KeepAlive</key>
  <true/>
  <key>Program</key>
  <string>/Applications/Stickies.app/Contents/MacOS/Stickies</string>
</dict>
</plist>

Vamos a detallar como se personaliza este .plist que luego activaremos.

Para empezar está el primer storing que hace referencia a user.launchkeep.stickies que realmente es el nombre que le daremos al archivo cuando lo guardemos. En el ejemplo hace referencia a la aplicación Notas adhesivas, pero, por ejemplo, si quisiéramos hacer un daemon para Mail, entonces lo renombraríamos como user.launchkeep.Mail.

El siguiente paso es definir la ruta a la aplicación que se mantendrá siempre abierta. La referencia es a la aplicación ejecutable que está dentro del paquete de la aplicación, no a la app que nosotros visualizamos en el interfaz de OS X. Si te fijas en la ruta del ejemplo es fácil definir la estructura para otra aplicación ya que se mantiene igual para todas las aplicaciones así que para Mail, por ejemplo sería /Applications/Mail.app/Contents/MacOS/Mail.

Ahora ya tienes personalizado el .plist, que guardaremos con el nombre que hemos definido en string. En este caso, el nombre de archivo sería user.launchkeep.Mail.plist.

Este sistema va bien para ciertos tipos de aplicaciones, y Mail es una de ellas, pero no para otros tipos de aplicaciones que necesitan actualizarse con frecuencia, como aplicaciones que has descargado desde la App Store. Lo que ocurre es que cuando tienes que actualizar esa aplicación desde la Mac App Store por ejemplo, es necesario que la aplicación esté cerrada para que pueda actualizarse, pero… ¡ups!, launchd la mantiene viva a través del daemon con lo cual no puedes actualizarla. Lo mismo ocurre al intentar reiniciar el ordenador: launchd reabre la aplicación a través del daemon y se interrumpe el proceso de apagado y por lo tanto, el reinicio. Así que hay que tocar alguna cosa más.

En la mayoría de los casos, una mejor solución es usar KeepAlive junto con SuccessfulExit.

KeepAlive y SuccessfulExit

La idea es crear un plist que permita mantener una aplicación en ejecución todo el tiempo a menos que salga de forma limpia[note]es decir, que el usuario haya tomado la decisión de cerrarla[/note]. Utiliza KeepAlive pero añade una etiqueta al XML llamada SuccessfulExit. Si una aplicación se cierra “con éxito[note]técnicamente, con un código de salida = 0[/note] entonces la aplicación no se reiniciará automáticamente. Sin embargo, si la aplicación se cuelga[note]el código de salida no es igual a 0[/note], se reiniciará automáticamente. Aquí está el código relevante del código de lanzamiento:

<key>KeepAlive</key>
 <dict>
 <key>SuccessfulExit</key>
 <false/>
 </dict>

Esto es útil si tienes una aplicación que ocasionalmente se bloquea, especialmente si es una aplicación que se ejecuta en segundo plano o en la barra de menús donde es posible que no te des cuenta inmediatamente.

KeepAlive y NetworkState

Otra opción es KeepAlive y NetworkState, que indica a la aplicación que siga funcionando mientras tengamos una conexión de red.

Aquí está la parte de la lista que trata específicamente de la pregunta que se hará el archivo plist “¿hay conexión a la red?”:

<key>KeepAlive</key>
 <dicto>
 <key>NetworkState</key>
 <true/>
 </dict>

Hay que tener en cuenta dos casos dentro de esta opción:

  • El ejecutable considerará que la “red” está “conectada” si tiene una dirección IP. Sin embargo, es posible que tu red local pueda estar en funcionamiento, pero tu conexión a Internet no funcione. Por ejemplo, en este momento mi ISP está fuera de línea, pero estoy conectado a mi red Wi-Fi local, por lo que en lo que respecta a launchd, la “red” está activa. Recuerda que “La red está arriba” no significa necesariamente “Internet está arriba/accesible”.
  • También es importante recordar que la reapertura de la aplicación no tendrá lugar sólo porque la conexión de red se interrumpa. La única vez que KeepAlive se usaría es si la aplicación se cierra y es vuelta a abrir por launchd, En ese momento, se leerá el plist por parte del sistema y responderá a la pregunta “¿Está lista la red?” y si la respuesta es afirmativa, abrirá la aplicación. Si la respuesta es no, no abrirá la aplicación.

Ahora que he explicado un par de ejemplos, hay que echar un vistazo a la documentación de Apple porque es fascinante. Por ejemplo, en la documentación, hay un ejemplo de creación de una tarea que se ejecuta cada 300 segundos[note]5minutos[/note] y aún más interesante, cómo pasar comandos de Terminal como argumentos de programa. La documentación de Apple, aunque técnica, es relativamente fácil de comprender y abre muchas puertas muy interesantes a la personalización del comportamiento del Mac.

¿Dónde guardar este archivo?

La ruta para guardar este archivo es:

/Library/LaunchAgents

Para todos los usuarios, o en

~/Library/LaunchAgents

solo para nuestro usuario.

¿Por qué en esta ubicación y no en LauchDaemons? Porque los daemons guardados en esta primera ubicación se activan antes de iniciar la sesión de usuario y a nosotros nos interesa que se ejecute después de iniciar la sesión de usuario, así que esa es la ubicación correcta.

Ahora vamos a cargar el daemon, así que abre el Terminal y usa el comando:

launchctl load ~/Library/LaunchAgents/user.launchkeep.Mail.plist

Ahora, si se cierra la aplicación, ya sea manualmente o por un problema, inmediatamente se volverá a abrir. Ten en cuenta que esto va a impedir el reinicio o el apagado del Mac porque cuando se mande una señal para el cierre de todas las aplicaciones, esta se volverá a abrir interrumpiendo el reinicio o el apagado así que para desactivar este comportamiento usaremos previamente:

launchctl remove user.launchkeep.Mail

Este tipo de acciones se utilizan para ordenadores que están permanentemente encendidos y sin atención, de forma que nos aseguramos que ciertas aplicaciones estén siempre activas.

Si buscamos utilizar este tipo de uso en un ordenador de usuario tendremos que automatizar la carga del daemon  en el inicio de sesión de usuario usando una acción de Automator, por ejemplo, y luego crear una miniaplicación también en Automator que desactive el daemon y luego apague o reinicie el Mac.

0 0 votos
Article Rating
Subscribe
Notify of
2 Comments
Oldest
Newest Most Voted
Opiniones Inline
Ver todos los comentarios
ChoPraTs
2 years ago

Hola Carlos.

A mí me gustaría editar el archivo plist de una aplicación que está en LaunchAgents. Es una aplicación que se inicia con el sistema pero cuyo icono se queda en el dock nada más arrancar el sistema operativo. Una vez iniciado el sistema, si pulso en el icono de esta aplicación y cierro manualmente su ventana (o uso la combinación de teclas CMD+W) entonces la aplicación ya sí se queda ejecutándose en segundo plano y el icono solo es visible en la barra de menús, como yo desearía que hiciese de forma automática al arrancar.

Así que me pregunto si existe alguna forma de editar ese archivo plist para incluir en él algún código que haga este proceso automáticamente nada más iniciarse la aplicación. Entiendo que sólo haría falta solicitar a la aplicación que, una vez iniciada, cierre su ventana (¿simulando el atajo de teclado CMD+W quizás?). Entiendo que por defecto la aplicación ya se quedaría ejecutándose en segundo plano y en la barra de menús como hace cuando ejecuto esto manualmente. No sé si esto es posible o si existe algún código específico para que lo haga automáticamente.

Otra opción que se me ocurre es crear un script que también se inicie con el sistema y haga todo el proceso automáticamente, convoque a la aplicación en cuestión y cierre la ventana principal. Pero si pudiese hacerse en el propio archivo plist añadiendo alguna línea de código sencilla pienso que sería lo ideal.

¿Tú sabrías cómo hacer esto?

¡Muchas gracias de antemano!

bladerunner
Responder a  ChoPraTs
2 years ago

Depende de la aplicación.

Lo que describes no es un daemon, es una aplicación que arranca con el sistema. A no ser que haya una versión de dicha aplicación que sea solo CLI, no creo que haya manera de hacer lo que tu quieres solo a través de launchd. A no ser que la aplicación tenga un modo residente, pero como te digo, depende de la propia aplicación. De todos modos, no soy un experto en launchd, porque es único a macOS. Mucho tiempo atrás me acuerdo de trabajar con una aplicación que permitía editar todos los agentes, pero se volvió de pago y ya no volví a usarla.

Si te decantas por hacer un script te recomiendo Platypus (https://sveinbjorn.org/platypus).

2
0
Me encantaría saber tu opinión, por favor, deja un comentariox
()
x