Cómo ejecutar un script usando cron o launchd para una cuenta de usuario Invitado en El Capitan

No pude encontrar una forma de ejecutar un script para la cuenta de invitado durante el inicio de sesión que se ejecuta cada minuto. Dicen que usar cron daemon está en desuso, por lo que parece que usaré launchd con archivos .plist.

Escenario: tengo un iMac público. Quiero permitir que el público en general use la cuenta de invitado y forzar el cierre de sesión cada media hora. Escribí un script Ruby para verificar el tiempo de inicio de sesión y calcular el tiempo restante. Puedo hacer que muestre una notificación de banner cada 10 minutos usando osascript y luego cerrar sesión en mi cuenta. El problema es que cuando trato de implementarlo para la cuenta de invitado, no funciona.

El problema es cuando coloco el archivo .plist dentro de /Library/LaunchDaemons, ya que se ejecuta después de iniciar sesión y también se ejecuta como root. Ejecutar como root es importante ya que puedo tener el privilegio de cerrar procesos cuando se acaba el tiempo. Necesito que se ejecute una vez cada minuto. Este es el archivo plist actual que funciona cuando inicio sesión como mi propio nombre de usuario "propietario" pero no como invitado. Usando org.user.plist

Mi archivo .plist original se veía así

<?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>org.user</string> 
    <key>Program</key> 
        <string>/usr/local/bin/notify-custom</string> 
    <key>RunAtLoad</key> 
        <true/> 
</dict> 
</plist>

Actualización 1 (Todavía no es una solución) Archivo .plist que se ejecuta cada 10 segundos tanto para Invitado como para mi nombre de usuario

<?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>org.user</string>
    <key>ProgramArguments</key>
        <string>/usr/local/bin/notify-custom</string>
    <key>WatchPaths</key>
        <array>        
            <string>/Users/Guest/Library</string>
            <string>/Users/owner/Library</string>
        <array>
    </integer>
</dict>
</plist>

Como prueba para asegurarme de que aparece el banner de notificación de osascript, tengo este código dentro de /usr/local/bin/notify-custom

#/bin/bash

#Using whoami would have shown me logged in as root under LaunchDaemon .plist
loggedinUser=`finger | awk 'NR==3{print $1}'`
#I need to manually run terminal and type sudo as guest for nextline to work
sudo -u $loggedinUser /usr/bin/osascript -e 'display notification "Test" with title "Banner Notification"'

La solución está abajo.

El archivo .plist que está mostrando posiblemente no podría cargarse ya que no hay una clave de programa ni de argumentos de programa especificada.
Lo siento, sé que olvidé pegar esa sección. Consulte la revisión anterior. (Haciéndome pensar que tal vez debería intentar usar la clave ProgramArguments seguido de un solo elemento de matriz que es el nombre del programa, ya que no toma ningún otro argumento).
¿Qué es notify-customy es un binario o un script, y si es el último, cuál es el contenido del script?
Es un script rubí. Está en el trabajo. Lo publicaré aquí mañana si crees que ayudará.
Acabo de probar con mi propio archivo .plist, propiedad rootdel wheelgrupo con 0644permisos /Library/LaunchAgentsy se ejecutó cuando inicié sesión como Invitado. Con la excepción de la cadena para la clave del programa , el resto del archivo era el mismo. Funcionó como se esperaba. También miro a los otros LaunchAgents en la misma ubicación y verifico en el Monitor de actividad que sus procesos también comenzaron. Entonces, sin saber qué notify-customes y su contenido, no hay mucho más que pueda ofrecer en este momento, aparte de los archivos .plist en el /Library/LaunchAgentstrabajo en la cuenta de invitado.
Además, los archivos .plist no tenían atributos extendidos. No programo en Ruby, por lo que el script no me ayudará a depurar la situación.
Hmmm, solo estaba buscando el banner para que apareciera en mi script Ruby. Utilicé ruby ​​ya que pasaba variables entre el shell y permitía una cadena más fácil para la manipulación y resta de tiempo, ya que no podía hacer que funcionara completamente a través del shell solo. ¿Qué tipo de secuencia de comandos utilizó para interactuar con la cuenta de invitado? ¿Tienes una muestra?
@Michael ¡Por favor, no modifique demasiado la pregunta básica! Mejor agregue una sección de actualización, haga una segunda pregunta o solicite más explicaciones. En su ejemplo, modificó la plist de una manera que mi respuesta no tiene mucho sentido porque la integró en parte en su pregunta (actualizada).
@Michael BTW para habilitar sin contraseña sudo shutdownpara un usuario invitado que modifique el archivo /etc/sudoers debería funcionar.
@klanomath. Lo lamento. Tienes razón. Pensé en mi error al modificar la pregunta original después de enviar la actualización. Quería dejar en claro que usar .plist como Daemon ejecutándose como root no inicia osascript. No estoy seguro de por qué querría modificar el archivo sudoers ya que el demonio se ejecuta como root y no debería necesitar permisos adicionales para usar el comando sudo. ¿Tener sentido?
OK, lo hice funcionar con tu idea de usar el archivo sudoers. Usando este método, tuve que colocar el archivo plist dentro de la carpeta LaunchAgents para que obtenga los permisos de los usuarios registrados. Intenté recuperar la primera respuesta a continuación, ya que eso hizo obvio que tenía un problema con mi archivo plist original mencionado anteriormente, pero no tengo suficientes puntos en stackexchange para hacerlo.
Propongo lo siguiente: Reescriba su pregunta a: pregunta original; update1 basado en answer1 a medio camino de trabajo; update2 basado en answer1 o 2 sigue siendo un pequeño problema técnico. Si hizo que todo funcionara como lo desea, publique una respuesta separada (basada en su investigación y/o en la de otros) a su propia pregunta, pero no responda su propia pregunta en la pregunta.

