1

I'm creating a systemd script to run a program as needed. The WorkingDirectory may not exist when I run it. So, I want to create the directory as part of the script. But, I get an error because the specified WorkingDirectory doesn't exist:

[Service]
User=Inplant
Group=Inplant
ExecStartPre=/bin/mkdir -p /home/inplant/IPSdevice/JAI1
WorkingDirectory=~/IPSdevice/JAI1
ExecStart=~/IPSdevice JAI1 

Is there a way to create the Working Directory as part of the systemd script. I want to run the program like the following:

mkdir ~/IPSdevice/JAI1; cd ~/IPSdevice/JAI1; ~/IPSdevice JAI1

By changing the tilde to path, I was able to get past the first issue. Now I'm getting a different issue:

    Aug 06 09:48:53 ubuntu systemd[1]: Started JAI Device Service.
-- Subject: Unit app-ips-jai1.service has finished start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- Unit app-ips-jai1.service has finished starting up.
-- 
-- The start-up result is done.
Aug 06 09:48:53 ubuntu systemd[103782]: app-ips-jai1.service: Failed at step CHDIR spawning /home/inplant/IPSdevice/IPSdevice: No such file or directory
-- Subject: Process /home/inplant/IPSdevice/IPSdevice could not be executed
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- The process /home/inplant/IPSdevice/IPSdevice could not be executed and failed.
-- 
-- The error number returned by this process is 2.

The systemd script is:

[Unit]
Description=JAI Device Service
# When systemd stops or restarts the app.service, the action is propagated to this unit
PartOf=app.service
# Start this unit after the app.service start
After=app-ips.service

[Service]
# Pretend that the component is running
RuntimeDirectory=IPS-JAI1
User=inplant
Group=inplant
ExecStartPre=mkdir -p /home/inplant/IPSdevice/JAI1
WorkingDirectory=/home/inplant/IPSdevice/JAI1
#ExecStart=/home/inplant/IPSdevice JAI1 > /dev/nul 2>&1 & echo $! > /var/run/jai1.pid
ExecStart=/home/inplant/IPSdevice/IPSdevice JAI1
# Restart the service on non-zero exit code when terminated by a signal other than SIGHUP, SIGINT, SIGTERM or SIGPIPE
#Restart=on-failure

[Install]
# This unit should start when app.service is starting
WantedBy=app-ips.service

Normally I start the service by logging in as user "inplant" and executing the following:

cd ~/IPSdevice
mkdir -p JAI
cd JAI
../IPSdevice JAI
  • How would you do it outside of systemd? I think it will be the same: put the wholes lot in a script, and run it. – ctrl-alt-delor Aug 5 '19 at 23:14
  • The command you executed manually runs the program IPSdevice in your home directory, but the ExecStart command tries to run it from the IPSdevice directory. – Johan Myréen Aug 6 '19 at 15:19
2

Part of the problem is your use of ~, systemd doesn't expand ~ or ~user, so you need to specify full paths in your commands.

Furthermore, you can't really create a directory in ExecStartPre= and then use it in WorkingDirectory=, because systemd will check WorkingDirectory= before it executes any commands. (In fact, all commands in ExecStartPre= will run under that same directory.)

In order to accomplish that, use a shell script in ExecStart= which will prepare the environment and exec the daemon program.

For example, create a /home/inplant/start_inplant.sh with:

#!/bin/sh
set -eu
mkdir -p /home/inplant/IPSdevice/"$1"
cd /home/inplant/IPSdevice/"$1"
exec /home/inplant/IPSdevice/IPSdevice "$1"

And in your unit file, use:

[Service]
RuntimeDirectory=IPS-JAI1
User=inplant
Group=inplant
Environment=LD_LIBRARY_PATH=/opt/pleora/ebus_sdk/Ubuntu-x86_64/lib:/opt/pleora/ebus_sdk/Ubuntu-x86_64/lib/genicam/bin/Linux64_x64:/opt/xview2_64/lib/:/opt/GO_SDK/lib/linux_x64
Environment=GENICAM_ROOT_V3_0=/opt/pleora/ebus_sdk/Ubuntu-x86_64/lib/genicam
ExecStart=/home/inplant/start_inplant.sh JAI1
# Restart the service on non-zero exit code when terminated by a signal other than SIGHUP, SIGINT, SIGTERM or SIGPIPE
Restart=on-failure

