83

Is it possible to give my systemd service more than one argument?

I'd like to execute a program with multiple arguments which have to be decided by the final user.

E.g: ./program arg1 arg2

To start it a single argument app I'd need something like systemctl start arg1@program, where in the service definition I have ExecStart = /usr/bin/program ℅i.

Thanks!

Thor
  • 3,970
  • 26
  • 31
peperunas
  • 1,221
  • 3
  • 12
  • 16

4 Answers4

68

Yes you can! Define them in a file somewhere and add them to EnvironmentFile in your systemd Service. For example, say the contents of /etc/.progconf are:

ARG1=-o
ARG2=--verbose

And your .service file:

EnvironmentFile=/etc/.progconf
ExecStart=/usr/bin/prog ${ARG1} ${ARG2}

You can write to that file if you need to change them on the go. A service shouldn't change its options very often, maybe consider autostarting or cron if you need to achieve that.

For more examples check: https://wiki.archlinux.org/index.php/Systemd/Services

alexm
  • 1,071
  • 9
  • 13
  • 1
    Heh, quite handy, didn't think of that. I have to agree though: Service parameters don't change on a regular basis, and neither do their config files. – Daniel B Mar 14 '14 at 11:55
  • if the service file uses environment variables, can you say `VAR1=... VAR2=... systemctl start foobar.service` to pass the variables? – Johannes Schaub - litb Jun 10 '15 at 18:16
  • Yes, I believe you can – alexm Jun 11 '15 at 11:31
  • 8
    @JohannesSchaub-litb, no, you can't. There is a `PassEnvironment` directive, but it takes variables from the `systemd` process (normally PID 1), not from `systemctl`. Environment variables from the `systemctl` process don't get propagated to the service being started. – cjm Mar 31 '16 at 19:55
  • 2
    But upstart can run multiple instances of the same service, with different parameters. For example a mail server on eth0 and another instance of said mailserver on eth1, passing the parameter to upstart and managing them as separate services. Can systemd do this? – LtWorf Jan 26 '17 at 10:03
  • Yes, SystemD supports such a feature using "Unit Templates". See a tutorial here: https://fedoramagazine.org/systemd-template-unit-files/ . It is functionally similar to passing a single argument to the unit. – Guss May 15 '17 at 11:26
  • I can't agree with the remark about service parameters don't change on a regular basis. They do whatever you want them to do. Cron isn't a standard and it will most likely be fully replaced in the future by systemd timers. Systemd services have different types, they are not always daemons whose configuration shouldn't change. Why a simple timed job's configuration shouldn't change? And also - this is not about changing a configuration. This is about being able to pass more than a single parameter to an instanced service. I want to pass two arguments that are NOT shared between instances. – Fanatique Apr 08 '21 at 08:53
  • @Fanatique Yes, they don't have to be daemons, you could run whatever you fancy. For the most part I use services as autostarts that require the knowledge of the environment, for example starting xorg and rotating/resizing the screen to desired orientation/size on a small embedded system. Again, if you want to change them with systemctl instead of a config file, the answer that nonagon posted might suit your case, rather my "set and forget" suggestion. – alexm Apr 09 '21 at 11:15
  • It looks like the variable name needs to be enclosed in curly braces, a la `${varname}`, according to what I see in the reference (and my experience) – Brian A. Henning Apr 24 '23 at 21:03
45

I wanted to do the same thing, but without a separate file for each combination of arguments. I found that I could pass one long argument with spaces and then use systemd's environment variable space-splitting feature to separate the arguments.

I made a service with filename argtest@.service (note the trailing 'at sign' which is required when a service takes arguments).

[Unit]
Description=Test passing multiple arguments

[Service]
Environment="SCRIPT_ARGS=%I"
ExecStart=/tmp/test.py $SCRIPT_ARGS

I run this with sudo systemctl start argtest@"arg1 arg2 arg3".service and it passes arg1, arg2 and arg3 as separate command-line arguments to test.py.

nonagon
  • 553
  • 4
  • 5
  • "note the trailing ampersand": I find no ampersand in your reply. Can you edit your reply to be clearer on this point? – Patrick Mevzek Apr 04 '18 at 23:12
  • Yes, sorry about that! – nonagon Apr 05 '18 at 02:20
  • I think the @ is only needed when you use the %I as you do. It's an instance of the service. – Toby Apr 09 '18 at 12:21
  • Agreed, I just ran into a few blog posts which omitted it. I'll clarify in my answer. – nonagon Apr 09 '18 at 15:31
  • This doesn't seem to work from within another service. I tried `Wants=argtest@"arg1 arg2".service` and only the first argument was passed. – Roger Dueck Jun 18 '19 at 22:05
  • 3
    On my debian I get `Invalid unit name "argtest@arg1 arg2.service" was escaped as "argtest@arg1\x20arg2.service" (maybe you should use systemd-escape?)` – sgargel Nov 29 '21 at 09:35
  • Tried to check status of "test-systemd-params@.service" with `systemctl status test-systemd-params@.service` and it tells me `Unit name test-systemd-params@.service is neither a valid invocation ID or unit name`. :( – DevOpsSauce Dec 08 '21 at 14:37
  • I had to use `${SCRIPT_ARGS}` instead of `$SCRIPT_ARGS` – Katu Feb 23 '22 at 12:09
  • 1
    > maybe you should use systemd-escape? `sudo systemctl start $(systemd-escape --template argtest@.service "arg1 arg2 arg3")` – David Lechner Apr 28 '22 at 17:48
  • actually, quotes are not needed: `sudo systemctl start $(systemd-escape --template argtest@.service arg1 arg2 arg3)` – David Lechner Apr 28 '22 at 17:56
  • There is no reason to use `Environment`. Just specify `ExecStart=/tmp/test.py %I`. – Quolonel Questions Dec 15 '22 at 01:24
6

Easiest I have found is:

ExecStart=/bin/bash -c "\"/path/with spaces/to/app\" arg1 arg2 arg3 \"arg with space\""

Keeps it all self contained.

Having said that, I have found that at least on Ubuntu 18.04 LTS, I don't even need to do that, I can do this and it works fine:

ExecStart="/path/with spaces/to/app" arg1 arg2 arg3 "arg with space"

$vars work as arguments with this pattern as well.

jjxtra
  • 163
  • 1
  • 6
3

Tell your users to override the service by creating a drop-in snippet through the use of the edit option in systemctl.

Firstly, start with some sane defaults. Let's assume the ExecStart part of your service declares the following:

[Service]
ExecStart=./program arg1

Now any user of this service can decide that they want to use a different set of arguments for running the program. They can do that by creating a drop-in snippet with:

systemctl edit myservice.service

In the new file, they simply write the following to override the ExecStart:

[Service]
ExecStart=
ExecStart=./program arg1 arg2

The benefit of doing it this way is that the end result does not involve modifying the system-installed service which may conflict with some package managers.

And that's it!

Now just reload systemd (systemctl daemon-reload) and restart the service (systemctl restart myservice.service).

When the service restarts, it will always also load the user's modifications.

I hope that helps


Sources:

smac89
  • 338
  • 4
  • 11