Monday, July 30, 2012

Starting Programs on Boot in Linux

Using the System V Init process
Wanting to be able to set programs to automatically run when my computer turns on is a common issue.  I usually have a variety of different programs running all the time on my Linux server, doing anything from recording webcam footage I want to go back and review, to watching for interesting tweets to print, to sending me text messages with NWS weather alerts, to running a Minecraft server.  Many of these (like checking for new weather alerts every 30 minutes) can be easily scheduled to run at regular intervals using the Unix cron tool, but many others really have to be running all the time and just need to be started when the system first boots.

Luckily, the System V Unix standard exposes an easy way to control how these long-running processes (called daemons) can be started and stopped when the computer is booted and shutdown through the init system.  Init is the very first process that the Linux kernel executes when you boot you computer, and init then goes on to spawn, or spawn the spawner of (or spawn the spawner of the spawners of,etc), every other process running on your computer.  Likewise, when you go to turn off your computer, init is the process that tells every running daemon to clean up whatever it's working on and exit so the system can shutdown cleanly.  The question then becomes "how does init know when we're turning on or turning off the computer, and how does it know which programs to start or stop at each point?"

Of course, since System V came out nearly three decades ago, programmers have developed alternatives to init, such as Upstart (which is used by Ubuntu), and systemd (which is used by Angstrom - Tutorial).  Luckily, since System V was such an important part of Unix history, even when a distro uses one of these other systems, they usually still support the init protocol using /etc/init.d and /etc/rc[0-6S].d/ I'm going to describe for you here.



Run Levels

Signalling init as to what we want it to be doing is done through what's called runlevels.  I always think of runlevels like DEFCON; a simple indicator of what general state of readiness the computer needs to be in.  Unfortunately, unlike DEFCON, runlevels aren't quite clearly as defined as DEFCON levels:
  • Runlevel 0 - This runlevel is for when an admin has told the computer to shutdown, so init sends signals to processes running which need to perform cleanup, and runs shutdown scripts to do things like unmounting hard drives and releasing resources on the network.
  • Runlevel 1 - Single-User mode is usually for when something has gone wrong, so we want as little of the system running as possible.  Runlevel 1 starts the bare minimum needed so that the superuser (root) can log in and play God while he fixes whatever is wrong for the peasants (otherwise known as normal users).  Once the superuser has fixed whatever was wrong (corrupt filesystem, mis-configured daemon, etc), he will tell init to take the system to one of the other runlevels, at which point init brings up the rest of the services that normal users use, like graphical interfaces and file servers.
  • Runlevel 2-5 - This is where the differences between different flavors of Linux get a little annoying.  Runlevels two through five are the normal "multi-user" runlevels that systems spend most of their time in, providing various levels of usefulness for all of the normal users; what exactly that means is defined differently for every distribution.  Usually, as you move to higher runlevels, init will start progressively more services, such as the graphical interface, etc.
  • Runlevel 6 - Level six is much like runlevel 0, and is usually almost the same set of shutdown scripts, with the one difference that at the very last moment, instead of shutdown down, the computer should reset itself and come back up.  Of course, any programs that you have running will definitely be stopped in either case, but if you know that the computer is just rebooting and not being shutdown to be put in cold storage, maybe you only want your daemon to say "hey! BRB!" instead of "Goodbye, cruel world!"  Exactly what that difference implies depends on exactly what the daemon is doing.



/etc/init.d/

So init has eight different run levels (0-6 and "S"), which each have a corresponding set of programs that should be either started or stopped when the system enters that runlevel.  Of course, now you want to add your own program to these glorious sets of lists, so that if the computer ever happens to be turned off, the programs will automatically come back up with it, instead of you having to manually log in and try and remember everything that you had running in the background the last time you rebooted your computer.

