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.