Writing a systemd unit file with a environment-set executable path

29,526

Solution 1

From the "Command lines" section in systemd.service(5):

Note that the first argument (i.e. the program to execute) may not be a variable.

I was going to suggest using the instance specifier %i (you can read more about it in systemd.unit(5)), but (now we're back in systemd.service(5)):

the first argument of the command line (i.e. the program to execute) may not include specifiers.

I think the best option at this point really is creating a shell script that wraps the execution of the java binary as suggested by Warren Young or you could ExecStart a shell directly like in the example for shell command lines in the "Command Lines" section of systemd.service(5) which has the following example:

ExecStart=/bin/sh -c 'dmesg | tac'

so you could do (untested):

ExecStart=/bin/sh -c '${JAVA_HOME}....'

Solution 2

Another similar option is to use /usr/bin/env:

ExecStart=/usr/bin/env "${JAVA_HOME}/bin/java" -jar ...

This way you can omit ' quotes around the whole command which is useful if you need to nest quoted stuff.

PS. As a side note, it is important to enclose variable names in {braces} in Systemd files or else they will not be recognized correctly.

Solution 3

Another, rather different option assumes usage of another system tool: alternatives. If your Java SDKs come from system packages, then most probably you are already set. If you install them by hand, you would have to add them to the system with something like that:

alternatives --install /usr/bin/java java /path/to/your/sdk/bin/java 3

The last number is priority (higher is more important). You can check existing priorities by issuing command alternatives --display java so you can decide what priority to pick for your new SDK.

Once installed you can just use /usr/bin/java in your service file and run alternatives --config java before starting service to determine which version you'd like to choose. Haven't really tried it, but it appears that you can do something like:

alternatives --set java /path/to/your/sdk/bin/java

...and have your SDK chosen from some script. This might be an option if the interactive interface for alternatives --config isn't suitable for your scenario. You might even do the setting in service file with ExecStartPre directive.

I really dislike wrapper script. Much of its contents probably will be identical to dreaded SysV init script (at least it's start section). Most probably service file starting with wrapper script would have to be a bit complicated by the fact, that command being executed isn't the process being run. If you can take time to go through many systemd directives, you just might find a cleaner way to achieve what you want without sticking to clunky wrapper scripts.

Share:
29,526

Related videos on Youtube

Robert Munteanu
Author by

Robert Munteanu

Summary In one life, I am Robert Munteanu, software engineer for a respectable software company. I write clean Java code, I maintain application builds ... help my colleagues discover bugs with automated analysis tools. The other life is lived in open source, where I go by the developer alias "Rombert" and try go get my code in virtually every open source software I have an use for.

Updated on September 18, 2022

Comments

  • Robert Munteanu
    Robert Munteanu over 1 year

    I am writing a systemd unit file for a Java application and I'd like to control the version of Java used to start it up. My (simplified) service file is

    [Service]
    Type=simple
    EnvironmentFile=%h/Documents/apps/app/app-%i/app.cfg
    ExecStart=${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar %h/Documents/apps/app/app-%i/myapp.jar
    SuccessExitStatus=143
    

    When trying to start it up I get an error back

    Apr 28 12:43:37 rombert systemd[1613]: [/home/robert/.config/systemd/user/[email protected]:7] Executable path is not absolute, ignoring: ${JAVA_HOME}/bin/java ${JAVA_OPT
    Apr 28 12:43:37 rombert systemd[1613]: [email protected] lacks both ExecStart= and ExecStop= setting. Refusing.
    

    I know that JAVA_HOME is correctly set ; if I change the ExecStart line to start with /usr/bin/java and then add something like -DsomeOption=${JAVA_HOME} I can see it just fine.

    The obvious workaround is to create a wrapper script but I feel that it defeats the point of using a service file.

    How can I set JAVA_HOME for my Java application using a unit file?

    • Warren Young
      Warren Young about 9 years
      Why does the wrapper script defeat the purpose of using a service file, exactly? You still get systemd's sequencing and dependency tracking, monitoring, etc. Basically, systemd trades away free-form programmability we had with SysVinit in favor of baked-in DTRT logic. When "the right thing" is something systemd doesn't do, you need to put that outside systemd, as in a shell script.
    • Robert Munteanu
      Robert Munteanu about 9 years
      @WarrenYoung - because I suddenly start managing shell scripts again. In my case not managing a shell script is more useful than the other bits.
    • Warren Young
      Warren Young about 9 years
      I really don't see the problem. Do you spend your days worrying about all the executables you have to manage, too? :)
    • Wieland
      Wieland about 9 years
      From systemd.service(5): "Note that the first argument (i.e. the program to execute) may not be a variable." That explains why ${JAVA_HOME} is not expanded at the beginning of the applications path, but is when used at some later point.
    • Robert Munteanu
      Robert Munteanu about 9 years
      @WarrenYoung - I prefer a single wrapper over the binary. I understand that it's not an issue to everyone, but it is for me :-)
    • Robert Munteanu
      Robert Munteanu about 9 years
      @Wieland - that sounds a lot like an answer I should accept ( even though I don't like it ). Can you please post it as an answer?
    • Warren Young
      Warren Young about 9 years
      It's a fair bet that file /usr/bin/* /usr/local/bin/* will turn up several shell scripts that wrap executables that live elsewhere. It's a fairly common thing to do, especially for Java-based programs. Just one example from my own experience: Apache FOP.