Respuestas (2)

En mi opinión, lo siguiente debería funcionar: ¡lo hace en mi máquina virtual! - lanzado como /Library/LaunchDaemons/org.user.plist:

<?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>org.user</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/touch</string>
        <string>-f</string>
        <string>/Users/Guest/Desktop/test.txt</string>
    </array>
    <key>UserName</key>
    <string>Guest</string>
    <key>GroupName</key>
    <string>_guest</string>
    <key>InitGroups</key>
    <true/>
    <key>WatchPaths</key>
    <array>
        <string>/Users/Guest/Library</string>
    </array>
</dict>
</plist>

Como tarea de ejemplo utilizo /usr/bin/touch -f /Users/Guest/Desktop/test.txt.

El truco aquí es que el contenido completo de la carpeta Invitado se elimina después de cerrar la sesión. Después de que un nuevo invitado inicia sesión, todo el contenido se vuelve a crear desde cero. Tan pronto como se crea la carpeta /Users/Guest/Library, touch ...se inicia la tarea de ejemplo ( ) debido a la clave WatchPaths.

Dado que la tarea/secuencia de comandos/aplicación debe ejecutarse como invitado, no puede usar agentes de lanzamiento porque la ruta /Users/Guest/Library/LaunchAgents/ simplemente no existe.

Utilice un demonio de lanzamiento en su lugar y ejecútelo como Guest/_guest . ¿Su secuencia de comandos ruby ​​/usr/local/bin/notify-custom tiene que ser legible/ejecutable en todo el mundo? por supuesto.


También traté de ejecutar la tarea cada 60 segundos, lo que funciona correctamente pero arroja algunos errores después de que el invitado cierra la sesión. Probablemente sea mejor implementar todo en el script Ruby. Sin embargo, dependiendo de su guión, su kilometraje puede variar.

Si tiene dos tareas diferentes para ejecutar (por ejemplo, mostrar un banner cada 10 minutos con Ruby y un temporizador para forzar el cierre de sesión después de 30 minutos), probablemente sea mejor crear dos demonios de lanzamiento diferentes.

Usted dijo: " Dado que la tarea/secuencia de comandos/aplicación debe ejecutarse como invitado, no puede usar agentes de lanzamiento porque la ruta /Users/Guest/Library/LaunchAgents/ simplemente no existe" . plist se ejecutó correctamente desde la cuenta de invitado cuando se ejecutó desde /Library/LaunchAgentsy sin el uso de WatchPaths la clave , así como lo hicieron otros LaunchAgents desde la misma ubicación.
@ user3439894 Hmm, estoy muy interesado en ver la notificación personalizada ... ;-)
@ user3439894 Un problema adicional de usar un agente de lanzamiento es que el agente/secuencia de comandos también se ejecuta al iniciar sesión como usuario normal, lo que probablemente no sea deseado.
En la máquina en la que probé esto, tengo 3 LaunchAgents /Library/LaunchAgents, el que creé para esta prueba y 2 existentes que fueron agregados por aplicaciones que se ejecutan en el sistema, por ejemplo, Little Snitch. Entonces, en general, no veo un problema al usar LaunchAgents para tomar medidas en la cuenta de invitado. Dicho esto, ciertamente puede depender exactamente de lo que uno está tratando de hacer que puede hacer que el uso de LaunchAgents en /Library/LaunchAgentseste escenario de caso de uso no sea ideal. Solo puedo seguir mis pruebas y funcionó para mí de la manera en que probé. No sé qué más puedo decir.