These magical lists are stored as eight directories of shell scripts in the /etc/ settings folder: /etc/rc0.d/ - /etc/rc6.d/ and /etc/rcS.d/ (which are often written using the shorthand /etc/rc[0-6S].d/).  The problem is, having a shell script to start your program in eight different places is going to be a pain in the ass, come the time you ever need to make an edit to this script.  To solve this, these eight directories actually contain no scripts at all, but just symbolic links pointing at master copies of all the scripts in a single directory: /etc/init.d/.

Depending on exactly which Linux you're running, and exactly what services you have installed, your /etc/init.d/ folder will have various different files in it.  The most useful file in this directory is the /etc/init.d/skeleton file, which is a shell script with all of the boiler plate already written for your script to behave properly when init, or an admin runs it to start or stop your daemon.

To write your own init.d script, copy this skeleton script:
# cp /etc/init.d/skeleton /etc/init.d/my-first-daemon
And then fill in all of the details about the daemon at the beginning of this script (bah! comments!), and then fill out the do_start() and do_stop() shell functions so that they, respectively, start and stop your daemon.


update-rc.d my-first-daemon defaults

Once you have your init.d script written, it's time to properly create all of the symlinks to it in the eight /etc/rc[0-6S].d/ directories.  Unfortunately, these symlinks all need to be made in a very specific format.  Fortunately, generations of admins before you have been really lazy, so you'll usually find a tool called "update-rc.d" which will do all of the hard work for you.  All you have to do is tell it what your init.d script is named, and then tell it which run levels you'd like init to either start or stop your daemon in.

The vast majority of the time, you'll want your daemon to start for the four multi-user modes, and stop for all the other modes.  This typical setting can be described as "defaults," which is a lot less verbose than the equivalent command which explicitly lists every runlevel under either start or stop:
# update-rc.d my-first-daemon start 20 2 3 4 5 . stop 20 0 1 6 .

Notice the 20 immediately after start and stop; this is where in the transition sequence you want your daemon's init.d script to be run.  These numbers range from 00 to 99, and are only important if your daemon depends on other services (which it probably does...) like networking, ssh, etc.  These sequence numbers, as well as the choice between starting or stopping a daemon, are actually encoded into the names of the symlinks, as follows:

Sample listings:


  • Each symlink will start with either an S (for start) or K (for kill), indicating if that daemon should be started or stopped.
  • Immediately after the S or K is the two digit sequence number dictating in what order all of the init.d scripts are run.
Luckily, you don't really need to remember this.  All you need to know is how to use the update-rc.d tool, and it does all the hard lifting and remembering for you.



UserStart - A Simple Example

Now that you know how to write an init.d script which will properly respond when init or root prod it, and how to tell init which runlevels you want your daemon started or stopped in, it's time for a useful example I use on some of my systems.

UserStart Source Listing:


This script (which is based on /etc/init.d/skeleton) scans through each user's home directory looking for a ~/cron/startup.sh script which, if it exists, this script executes as that user.  I find this useful because the init system is designed to start full-blown daemons as root, but often I want to start programs as a normal user.  This now lets me just add a call to my programs from ~/cron/startup.sh instead of having to go through all of the work of building an init.d script and installing it for every separate program.

Of course, this script has some glaring security issues; when a user can schedule something to run on startup, they can schedule things that go horribly wrong and make even booting a system to remove their startup.sh scripts kind of a pain in the ass.  Also, having init run ANY ~/cron/startup.sh script for ANY user willy-nilly is kind of a bad deal.  This all means that you should probably only deploy this script on a system where you inherently trust all your users, but then again, I have a rather unpopular opinion that Unix is an inherently insecure operating system all together...

2 comments:

  1. Hey

    Today Sys V boot scripts are really something vintage that nobody should be writing anymore. You should look at the much-improved replacements (either upstart or systemd, depending on your distro of choice).

    ReplyDelete
    Replies
    1. Why not? They start my daemons just fine, and they're the only thing that *all* of my systems support. How does upstart start my daemons for me better than SysV?

      Delete