How to start/stop a script using systemd?

Hi all,

I'm trying to use systemd to run a very simple application. Basically, I'm trying to learn how to use systemd. In "/etc/systemd/system/test.service" I have the following script:

# this is a test/dummy service.

[Unit]
Description=Test Application

[Service]
SyslogIdentifier=Test_Application
Type=oneshot
User=foobar
Group=foobar
ExecStart=/opt/blah/service.sh start
ExecStop=/opt/blah/service.sh stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

The service.sh script starts the little c++ application (which basically prints out "hello world" in an infinite loop every 3 seconds and the binary name is literally "test") like so:

exec "./$APPLICATION_NAME" & disown

And every time that I run "systemctl start test.service", this is what I see:

May 07 16:55:25 ruc-rhel-test systemd[1]: Started Test Application.

Now, when I use "ps" to see if the test application is still running, I don't see it. I don't mind if it's chugging along in the background, but I don't see it when I run "ps -ef".

If I use the service.sh script to start/stop the "test" binary, everything works just fine. My question is, how can I just run the service.sh script and that's it? What am I screwing up?

Have you looked at this tutorial?

I didn't know that that existed. I looked at about 20+ tutorials that I found duckduckgo. I'll have a look.

Thank you.

You need to read up on what the different kinds of "Type" do.

For a normal simple script the Type oneshot is correct but your example script is not that typical.

Systemd will stop the unit and kill all the processes after the script is done. This means after the script put the application in the background the script is finished and all processes will be killed.

Why use a script to call a application in the first place? Simply start the c++ application with the systemd unit. Either don't set a Type or select a type that is for a normal application that it self don't move to the background.

https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=

You also don't need Remainafterexit. it is only necessary if you want the service to be active even all processes are already dead. Sometimes needed for dependencies of other services.

The reason for service.sh to call "test" is that we have already a bunch of scripts that work from when we used the old /sys/init.d in an older version of Linux. They work. Furthermore, while this is a dumbed down example, in my dev environment there are far more complex applications (Java and C++) so having a script handle all of the complexities of starting an application and bringing it down -- consistently -- is very advantageous. I'm just trying to better understand systemd so that I can go back and port over our other scripts to the new version of Linux.

Thanks for the link and other suggestions, it gives me some ideas to try out.

My strategy for re-using my existing scripts was born out of this post (first reply) that I found online:

I liked it and just rolled with it.

I think that systemd is quite useful, but how to operate it is just a bit over-complicated. You have to think carefully each single option, and if the option is really useful.

For example I wrote a service just to start a daemon in user space, after dbus. After plenty of thought it ended being like this:

[Unit]
Description=Xiccd Screen Color Profiler
PartOf=dbus.service
After=dbus.service

[Service]
ExecStart=/bin/xiccd
Restart=always

[Install]
WantedBy=dbus.service

Translation to humans:

  • WantedBy: If that starts, this also starts.
  • PartOf: If that restarts of stops, this also stops. Cause it cannot operate without that.
  • After: Start this only after that, cause otherwise this crashes.
  • Restart=always: if this crashes try to restart it a limited number of tries.

I don't need to specify things like type, user or group. Cause the default setting is already the intended.

For enabling it in user space I had to run:

sudo systemctl [enable/start] --user --global [service].service

For further information just google systemd.unit.

Quick comment, couldn’t I also set the user in the *.service file as well? And the resulting process should run as a non-privileged that I specified (this could be useful since I need a specific user to execute the services).

Using the "user" option in a system service file or creating a user service are two completely different things.

The first one ca be used to start a system service and let the process(es) run by the specified user. It still requires root permissions to start and stop the service and the environment in which the service will be run is more like a default system environment. This also does not require a normal (human) user account on the system. It can be used with any system user. The user does not need to be log in to start the service. If it is enabled, it usually starts immediately after all the required services are up and running. It does not care if the user logs in or out.

The user services require a user that can log in. And the service is only started after the user logs in if the service is enabled. If the user logs out the service stops and all processes are killed.

1 Like

Thanks for that. One of my other requirements is to start the service when the machine boots, but give a non-privileged user the ability to start/stop/restart it. How can that be done?

When I used init.d, I just set the test file to be owned by that non-privileged user.

So, I updated my test.services file to this:

[Unit]
Description=Test Application
After=systemd-user-sessions.service

[Service]
SyslogIdentifier=Test_Application
Type=forking
User=foobar
Group=foobar
ExecStart=/opt/blah/z_start_service.sh
ExecStop=/opt/blah/z_stop_service.sh

What I did was take my test_app.sh and wrapped the starting and stopping functionality into their own scripts. I guessed -- perhaps incorrectly -- that by including a space after the script to include the start or stop command line argument was causing issues for starting the script. Having an extra layer of scripts is not a problem. The scripts z_start_service.sh and z_stop_service.sh work just fine by themselves.

Well, when I restart everything and then look at the journal, this is what I see:

May 11 12:29:36 blah-box systemd[1]: test.service: main process exited, code=exited, status=127/n/a
May 11 12:29:36 blah-box systemd[1]: Unit test.service entered failed state.
May 11 12:29:36 blah-box systemd[1]: test.service failed.

My question is this. What is the status of 127? Just what is systemd doing when it's trying to run my script? I feel like the output is not verbose enough, is there a way to add more log details when I run journalctl?

Run instead:

systemctl status name_of_your.service

I'm not sure "forking" is what you would want to use.

This is the result:

