我正在为 Java 应用程序编写一个 systemd 单元文件,并且我想控制用于启动它的 Java 版本。我的(简化的)服务文件是
[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
当尝试启动它时,我收到错误消息
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.
我知道JAVA_HOME
设置正确;如果我更改行ExecStart
的开头/usr/bin/java
,然后添加类似的内容,-DsomeOption=${JAVA_HOME}
我就可以看到它了。
明显的解决方法是创建一个包装器脚本,但我觉得它违背了使用服务文件的目的。
如何使用单元文件为我的 Java 应用程序设置 JAVA_HOME?
答案1
来自 systemd.service(5) 中的“命令行”部分:
请注意,第一个参数(即要执行的程序)可能不是变量。
我本来建议使用实例说明符%i
(您可以在 systemd.unit(5) 中阅读更多相关信息),但是(现在我们回到了 systemd.service(5)):
命令行的第一个参数(即要执行的程序)可能不包含说明符。
我认为此时最好的选择确实是创建一个 shell 脚本,按照 Warren Young 的建议包装 java 二进制文件的执行,或者您可以直接 ExecStart 一个 shell,就像“命令行”部分中的 shell 命令行示例一样。 systemd.service(5) 具有以下示例:
ExecStart=/bin/sh -c 'dmesg | tac'
所以你可以这样做(未经测试):
ExecStart=/bin/sh -c '${JAVA_HOME}....'
答案2
另一个类似的选项是使用/usr/bin/env
:
ExecStart=/usr/bin/env "${JAVA_HOME}/bin/java" -jar ...
这样,您可以省略'
整个命令周围的引号,这在您需要嵌套引号内容时很有用。
附言。附带说明一下,在 Systemd 文件中将变量名称括在{
大括号中非常重要}
,否则将无法正确识别它们。
答案3
另一个相当不同的选项假设使用另一个系统工具:alternatives
.如果您的 Java SDK 来自系统包,那么很可能您已经准备好了。如果您手动安装它们,则必须使用类似以下内容将它们添加到系统中:
alternatives --install /usr/bin/java java /path/to/your/sdk/bin/java 3
最后一个数字是优先级(越高越重要)。您可以通过发出命令来检查现有优先级alternatives --display java
,以便决定为新 SDK 选择哪个优先级。
安装后,您可以/usr/bin/java
在服务文件中使用并alternatives --config java
在启动服务之前运行以确定您想要选择哪个版本。还没有真正尝试过,但看来你可以这样做:
alternatives --set java /path/to/your/sdk/bin/java
...并从某个脚本中选择您的 SDK。如果交互界面alternatives --config
不适合您的场景,这可能是一个选项。您甚至可以使用指令在服务文件中进行设置ExecStartPre
。
我真的不喜欢包装脚本。它的大部分内容可能与可怕的 SysV 初始化脚本相同(至少是开始部分)。最有可能的是,以包装器脚本开头的服务文件必须有点复杂,因为正在执行的命令不是正在运行的进程。如果您能花时间浏览许多 systemd 指令,您可能会找到一种更简洁的方法来实现您想要的目标,而无需坚持使用笨重的包装脚本。