Es posible que queramos que un determinado script o aplicación se ejecute en el arranque del sistema. Normalmente, cuando instalamos una aplicación mediante apt-get o un instalador, la propia aplicación genere un script de arranque si se trata de un servicio, como por ejemplo apache2 o samba. En otras ocasiones no es así, como por ejemplo si es un programa propio o un script que simplemente deseamos ejecutar en tiempo de arranque, para que realice alguna tarea de mantenimiento.
Para que un script se ejecute en el arranque del sistema, existe un directorio que se destina a tal efecto. Se trata del /etc/init.d y en él residen todos los daemos o scripts de arranque de servicios del sistema. Normalmente son scripts que aceptan parámetros de entrada para indicar la acción que deseamos ejecutar, como start, stop, restart…
Linux, y en especial Raspbian, que es la distribución con la que trabajamos con Raspberry Pi, nos acerca un poco mas a su mantenimiento, y nos ofrece un esqueleto que podemos utilizar como bootstrap (punto de inicio) a la hora de crear nuestros propios scripts. Es algo complejo, y podéis encontrarlo en /etc/init.d/skeleton.
Lo idóneo es utilizar este esqueleto. Así que lo que vamos a hacer es editarlo para ajustarlo a nuestras necesidades.
Primero lo copiamos sobre un nuevo script, que nombraremos según nos convenga. En nuestro ejemplo, myscript:
jordi@raspberrypi ~ $ sudo cp /etc/init.d/skeleton /etc/init.d/myscript
Ahora podemos editarlo:
jordi@raspberrypi ~ $ sudo nano /etc/init.d/myscript
Y lo editamos, de una manera parecida a esta, según nuestras necesidades. Yo he creado, o editado mejor dicho, el siguiente script para adaptarlo al servicio del cliente No-ip.com que hemos creado en esta entrada de mi blog:
#! /bin/sh ### BEGIN INIT INFO # Provides: skeleton # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Example initscript # Description: This file should be used to construct scripts to be # placed in /etc/init.d. ### END INIT INFO # Author: Jordi <jordi@jormc.es> # Do NOT "set -e" # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="No-ip.com client service" NAME=noip2 DAEMON=/usr/sbin/$NAME DAEMON_ARGS="--options args" PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.2-14) to ensure that this file is present # and status_of_proc is working. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ $DAEMON_ARGS \ || return 2 # Add code here, if necessary, that waits for the process to be ready # to handle requests from services started subsequently which depend # on this one. As a last resort, sleep for some time. } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks # and if the daemon is only ever run from this initscript. # If the above conditions are not satisfied then add some other code # that waits for the process to drop all resources that could be # needed by services started subsequently. A last resort is to # sleep for some time. start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return "$RETVAL" } # # Function that sends a SIGHUP to the daemon/service # do_reload() { # # If the daemon can reload its configuration without # restarting (for example, when it is sent a SIGHUP), # then implement that here. # start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; #reload|force-reload) # # If do_reload() is not implemented then leave this commented out # and leave 'force-reload' as an alias for 'restart'. # #log_daemon_msg "Reloading $DESC" "$NAME" #do_reload #log_end_msg $? #;; restart|force-reload) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 exit 3 ;; esac :
No os asustéis, después de todo este tocho solo he tocado 3 líneas:
- línea 13, para poner mi contacto (aunque el script no es mío, o no al menos el servicio al que llama)
- línea 19, para añadir una pequeña descripción del script
- línea 20, el nombre del script en sí, noip2
- línea 21, para indicar dónde se encuentra el ejecutable de nuestro servicio, en /usr/local/bin
Ahora ya podemos guardarlo y prepararlo para ser ejecutado:
jordi@raspberrypi ~ $ sudo chmod +x /etc/init.d/noip2 jordi@raspberrypi ~ $ sudo update-rc.d -f noip2 defaults 99 update-rc.d: using dependency based boot sequencing insserv: Script noip2 is broken: incomplete LSB comment. insserv: missing `Required-Start:' entry: please add even if empty. insserv: missing `Required-Stop:' entry: please add even if empty. insserv: missing `Default-Start:' entry: please add even if empty. insserv: missing `Default-Stop:' entry: please add even if empty. insserv: script noip2 provides system facility $remote_fs, skipped! insserv: script noip2 provides system facility $syslog, skipped! insserv: script noip2 provides system facility $remote_fs, skipped! insserv: script noip2 provides system facility $syslog, skipped! insserv: Default-Start undefined, assuming empty start runlevel(s) for script `noip2' insserv: Default-Stop undefined, assuming empty stop runlevel(s) for script `noip2' insserv: warning: script 'mathkernel' missing LSB tags and overrides
Le hemos dado permisos de ejecución (línea 1) y hemos informado al sistema que debe ejecutarlo en el arranque, para que esté siempre disponible (línea 2).
Ahora ya podemos tratarlo como un servicio mas:
jordi@raspberrypi ~ $ sudo service noip2 status jordi@raspberrypi ~ $ sudo service noip2 stop jordi@raspberrypi ~ $ sudo service noip2 start jordi@raspberrypi ~ $ sudo service noip2 status
Notad que con este ejemplo no obtendremos nada a la salida, ya que el propio ejecutable del noip2 es “silencioso”.
Y con esto ya tenemos nuestro script funcionando. Una cosa muy importante, este proceso no implica un respawn automático. Es decir, si el servicio se mata con in kill -9, no se reiniciará, habrá que ejecutarlo a mano… Eos lo veremos en otro artículo más adelante.
Espero haberos sido de ayuda 😉
Jordi
Pingback: RasPi: cómo gestionar un DNS dinámico | jormc.es