Home » Linux » systemd

Debian: native systemd

I'm a happy user of Debian. I like it quite.

But there are some things I dislike. One is Debian's policy to start automatically any installed daemon. I even wrote a disable-services shell script to solve this problem. This script has to be run after each package upgrade. If you need to want know more, there's a German language description of it: "Debian: Startup-Scripte disablen".

As there was much talk about systemd. Let's see how this works.

Debian's systemd.deb 

The first stop is, of course, the package provided by Debian itself. I installed it, made sure that I had init=/bin/systemd on the kernel command line ... and it worked like a charm.

It was not noticeable faster. That's because Debian's systemd honors /etc/rcS.d/ and /etc/rc2.d/. Every file in those directories are made implicitly into systemd services and started. But all of those files use lots of shell script magic, which make them slow.

The only good thing now is that I don't need the disable-services script anymore. Because I can disable any of them with a simply symlink for good:

cd /etc/systemd/system
ln -s /dev/null openbsd-inetd.service

Nice so far. But I thought "When I'm already using systemd, why don't I use it totally".

Compiling my own 

Systemd from GIT 

So first I uninstalled (even purged) Debian's systemd and systemd-sysv packages.

Then I thought "If I'm going bleeding edge, than make it all the way" and decided to get systemd from GIT. Visiting http://cgit.freedesktop.org/systemd/ told me all I needed was

git clone git://anongit.freedesktop.org/systemd

For your reference: I wrote this article using systemd version v32-1-g71092d7 (output of git describe).

Configuration 

Now you need to run ./autogen.sh. This script generates configure and calls it. It's even possible to already specify configure options, so I started it this way:

./autogen.sh \
    --prefix=/usr \
    --with-udevrulesdir=/lib/udev/rules.d \
    --with-distro=debian \
    --with-sysvinit-path= \
    --with-sysvrcd-path= \
    --disable-selinux \
    --disable-libcryptsetup \
    --disable-audit \
    --disable-tcpwrap \
    --disable-plymouth

I would have also added --disable-acl, because I don't use them either. But the source of systemd is buggy, it won't compile with ACL disabled.

But before we compile, I applied a bunch of little patches. Some are my own, some are from the debian systemd package:

Debianization 

The original debian package contained some patches to systemd, which you can see here. Because I use systemd from GIT, I decided to use only 0001-Tweak-unit-files-for-Debian.patch.

Well, almost. That patch depends getty on openvpn and apache2 for no apparent reason.

Code tweaking 

I also applied a patch to unclutter the output of systemd. This patch is completely optional. When I turn on debug messages, systemd emits two lines for each unit it starts:

Starting File System Check on Root Device...
...
Started File System Check on Root Device.

For every unit! And systemd has a lot of units. Way too much clutter for my taste. So I first disabled the "Started" messages.

And then thought that "File System Check on Root Device" sounds nice for an end-user. But while I'm fine tuning this new init system for my usage, I'd rather want to know the name of the service. So I made sure that I see instead:

Starting fsck-root.service

That way, should there be a problem, I know that I have to look into the file /lib/systemd/system/fsck-root.service.

Index: systemd/src/job.c
===================================================================
--- systemd.orig/src/job.c  2011-07-09 18:56:33.754601166 +0200
+++ systemd/src/job.c   2011-07-09 18:56:59.037797531 +0200
@@ -484,7 +484,7 @@
                 switch (result) {

                 case JOB_DONE:
-                        unit_status_printf(u, "Started %s.\n", unit_description(u));
+                        log_debug("Started %s.\n", unit_description(u));
                         break;

                 case JOB_FAILED:
Index: systemd/src/unit.c
===================================================================
--- systemd.orig/src/unit.c 2011-07-09 18:56:33.761267796 +0200
+++ systemd/src/unit.c  2011-07-09 18:56:59.037797531 +0200
@@ -903,7 +903,7 @@

         unit_add_to_dbus_queue(u);

-        unit_status_printf(u, "Starting %s...\n", unit_description(u));
+        unit_status_printf(u, "Starting %s\n", u->meta.id);
         return UNIT_VTABLE(u)->start(u);
 }

Installation 

Compilation & installation is easy:

make
make install

Fine tuning 

Systemd configuration 

I use this /etc/systemd/system.conf file:

[Manager]
LogLevel=debug
LogTarget=syslog-or-kmsg
#LogColor=yes
#LogLocation=no
#DumpCore=yes
#CrashShell=no
ShowStatus=yes
#SysVConsole=yes
#CrashChVT=1
#CPUAffinity=1 2
MountAuto=yes
SwapAuto=yes
#DefaultControllers=cpu
#DefaultStandardOutput=inherit
#DefaultStandardError=inherit

First boot 

Now make sure that the command line to the kernel contains "init=/bin/systemd". I also want to have some status output, so I removed the "quiet" kernel command line parameter. "quiet" is not only honored by the linux kernel, but also by systemd.

I rebooted and ... my system booted successfully on the first try.

Success!

Fixing garbled debug output 

The first problem: the debug output of systemd is garbled. The getty service from console one clears the screen and writes stuff onto the console while systemd also writes there. Both writes fight ... and no one wins.

So I temporarily disable the getty service for console 1:

cd /etc/systemd/system
ln -s /dev/null getty@tty1.service

With a newer version of systemd (from v32 on) you can also do:

systemctl mask getty@tty1.service

A reboot now shows that the debug output to be much cleaner.

