I've contemplated and even tried a few alternatives to cron. This one
was discovered while throwing stones at other birds. I was pleasantly
surprised.
Basically it boils down to using a process supervisor called runit
and a fancy big brother to the unix sleep
command called snooze.
Runit was originally meant to be a replacement for older init systems
like sysvinit
that is cross-platform as well as very simple and not
requiring many libraries. This was to suite it as part of the core
system to bootstrap everything else (think cron and sshd
etc.).
While these are nice characteristics they aren't killer for us
here. However, nowadays its pretty popular with the anti-systemd
crowd. Its an option for init systems in Gentoo and others and is the
default in Void linux. Besides its simplicity it is pretty easy to get
up and configured and you just run a few shell scripts to get
everything going.
I have a few complaints
- the documentation is kind of nonlinear and doesn't give you a
walkthrough of how to use the whole system .
- commands are disjointed and spread between a number of executables
and use of standard unix commands like
ln
and rm
.
The second point is actually a feature where each of the little
components can be used standalone. However, this makes it a little
more confusing to wrap your head around and I found myself constantly
reviewing my notes to know which command to use.
I solved this with a few shell functions, but I would like to see a
wrapper CLI to make it a little centralized conceptually (and to add a
few convenience features) for those that would want it.
Reading these articles also helped in understanding it:
Even after reading these I had to muck around and figure a bunch of
little details out so I thought I would throw my own little tutorial
on the pile to hopefully save some people's time and make runit a
little more approachable.
Luckily on Ubuntu 20.04 its really easy to get runit installed and
running as a systemd service. Just install using apt:
This even starts the service:
sudo systemctl status runit
sudo journalctl -u runit
Normally there are 3 stages (i.e. states) runit has:
- Single run tasks on startup
- Process supervision: starting, stopping, restarting services
- Shutting services down as the system goes down
Because we aren't using runit as a PID 0 init system, we only care
about stage 2 & 3. The apt
installation takes care of this for us
thankfully.
So you should see the following directories appear:
/etc/runit
- stages and runlevel stuff, ignore this (for now).
/etc/sv
- The services directory, this is where you author things.
/etc/service
- This is where "enabled" services are put.
I'll call these different directories by the environment variable I
refer to them as. I put this in my ~/.profile
:
## runit known env variables
# the active service directory to query for state, this is what it is
# default, but I like to set so its easier for me to disable services
export SVDIR="/etc/service"
# SNIPPET: the normal wait time
# export SVWAIT=7
## my vars, not recognized by any runit tools
# this is the standard directory of where services are put for the
# system. The SerVice LIBrary
export SVLIB="/etc/sv"
# log dir
export SVLOG_SYS_DIR="/var/local/log/runit"
To define a service you make a directory in SVLIB
with some
specially named shell scripts. Mine has these directories in it:
--> ls $SVLIB
backup hello printer_live recollindex test_env
Each one is a specific service. Lets first look at hello
to get a
simple picture of what these are:
--> ls $SVLIB/hello
finish log run supervise
The most important one is run
which is a shell script:
#!/bin/sh
# run the service
while :
do
echo "Hello"
sleep 2
done
This just prints "Hello" to stdout and then waits 2 seconds.
This service isn't being run yet. For that you need to put it into the
SVDIR
:
sudo ln -s -f "$SVLIB/hello" "$SVDIR/"
You can check the status of the service with the sv
command:
--> sudo sv status $SVDIR/hello
run: /etc/service/hello: (pid 664634) 193s
You can check the status of all services similarly:
If you see this:
--> sudo sv status $SVDIR/hello
down: /etc/service/hello: 1s, normally up, want up
There is something wrong with your run script.
Looking at sudo systemctl status runit
and sudo journalctl -u
runit
could usually help me figure the issue out (no email!!!).
Once its working you should see the "Hello"s on the log for runit if
you aren't logging this service:
--> sudo journalctl -u runit | tail
Oct 23 16:49:30 ostrich 2[664634]: Hello
Oct 23 16:49:32 ostrich 2[664634]: Hello
Oct 23 16:49:34 ostrich 2[664634]: Hello
Oct 23 16:49:36 ostrich 2[664634]: Hello
Oct 23 16:49:38 ostrich 2[664634]: Hello
Oct 23 16:49:40 ostrich 2[664634]: Hello
Oct 23 16:49:42 ostrich 2[664634]: Hello
Oct 23 16:49:44 ostrich 2[664634]: Hello
Oct 23 16:49:46 ostrich 2[664634]: Hello
Oct 23 16:49:48 ostrich 2[664634]: Hello
Next you'll have the finish
script which is just what should be run
at the end of the script:
#!/bin/sh
echo "Shutting Down"
We don't have anything to do really so we just write a message. But
you could do cleanup stuff here if you want.
Last the logging spec. This is a subdirectory called log
:
--> tree $SVLIB/hello/log
/etc/sv/hello/log
├── run
└── supervise [error opening dir]
1 directory, 1 file
Where again the run
is a shell script:
#!/bin/sh
exec svlogd -tt /var/local/log/runit/hello
To keep things simple this is what you want. In general you could swap
out different logging daemons other than svlogd
(which comes with
runit), but I don't see a reason to and this Just Works™. Basically
runit will create this as a separate service, but just knows how to
pipe around outputs now.
If you add these and then reload the services:
sudo sv reload $SVDIR/hello
You'll stop seeing "Hello" in the runit system log, and start seeing
it in the log file we configured:
sudo less +F "$SVLOG_SYS_DIR/hello/current"
# or
sudo tail -f "$SVLOG_SYS_DIR/hello/current"
Before we go over configuring the logging daemon (that sysvlogd
thing we ran in log/run
) I should mention all those supervise
dirs
that were laying around.
These basically are the locks and other control data that runit uses
to manage the services. Don't mess with them. They are owned by root
anyways. One thing you can do if you think you messed things up is to
disable the service and remove them all to start fresh:
sudo rm -rf $SVDIR/hello
rm -rf $SVLIB/hello/supervise
rm -rf $SVLIB/hello/log/supervise
Now one last thing is to configure the log. This file doesn't go in
the service directory (SVLIB
) but the directory where the logs
are. So make this file SVLOG_SYS_DIR/hello/config
and it should have
something like this:
# max size in bytes
s100000
# keep max of 10 files
n10
# minimum of 5 files
N5
# rotate every number of seconds
t86400
# prepend each log message with the characters
pHELLO::
This lets you rotate logs and control file sizes. Its a really not
nice file format but I will forgive them considering they aren't using
any libraries for TOML or YAML parsing or such things. Again something
I would improve on for non PID 0 usage.
With this all in place you'll see something like this in
SVLOG_SYS_DIR/hello
:
--> sudo tree $SVLOG_SYS_DIR/hello
/var/local/log/runit/hello
├── @400000005f929f2c12d2041c.s
├── @400000005f92b3043a84c42c.s
├── @400000005f92c6dd22d71b04.s
├── @400000005f92dab60d0ce77c.s
├── @400000005f92ee8f37dec1dc.s
├── @400000005f93026921e78ae4.s
├── @400000005f931643127f5bf4.s
├── @400000005f932a1d27cdd3b4.s
├── @400000005f933df70f77ffc4.s
├── @400000005f933e0a239542b4.s
├── config
├── current
└── lock
Where those ID named files are the rotated logs.
Now that we're done with the runit tutorial lets show you how to make
a timer service that acts like a cron job.