Using systemd's Exec*-command to pass commands to the process
I'm building up my linux knowledge (at least I'm trying) by setting up services for different game servers.
Currently I'm working on a Minecraft Server that shall be started automatically on a server restart. Everything seems to work but I assume the way I'm terminating the server on stop command is not the best one.
That's the current unit file:
[Unit]
Description=Minecraft Dedicated Server
After=network.service
[Service]
User=user
Group=user_group
Type=simple
WorkingDirectory=/usr/local/bin/services/minecraft/
ExecStart=/usr/local/bin/services/minecraft/run.sh
ExecStop=/bin/kill -9 $MAINPID
RestartSec=15
Restart=on-failure
[Install]
WantedBy=multi-user.target
I've read that simply using the kill command will not terminate the server gracefully and one should use the minecraft-internal "stop" command instead. Unfortunately I could not figure out how to modify the ExecStop command to send internal commands to the minecraft process.
EDIT:
The following has been implemented:
minecraft.service:
[Unit]
Description=Minecraft Dedicated Server
After=network.service
[Service]
User=user
Group=user_group
Type=simple
KillSignal=SIGCONT
Sockets=minecraft.socket
WorkingDirectory=/usr/local/bin/services/minecraft/
ExecStart=/bin/sh -c "java -Xms2G -Xmx4G -XX:+UseConcMarkSweepGC -jar spigot-1.14.4.jar < /run/minecraft.control"
ExecStop=/bin/sh -c "echo /stop > /run/minecraft.control"
RestartSec=15
Restart=on-failure
[Install]
WantedBy=multi-user.target
minecraft.socket:
[Unit]
BindsTo=minecraft.service
[Socket]
ListenFIFO=/run/minecraft.control
RemoveOnStop=true
sockeSocketMode=0664
SocketUser=user
SocketGroup=user_group
Unfortunately, the commands passed through the minecraft.control fifo do not work.
I observed the following behavior: When sending a command using
echo "/help" > /run/minecraft.control
sometimes gives me an output when viewing the minecraft.control fifo (with cat) and sometimes not. If there's no output in the fifo, the journal of the minecraft.service unit displays Unknown command. Type "/help" for help. although I've sent "/help" as command. Additionally, the unit's ExecStop command
echo "/stop" > /run/minecraft.control
does not work either and the server gets killed by the sigkill signal instead.
Any idea why this does not work?
Solution 1:
Add a new minecraft.socket file (assuming your service is named minecraft.service) to bind your server's input to:
[Unit] BindsTo=minecraft.service [Socket] ListenFIFO=/run/minecraft.control FileDescriptorName=control RemoveOnStop=true SocketMode=0660 SocketUser=user SocketGroup=user_group
Then modify your minecraft.service file to not send SIGTERM, bind standard input to the socket by modifying ExecStart with an exec wrapper. Then ExecStop will simply write to the new socket to send "/stop\n" to the input.
[Service] ... KillSignal=SIGCONT Sockets=minecraft.socket ExecStart=/bin/sh -c "exec /usr/local/bin/services/minecraft/run.sh </run/test.control" ExecStop=/bin/sh -c "echo /stop >/run/minecraft.control"
Bonus, you'll be able to send command to the server using echo "/command" > /run/minecraft.control
anytime.
You don't have to take care of adding dependencies to the .service file, it is automatically added by the Sockets= option, the BindsTo= option will take care of stopping the socket and cleaning up the FIFO file when the service stops.
Here are some clarifications from suggestions of the original author.
Why do we have to use KillSignal=SIGCONT?
This prevent sending SIGTERM to the server process and cause a premature stopping of the process. It will still send SIGKILL after a certain time unless you add some more parameters (man systemd.kill).
Sockets=xxx contains the name of the Socket you created above, correct?
Yes
The socket-file (in this case minecraft.socket?) has to be placed in the same directory as the service?
Yes, usually /etc/systemd/system/ if added by the sysadmin.