Success!

Console keymap 

Console setup on Debian is black magic. Yes, really. I have the slight feeling that even Debian package maintainer don't have a clue what the canonical procedure is to setup the keymap and font. Or why do so many (partially competing) packages exist for this? To name a few: console-common, console-data, console-setup, console-setup-linux, console-setup-mini, console-tools, keyboard-configuration.

The systemd developers also threw their hands into the air. vconsole-setup.c didn't contain any #ifdef TARGET_DEBIAN when I wrote this article.

No wonder that systemd-vconsole-setup.service spits out error messages and can't setup my keymap.

So I first disable this beast:

cd /etc/systemd/system
ln -s /dev/null systemd-vconsole-setup.service
rm -f /lib/systemd/systemd/sysinit.target.wants/*vconsole*

Unfortunately, the rm command is necessary, otherwise you'll get annoying error message at start up.

Then I install my own service that simply loads the boot time keymap. Store this into /etc/systemd/systemd/boottime-kmap.service:

[Unit]
Description=Boottime keyboard map
DefaultDependencies=no
Before=sysinit.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/loadkeys -q /etc/console/boottime.kmap.gz
StandardOutput=syslog

[Install]
WantedBy=sysinit.target

and then enable it:

systemctl enable boottime-kmap.service

The systemctl enable command just reads the [Install] section of a unit file and create the necessary symlink.

You can now start the service right away ...

systemctl start boottime-kmap.service

... or reboot. And now you'll have at least a working keymap.

Success!

Network interfaces 

To bring up the network interfaces the debian way, I install this file as /etc/systemd/system/ifupdown-clean.service:

[Unit]
Description=Clean old interface status during boot
DefaultDependencies=no
After=remount-rootfs.service

[Service]
Type=oneshot
ExecStart=-/bin/rm -f /etc/network/run/ifstate
RemainAfterExit=true

and this file as /etc/systemd/system/ifup@.service:

[Unit]
Description=Start ifup for %I
After=local-fs.target
After=ifupdown-clean.service
Wants=ifupdown-clean.service

[Service]
ExecStart=/sbin/ifup --allow=hotplug %I
ExecStop=/sbin/ifdown %I
RemainAfterExit=true

Success!

Starting cron 

Currently the debian packages for udevd, rsyslogd and dbus-daemon install systemd .service files. No other daemon on my system has a service file. So I have to write service files for some of them by hand.

But writing unit files is easy. Here is my unit file to start cron:

[Unit]
Description=Periodic Command Scheduler
After=syslog.target

[Service]
Type=forking
PIDFile=/var/run/crond.pid
ExecStart=/usr/sbin/cron
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
StandardOutput=syslog

[Install]
WantedBy=multi-user.target

Store it as /etc/systemd/system/cron.service and activate it via

systemctl enable cron.service
systemctl start cron.service

Success!

Starting SSH 

Here I now use the power of systemd. I don't start OpenSSH at boot time. Instead, I let systemd open the TCP port 22 and watch for it. Only when a connection comes in will sshd be started. Basically there's an inetd inside systemd.

First the unit file for the TCP socket. Store it as /etc/systemd/system/sshd.socket:

[Unit]
Conflicts=sshd.service

[Socket]
ListenStream=22
Accept=yes

[Install]
WantedBy=sockets.target

And here is the unit file for the ssh service. Store it as /etc/systemd/system/sshd@.service:

[Unit]
Description=SSH Per-Connection Server
#Requires=sshdgenkeys.service
After=syslog.target
#After=sshdgenkeys.service

[Service]
ExecStartPre=/bin/mkdir -m700 -p /var/run/sshd
ExecStart=-/usr/sbin/sshd -i
ExecReload=/bin/kill -HUP $MAINPID
StandardInput=socket

Now we activate it:

systemctl enable sshd.socket
systemctl start sshd.socket

And if you look with netstat -ntlp, you'll see the socket is open:

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp6       0      0 :::22                   :::*                    LISTEN      1/systemd

But if you look at the output of ps, you won't yet find sshd running. Now ssh into your own box:

$ ssh localhost
root@localhost's password:

Success!

Binary formats 

The last unit file for today will be the support for the linux kernel binfmt feature. It allows one to start wine or java files directly, because the kernel knows there signature and the needed interpreter / binary helper.

Store this as /etc/systemd/system/binfmt-support.service:

[Unit]
Description=Support for extra binary formats
After=local-fs.target

[Service]
ExecStart=/usr/sbin/update-binfmts --enable
ExecStop=/usr/sbin/update-binfmts --disable
RemainAfterExit=true

[Install]
WantedBy=multi-user.target

and activate it:

systemctl enable binfmt-support
systemctl start binfmt-support

Now we check if it was successful:

$ ls /proc/sys/fs/binfmt_misc
-rw-r--r-- 1 root root 0 Jul 10 22:40 jar
-rw-r--r-- 1 root root 0 Jul 10 22:40 llvm.binfmt
-rw-r--r-- 1 root root 0 Jul 10 22:40 python2.5
-rw-r--r-- 1 root root 0 Jul 10 22:40 python2.6
--w------- 1 root root 0 Jul 10 22:40 register
-rw-r--r-- 1 root root 0 Jul 10 22:40 status
-rw-r--r-- 1 root root 0 Jul 10 22:40 wine

Success!

Boot analysis 

Here's an SVG graph of my current boot up. I didn't do any specific optimization.