No need for WorkingDirectory= or ExecStartPre= in your unit file. You might also decide to export environment variables in the shell script itself, up to you to decide whether you prefer one or the other.

One important part is to use exec in the shell script. That makes the shell replace itself with the program being executed, so that the program is running under the same PID that started as the shell. That's important, as systemd will use that PID to control the daemon (for example, to kill it while stopping the service), so you want that to be the daemon and not a shell script. Also, you don't want to leave an extra shell process running, just taking space for no good reason.

Note that you actually can use ~ in your start_inplant.sh shell script. Since it's a shell script, it will allow and perform all expansions normally performed by a shell.


Note on your other answer to this question:

This doesn't really work:

ExecStartPre=cd /home/inplant/IPSdevice/JAI1

systemd doesn't really run shell commands (and cd is a shell built-in.) This would only work if there was a /bin/cd or /usr/bin/cd binary, but there is no such a thing (since changing directory affects the current process only, so an external binary doesn't make sense since it would only affect itself and quit, you wouldn't see the effects of it.)

Even if you ran that explicitly with a shell:

ExecStartPre=/bin/sh -c "cd /home/inplant/IPSdevice/JAI1"

That wouldn't work either, because that would only affect that current shell and the effects of changing directory wouldn't propagate to any of the other commands, particularly not the ExecStart= command.

I'm not sure how that is a working file for you, since systemd doesn't really run a unit file if a command in ExecStartPre= fails, though I could see systemd ignoring this line (and logging about it) because it considers it incorrect syntax (versions of systemd require full path on commands they run until a very recent version) and it simply skips that line and moves on to the next one.

If that's really the case, then your daemon is being run under /home/inplant/IPSdevice/ and not /home/inplant/IPSdevice/JAI1/, and perhaps it works just as well there, which I wouldn't be surprised with, since daemons (particularly the well written ones) don't really care about which directory they're started at, so maybe that requirement didn't really exist to start with.

Furthermore, this command:

ExecStartPre=/bin/chown inplant.inplant /home/inplant/IPSdevice/JAI1

Should be unnecessary, since ExecStartPre= commands will run by default with the credentials specified by User= and Group=, so your mkdir is already creating a directory using the inplant user (in particular, that means this user needs to have permissions to create that directory), so there's no need to change ownership, since that should already be the ownership of the directory that was created.

| improve this answer | |
1

The tilde is a shell-specific expansion, but the directives specified in a unit file are not run using a shell. You will have to replace the ~ with an absolute path to the directory. Systemd is looking for a working directory with the name ~/IPSDevice/JAI1, literally.

| improve this answer | |
  • Thanks, that got me past the first issue. Now I have another issue I'll add to the question. – Wilda Hunden Aug 6 '19 at 14:53
1

I found that that I can't set the WorkingDirectory= to a non-existent directory even though the ExecStartPre created it. I had to use cd in an ExecStartPre directive to change to the directory after I created it. Here's the working file:

[Unit]
Description=JAI Device Service
# When systemd stops or restarts the app.service, the action is propagated to this unit
PartOf=app.service
# Start this unit after the app.service start
After=app-ips.service

[Service]
RuntimeDirectory=IPS-JAI1
User=inplant
Group=inplant
Environment=LD_LIBRARY_PATH=/opt/pleora/ebus_sdk/Ubuntu-x86_64/lib:/opt/pleora/ebus_sdk/Ubuntu-x86_64/lib/genicam/bin/Linux64_x64:/opt/xview2_64/lib/:/opt/GO_SDK/lib/linux_x64
Environment=GENICAM_ROOT_V3_0=/opt/pleora/ebus_sdk/Ubuntu-x86_64/lib/genicam
ExecStartPre=/bin/mkdir -p /home/inplant/IPSdevice/JAI1
ExecStartPre=/bin/chown inplant.inplant /home/inplant/IPSdevice/JAI1
ExecStartPre=cd /home/inplant/IPSdevice/JAI1
ExecStartPre=/bin/ls -al /home/inplant/IPSdevice/JAI1
WorkingDirectory=/home/inplant/IPSdevice/
ExecStart=/home/inplant/IPSdevice/IPSdevice JAI1
# Restart the service on non-zero exit code when terminated by a signal other than SIGHUP, SIGINT, SIGTERM or SIGPIPE
Restart=on-failure

[Install]
# This unit should start when app.service is starting
WantedBy=app-ips.service
```
| improve this answer | |

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.