Resuelto. He estado trabajando en esto por un tiempo. Mi solución finalmente hace lo que necesito y es que se inicia durante el inicio de sesión para el usuario Invitado (y como opción, también lo tengo iniciando para mí, el usuario iMac1, solo para mostrar el tiempo de inicio de sesión). No vi una forma sencilla de poner el archivo org.user.plist en /Users/Guest/Library/LaunchAgents que teóricamente lo habría lanzado cuando el Invitado inició sesión y la razón por la que abandoné esa situación es porque esa carpeta no se crea hasta el inicio de sesión.

Lo que hice fue poner mi archivo .plist dentro de /Library/LaunchAgents/ que se ejecuta para cada usuario. Eso está bien ya que mi código distinguirá al usuario invitado y tomará medidas (en este caso, cerrará la sesión después del tiempo establecido).

El archivo .plist final:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
        <string>org.user</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/notify-custom</string>
    </array>
    <key>RunAtLoad</key>
        <true/>
    <key>StartInterval</key>
        <integer>60</integer>
</dict>
</plist>

Tenga en cuenta que agregué una clave RunAtLoad ya que sin ella, el script se ejecutó pero esperó un minuto para activar su primer evento. Si, en cambio, utilicé la clave WatchPaths como señaló @klanomath en su comentario, entonces el script se ejecutaría cada 10 segundos, ya que debe haber actividad en esa carpeta con regularidad. Solo quería que se ejecutara cada 60 segundos por ahora. Podría cambiar ese temporizador más adelante cuando limpie todo el procedimiento con algunos diálogos de advertencia más coloridos escritos en Python.

Aquí está el código Ruby dentro de /usr/local/bin/notify-custom que se ejecuta para cada inicio de sesión de usuario:

#!/usr/bin/ruby -w

require 'time'
require 'FileUtils'

loggedinUser=`finger|awk 'END{print $1}'`.strip
getloginTime=`finger|awk 'END{print}'|cut -c49-53`
getnowTime=`date|awk 'NR==1{print $4}'`[0..4]
loginTime=(Time.parse(getloginTime).to_i)
nowTime=(Time.parse(getnowTime).to_i)
diffSec=(nowTime-loginTime)
diffMin=(diffSec/60)
timeRemain=30-diffMin

#To see some console output while debugging
puts "getloginTime      =#{getloginTime}"
puts "getnowTime        =#{getnowTime}"
puts "loginTime=#{loginTime}"
puts "nowTime  =#{nowTime}"
puts "timeRemain=#{timeRemain}"

if loggedinUser == "Guest"
        open("/Users/#{loggedinUser}/Desktop/30 Minutes Max Use Per Day",'a'){|f| f.puts "With this new iMac, you are limited to a maximum of 1/2 hour use per day"}
        if timeRemain < 0
            `/usr/bin/osascript -e 'tell application "Finder" to set desktop picture to POSIX file "/Library/Desktop Pictures/Earth Horizon.jpg"'`
            `/usr/bin/osascript -e 'display notification "SHUTTING DOWN! Now= #{getnowTime}   LoggedInAt=#{getloginTime}   TimeRemain=#{timeRemain}" with title "Guest SHUTTING DOWN" sound name "Glass"'`
            `/usr/bin/osascript -e 'tell app "Terminal" to do script "sudo shutdown -h now"'`
        else
            `/usr/bin/osascript -e 'display notification "Now= #{getnowTime}     TimeRemain=#{timeRemain}" with title "#{loggedinUser} TIME LOGGED IN= #{getloginTime}" subtitle "User= #{loggedinUser}"'`
        end
else
        `/usr/bin/osascript -e 'display notification "Now= #{getnowTime}     TimeRemain=#{timeRemain}" with title "#{loggedinUser} TIME LOGGED IN= #{getloginTime}" subtitle "User= #{loggedinUser}"'`
end

Nuevamente, tenga en cuenta que si usa LaunchDaemons en su lugar, se ejecutan bajo la cuenta raíz del sistema mientras que los Agentes se ejecutan en la cuenta de los usuarios que iniciaron sesión. Usando la segunda opción, tuve que dar permiso al usuario Invitado para ejecutar Sudo shutdown como @klanomath mencionado a continuación. Esto se hizo ejecutando el comando: $sudo visudo y agregando lo siguiente al final del archivo:

Guest ALL=NOPASSWD: /sbin/shutdown

También quería mostrar solo la cuenta de invitado en la página de inicio de sesión, así que oculté mi cuenta de esa pantalla con este comando:

sudo dscl . create /Users/hiddenuser IsHidden 1

y si cambia de opinión, puede traerlo de vuelta con:

sudo dscl . create /Users/hiddenuser IsHidden 0

Gracias @klanomath y @user3439894