Saturday 6 January 2018

A run-once system for linux

Current development of the Linux image for the community centre I'm involved with requires me to be able to carry out tasks just once (a "run once" capability). Moreover, I have need to run such tasks at three different stages in the overall process:
  1. At start up before the network is active (to manipulate network definitions)
  2. At start up after the network is active but before users log in (in my case to incorporate the IP address into a workspace wallpaper)
  3. At shut down, after the users are logged off but while the filesystems are still mounted.

The run-once script

I have a run-once script /usr/local/bin/runonce, based on one I found here.
RUNONCE_DIR=$1
[ -z $RUNONCE_DIR ] && RUNONCE_DIR=/etc/local/runonce.d

[ ! -d $RUNONCE_DIR/ran ] && mkdir -p $RUNONCE_DIR/ran

for FILE in $RUNONCE_DIR/*; do
    [ -d "$FILE" ] && continue
    COMMAND_NAME=$(basename $FILE)
    logger -it runonce -p local3.info "Running: $FILE"
    "$FILE"
    mv "$FILE" "$RUNONCE_DIR/ran/$COMMAND_NAME-$$-$(date +%Y%m%d-%H%M%S)"
    logger -it runonce -p local3.info "Ran: $FILE"
done


I use it in all three places, with three directories /etc/local/runonce-a.d, /etc/local/runonce-b.d and so on.  The parameter to the runonce job specifies which one to use.

The three jobs are then controlled by systemd unit files (I'm just getting used to systemd ..), as follows:

runonce-a.service

# Run-once service; runs any executable in /etc/local/runonce-a.d
# Ensuring that it runs before the network is active
[Unit]
Description=Run once programs (pre network)

Before=network-pre.target
Wants=network-pre.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/runonce /etc/local/runonce-a.d

[Install]
WantedBy=network.target

runonce-b.service

# Run-once service; runs any executable in /etc/local/runonce-b.d
# Ensuring that it runs after the network is active
[Unit]
Description=Run once programs (post-network)

After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/runonce /etc/local/runonce-b.d

[Install]
WantedBy=multi-user.target

runonce-c.service

# Run-once service; runs any executable in /etc/local/runonce-c.d
# before the system is shut down or rebooted.
[Unit]
Description=Run once programs (before shutdown)
RequiresMountsFor=/home

[Service]
Type=oneshot
ExecStop=/usr/local/bin/runonce /etc/local/runonce-c.d
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target


It took quite a while to assemble the various bits of this from around the web; hope it helps someone else.