29

I created my own service for jekyll and when I start the service it seems like it doesn't run as a background process because I am forced to ctrl+c out of it. It just stays in the foreground because of the --watch. I am not sure how to go around it and make it so that it runs in the background. Any thoughts?

# /etc/systemd/system/jekyll-blog.service

[Unit]
Description=Start blog jekyll

[Service]
Type=forking
WorkingDirectory=/home/blog
ExecStart=/usr/local/bin/jekyll build --watch --incremental -s /home/blog -d /var/www/html/blog &
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
User=root
Group=root

[Install]
WantedBy=multi-user.target
  • systemd will start your process and expect it to fork another process if you are using Type=forking. Moreover, it will not run execStart as a shell expansion, so that & at the end will never be understood as a background flag. – grochmal Sep 7 '16 at 0:15
  • my bad the & was me testing it. So should type be simple? – madmanali93 Sep 7 '16 at 0:22
  • 2
    If I'm not mistake jekyll is a rails type thingy, i.e. a small weby server in ruby. So yeah, Type=simple would be appropriate. Also, this is not the kind of application I would run as root, at least not on an internet facing machine (which may not be your case). – grochmal Sep 7 '16 at 0:30
  • Thanks yea simple worked. Also the this command generates the static html for Apache so that jekyll doesn't serve on the server. I think it should be fine if it runs as root. Not sure though was debating it. – madmanali93 Sep 7 '16 at 0:55
  • Oh OK, so that is what --incremental does :). Yeah, I see no security issue in regenerating files as root. Of course, given that those files are not user supplied. – grochmal Sep 7 '16 at 1:17
51

Systemd is able to handle various different service types specifically one of the following

  • simple - A long-running process that does not background its self and stays attached to the shell.
  • forking - A typical daemon that forks itself detaching it from the process that ran it, effectively backgrounding itself.
  • oneshot - A short-lived process that is expected to exit.
  • dbus - Like simple, but notification of processes startup finishing is sent over dbus.
  • notify - Like simple, but notification of processes startup finishing is sent over inotify.
  • idle - Like simple, but the binary is started after the job has been dispatched.

In your case you have picked Type=forking which means systemd is waiting for the process to fork itself and for the parent process to end, which it takes as an indication that the process has started successfully. However, your process is not doing this - it remains in the foreground and so systemctl start will hang indefinitely or until the processes crashes.

Instead, you want Type=simple, which is the default so you can remove the line entirely to get the same effect. In this mode systemd does not wait for the processes to finish starting up (as it has no way of know when this has happened) and so continues executing and dependent services straight away. In your case there are none so this does not matter.

A small note on security:

You are running the service as root, this is discouraged as it is less secure than running it as an unprivileged user. The reason for this is that if there is a vulnerability in jekyll that somehow allows execution of commands (possibly via the code it is parsing) then the attacker needs to do nothing else to completely own your system. If, on the other hand, it is run as a non-privileged user, the attacker is only able to do as much damage as that user and must now attempt to gain root privileges to completely own your system. It simply adds an extra layer attackers must go though.

You can simply run it as the same user that is running your web server, but this leaves you open to another potential attack. If there is a vulnerability in your web server that allows the user to manipulate files on your system they can modify the generated html files, or worst the source files and cause your server to serve anything they want. However, if the generated files and source files are only readable by the webserver and writable be another non-privileged user they will not be able to, as easily, modify them by attacking the web server.

However, if you are simply serving static files from this server and keep the server up to date these attacks are very very unlikely - but still possible. It is your responsibility to weigh the risks vs the overhead of setting it up based on how critical your system is but both of these tips are very simple to set up and next to no maintenance overhead.

0

In addition to @Michael Daffin 's solution, you can also use the daemonize tool to achive the usage of forking as shown in the following example.

Given a little shell script which I want to daemonize and which I want to control over systemd, I saved it as /home/pi/testscript.sh:

#!/bin/bash

while true;
do
    sleep 1
    echo -n "."
done

If you don't have it yet, install daemonize, like this:

sudo apt install daemonize

Now create the file service definition file:

sudo vi /etc/systemd/system/testomat.service
# It is not recommended to modify this file in-place, because it will
# be overwritten during package upgrades. If you want to add further
# options or overwrite existing ones then use
# $ systemctl edit testomat.service
# See "man systemd.service" for details.

# copied from https://github.com/bitcoin/bitcoin/blob/master/contrib/init/bitcoind.service and modified by Michael 

[Unit]
Description=Test service
After=network.target

[Service]
ExecStart=daemonize -p /run/testomat/testomat.pid -o /home/pi/testscript.log /home/pi/testscript.sh
TimeoutSec=1200

# Make sure the config directory is readable by the service user
PermissionsStartOnly=true

# Process management
####################
Type=forking
PIDFile=/run/testomat/testomat.pid
Restart=on-failure
GuessMainPID = true

# Directory creation and permissions
####################################

# Run as pi:pi
User=pi
Group=pi

# /run/testomat
RuntimeDirectory=testomat
RuntimeDirectoryMode=0710

# /var/lib/testomat
StateDirectory=testomat
StateDirectoryMode=0710

# Hardening measures
####################

# Provide a private /tmp and /var/tmp.
PrivateTmp=true

# Mount /usr, /boot/ and /etc read-only for the process.
ProtectSystem=full

# Allow access to /home, /root and /run/user
# Chosing "false" is actually no hardening, this is just to demonstrate the usage of a service. Well, I could have omitted it. True. :)
ProtectHome=false

# Disallow the process and all of its children to gain
# new privileges through execve().
NoNewPrivileges=true

# Use a new /dev namespace only populated with API pseudo devices
# such as /dev/null, /dev/zero and /dev/random.
PrivateDevices=true

# Deny the creation of writable and executable memory mappings.
MemoryDenyWriteExecute=true

[Install]
WantedBy=multi-user.target

The newly created service has to be announced to systemd:

systemctl daemon-reload

Now you can start the service and the script forks. As expected, the service start comes back to the shell immediately. The result is obvious:

$ tail -f testscript.log 
.....................

  • What is the advantage of using daemonize + Type=forking instead of Type=simple and letting systemd take care of starting the service? Type=forking is kind of a compatibility setting in systemd to support legacy programs that are written to fork. – Johan Myréen Oct 5 at 17:10
  • I think it's an equivalent solution; I just wanted to provide the OP an alternative solution and to make him aware of this tool which I've already used in /etc/init.d times as the question is also a little bit about how to daemonize a process. – Michael Oct 5 at 18:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.