Editing and Creating Systemd Services (Updated)

I originally wrote this post back in July. I had to refer to it today, and realized it's pretty sparse. So this is the same thing, re-edited for more detail.


There was a lot of noise around the introduction of Systemd - a couple years worth of it. There was no question in my mind that Linux needed a new init system, but I don't think any possible replacement could have made everyone happy. I was (and still am) against Systemd's use of binary logs (unreadable without the application), but otherwise I stayed out of the vitriolic fight and opted to roll with whatever Debian/Ubuntu did. So now I need to know how to create a service that starts when the machine starts and that can be controlled by systemctl.

Turned out to be easy for a Ruby application: I used the mikewilliamson link below, with the judicious application of information found at the RedHat and Digital Oceans links.

To see all the system's listed services:

systemctl list-unit-files --type=service

Or to see the currently enabled ones:

systemctl list-unit-files --type=service | grep enabled

These show the intended state (ie. "enabled" or "disabled"), but not the current state (ie. whether or not the service is currently running). For that, use:

systemctl list-units

You'll want a very wide console, but the information is there. To see which are currently causing problems:

systemctl --failed

On most systems, this should so zero results - but if you're debugging a new service that may not be true. In my case, it showed the only failed unit is the one I wrote. <sigh> To see more about the failure:

systemctl status <unit_name>

But that only tells us the state the service is in. Errors are logged to /var/log/syslog - which strikes me as very strange, because shouldn't it be in journalctl? In my case, the problem turned out to be a Ruby error in the git repo, easily fixed. With the information in the Bibliography below, this was surprisingly easy - and has greatly eased the maintenance of services on my server. System-supplied service files are in /lib/systemd/system/, whereas your local ones will be in /etc/systemd/system/.

Here's the file I eventually created:

[Unit]
Description=Run the BookList application
Requires=network.target

[Service]
Type=simple
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/rubies/ruby/bin
WorkingDirectory=/usr/share/nginx/booklist
PIDFile=/var/run/puma.pid
ExecStart=/opt/rubies/ruby/bin/bundle exec /opt/rubies/ruby/bin/puma -b unix:///var/run/booklist.sock --pidfile /var/run/puma.pid
ExecStop=/opt/rubies/ruby/bin/bundle exec /opt/rubies/ruby/bin/pumactl --pidfile /var/run/puma.pid stop
ExecReload=/opt/rubies/ruby/bin/bundle exec /opt/rubies/ruby/bin/pumactl --pidfile /var/run/puma.pid reload
Restart=always
ExecStartPost=/opt/rubies/ruby/bin/bundle exec rails runner BookList; /opt/rubies/ruby/bin/bundle exec rails runner EventList; /opt/rubies/ruby/bin/bundle exec rails runner StoryList;

[Install]
WantedBy=multi-user.target

I've used ExecStartPost which means "run this stuff after the main service is started." It can (and in this case does) include multiple commands separated by semicolons. There's also an ExecStartPre which would have been preferred, but the bundle exec rails runner <...> commands take a long time to run and are executed sequentially, so it was decided to start the service first with old data and regenerate the data after.