● test.service - Test Application
   Loaded: loaded (/etc/systemd/system/test.service; static; vendor preset: disabled)
   Active: failed (Result: exit-code) since Mon 2020-05-11 13:19:57 EDT; 8s ago
  Process: 20596 ExecStart=/opt/blah/z_start_service.sh (code=exited, status=0/SUCCESS)
 Main PID: 20606 (code=exited, status=127)

May 11 13:19:56 blah-box systemd[1]: Starting Test Application...
May 11 13:19:57 blah-box systemd[1]: Started Test Application.
May 11 13:19:57 blah-box systemd[1]: test.service: main process exited, code=exited, status=127/n/a
May 11 13:19:57 blah-box systemd[1]: Unit test.service entered failed state.
May 11 13:19:57 blah-box systemd[1]: test.service failed.

What would you recommend instead of forking?

I've tried "oneshot" and that does not create the error shown above. And it seems that test_app.sh was run successfully. However, the C++ application "test" is left to run in the background non-stop.

I'll keep testing different options.

Have you tried "simple".

Same as oneshot.

Except with oneshot I see this in the log:

Starting Test Application...
Started Test Application.

And with simple, I see this in the log:

Started Test Application.

The C++ test application is not running in the background.

Question. How can I step through the different steps of the parts of starting up a service? The z_start_service.sh and z_stop_service.sh scripts work just fine by themselves. And I'm confused why I can't just invoke them. I know that I'm screwing something up myself, but I don't know what.

Unfortunately, I don't currently have access to a running Linux system to test different units. I am only using my cell, so I am quite handicapped ATM.

Perhaps you might want to check into methods to respawn a script after it has finished running. I have used this method in the past.

See:

This is the relevant line:

exec "$ScriptLoc"/usr/local/sbin/network_restart.sh && exit

There are other methods than using ScriptLoc.

That usually works without a problem. At least on my system results

ExecStart=/opt/blah/service.sh start

in $1 = start in that service.sh script.

But that indicates

to me that we are not using the same systemd version. It is probably not even the same Distribution.

That means the process which should forked in the background exited with a non zero status code. It is impossible to say without the script and the other programs you use.

As far as I know there is no standard why to do it. By default and if available systemctl uses polkit. You could write a rule to allow a specific user to start/stop and restart this service. This might depends on the version of polkit that is available.

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units" &&
        action.lookup("unit") == "verycoolservcice.service" &&
        subject.user == "xabbu") {
        return polkit.Result.YES;
    }
});

Or put the three systemctl commands in the sudoers file. This will probably also work.

2 Likes

This is an update.

The reason why my test C++ application did no start was because when I called the script /opt/blah/test_app.sh start, I figured that it would just call the "test" application that was also located in /opt/blah. Turns out, that that path was something completely different. When I called /opt/blah/test, it began to work just fine.

Yet one more question. How can I specify a default directory for execution of applications? Is that done by using this?
https://www.freedesktop.org/software/systemd/man/systemd.service.html#RootDirectoryStartOnly=

This all only applies to the newest version of systemd. If you use a older systemd version check if the options are available.

No "RootDirectoryStartOnly" only takes true/false and applies RootDirectory to only "ExecStart=" or to all Exec lines.
I don't think you should use "RootDirectory" . Of course changing the / to something else to create sandboxes/chroots is always quit funny to debug :japanese_goblin:, but it might not the thing you are looking for.

Try

[Service]
...
WorkingDirectory=/opt/my/app
...

You should also get familiar with the Environment variables that systemd uses and how to add or change it. services use a different standard environment as your normal system.
https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Environment%20variables%20in%20spawned%20processes

If you really want to go check out namespaces and how to secure your systemd services start reading
https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Sandboxing

1 Like

Hi, I ended up discovering WorkingDirectory and went with that. Thank you for the links as well. I don't think that I'll need to be as concerned about sandboxing at the moment, but if I do, I'll know where to turn.

So, I have one more question.

This is my current service file:

[Unit]
Description=Core Process Service Application
After=systemd-user-sessions.service
After=nework.target

[Service]
SyslogIdentifier=Core_Process_Service_Application
Type=forking
User=foobar
Group=foobar
WorkingDirectory=/opt/blah
ExecStart=/usr/bin/bash /opt/blah/cp_script start
ExecStop=/usr/bin/bash /opt/blah/cp_script stop
ExecReload=/usr/bin/bash /opt/blah/cp_script restart

And this is what happens when I run it:

May 21 13:51:56 blah-box polkitd[819]: Registered Authentication Agent for unix-process:11684:10043093 (system bus name :1.792 [/usr/bin/pkttyagent --notify-fd 5 --
May 21 13:51:56 blah-box systemd[1]: Starting core-process Service Application...
May 21 13:51:56 blah-box systemd[1]: Started core-process Service Application.
May 21 13:51:56 blah-box polkitd[819]: Unregistered Authentication Agent for unix-process:11684:10043093 (system bus name :1.792, object path /org/freedesktop/Polic
May 21 13:55:35 blah-box dbus[809]: [system] Activating service name='org.freedesktop.problems' (using servicehelper)
May 21 13:55:35 blah-box dbus[809]: [system] Successfully activated service 'org.freedesktop.problems'

If I wan to run cp_script by itself, everything works flawlessly. I've even specified /usr/bin/bash explicitly in hopes to nullify any execution permissions issues.

Why does the service not start and I get some freedesktop URL? I figure that that is related to systemd, but I don't know 100%.

Forum kindly sponsored by