moonlight200 / minecraft-tmux-service

A systemd service that starts the minecraft server in a tmux session
GNU General Public License v3.0
36 stars 14 forks source link

minecraft.service launches script, then process exits #1

Open mstarks01 opened 3 years ago

mstarks01 commented 3 years ago

Thanks for making these scripts available. They are by far the best of the minecraft init scripts I have run across.

I'm having a bit of an issue when launching it from systemctrl. The tmux and java process start, then stop a second or two later. The shell script works correctly; it's only when launching from systemd that I have an issue.

root@dorkcraft:/usr/local/bin/minecraft# systemctl status minecraft
* minecraft.service - Minecraft Server
     Loaded: loaded (/lib/systemd/system/minecraft.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Fri 2021-01-01 18:42:58 CST; 18min ago
    Process: 5246 ExecStart=/usr/local/bin/minecraft/service.sh start (code=exited, status=0/SUCCESS)
    Process: 5283 ExecStop=/usr/local/bin/minecraft/service.sh stop (code=exited, status=1/FAILURE)
   Main PID: 5250 (code=exited, status=0/SUCCESS)

Jan 01 18:42:52 dorkcraft systemd[1]: Starting Minecraft Server...
Jan 01 18:42:52 dorkcraft service.sh[5246]: Starting minecraft server in tmux session
Jan 01 18:42:52 dorkcraft systemd[1]: Started Minecraft Server.
Jan 01 18:42:58 dorkcraft service.sh[5283]: Server is not running!
Jan 01 18:42:58 dorkcraft systemd[1]: minecraft.service: Control process exited, code=exited, status=1/FAILURE
Jan 01 18:42:58 dorkcraft systemd[1]: minecraft.service: Failed with result 'exit-code'.

Here's the service script. The only changes I've made are to change the location to my install.

[Unit]
Description=Minecraft Server

Wants=network.target
After=network.target

[Service]
Type=forking
User=minecraft
Group=minecraft
KillMode=none

ProtectHome=read-only
ProtectSystem=full
PrivateDevices=no
NoNewPrivileges=yes
PrivateTmp=no
InaccessiblePaths=/root /sys /srv -/opt /media -/lost+found
ReadWritePaths=/usr/local/bin/minecraft
WorkingDirectory=/usr/local/bin/minecraft
ExecStart=/usr/local/bin/minecraft/service.sh start
ExecReload=/usr/local/bin/minecraft/service.sh reload
ExecStop=/usr/local/bin/minecraft/service.sh stop

[Install]
WantedBy=multi-user.target

And here's the shell script. It is executable. I changed the location here as well as a couple of minecraft arguments. Note that $MC_HOME/minecraft/minecraft-server is simply a link, so that is correct.

# Minecraft service that starts the minecraft server in a tmux session

MC_HOME="/usr/local/bin/minecraft"

TMUX_SOCKET="minecraft"
TMUX_SESSION="minecraft"

is_server_running() {
    tmux -L $TMUX_SOCKET has-session -t $TMUX_SESSION > /dev/null 2>&1
    return $?
}

mc_command() {
    cmd="$1"
    tmux -L $TMUX_SOCKET send-keys -t $TMUX_SESSION.0 "$cmd" ENTER
    return $?
}

start_server() {
    if is_server_running; then
        echo "Server already running"
        return 1
    fi
    echo "Starting minecraft server in tmux session"
    tmux -L $TMUX_SOCKET new-session -c $MC_HOME -s $TMUX_SESSION -d /usr/bin/java -Xms512M -Xmx1024M -jar $MC_HOME/minecraft/minecraft-server nogui
    return $?
}

stop_server() {
    if ! is_server_running; then
        echo "Server is not running!"
        return 1
    fi

    # Warn players
    echo "Warning players"
    mc_command "title @a times 3 14 3"
    for i in {10..1}; do
        mc_command "title @a subtitle {\"text\":\"in $i seconds\",\"color\":\"gray\"}"
        mc_command "title @a title {\"text\":\"Shutting down\",\"color\":\"dark_red\"}"
        sleep 1
    done

    # Issue shutdown
    echo "Kicking players"
    mc_command "kickall"
    echo "Stopping server"
    mc_command "stop"
    if [ $? -ne 0 ]; then
        echo "Failed to send stop command to server"
        return 1
    fi

    # Wait for server to stop
    wait=0
    while is_server_running; do
        sleep 1

        wait=$((wait+1))
        if [ $wait -gt 60 ]; then
            echo "Could not stop server, timeout"
            return 1
        fi
    done

    return 0
}

reload_server() {
    tmux -L $TMUX_SOCKET send-keys -t $TMUX_SESSION.0 "reload" ENTER
    return $?
}

attach_session() {
    if ! is_server_running; then
        echo "Cannot attach to server session, server not running"
        return 1
    fi

    tmux -L $TMUX_SOCKET attach-session -t $TMUX_SESSION
    return 0
}

case "$1" in
start)
    start_server
    exit $?
    ;;
stop)
    stop_server
    exit $?
    ;;
reload)
    reload_server
    exit $?
    ;;
attach)
    attach_session
    exit $?
    ;;
*)
    echo "Usage: ${0} {start|stop|reload|attach}"
    exit 2
    ;;
esac

This is running in an Ubuntu 20.04 LXC container. More info:

root@dorkcraft:~# uname -a
Linux dorkcraft 5.4.34-1-pve #1 SMP PVE 5.4.34-2 (Thu, 07 May 2020 10:02:02 +0200) x86_64 x86_64 x86_64 GNU/Linux
root@dorkcraft:~# systemd --version
systemd 245 (245.4-4ubuntu3.3)
+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=hybrid
moonlight200 commented 3 years ago

+1 for all of the information provided.

I don't see any mistake in your changes. Does the Minecraft server log file state any reason why the server is shutting down?


Looking at the man page of systemd.exec, I found this in the section about ReadWritePaths=

Use ReadWritePaths= in order to whitelist specific paths for write access if ProtectSystem=strict is used.

It doesn't state what happens if ProtectSystem is set to full. Although unlikely, there is a chance that changing it to strict could help. A side effect would be, that /tmp gets protected, too. This needs to stay writeable for the minecraft user to allow tmux creating its socket in there.

ProtectSystem=strict
ReadWritePaths=/usr/local/bin/minecraft /tmp

On the other hand you could also try to relax the restrictions done by systemd since you're already using containerization. Although I have no experience with LXC and don't know what parts of the system are isolated by it.

If you want to leave it to LXC or don't need the extra protection, you can remove the lines from ProtectSystem to ReadWritePaths.

mstarks01 commented 3 years ago

Thanks for replying. Upon further inspection, I realized that although the process didn't die when running from the script, I could not connect to the tmux session. It thought there were no sessions. So I started to simplify the command. I took out -c $MC_HOME, and when that didn't help, -L $TMUX_SOCKET. Taking out the socket allowed me to connect to the session. I suspect this might be due to an apparmor profile protecting the /tmp directory, although I also don't understand the use of -c $MC_HOME here, since it is only a directory.

moonlight200 commented 3 years ago

I added the separate socket so that no one could accidentally close the tmux session, as it won't show up when using the default socket. This is to help preventing that the server stops without systemd knowing about it. If there was a pid file systemd could monitor the process directly, but I couldn't find one for tmux / the process running in the tmux terminal. Using the default socket should work just as fine.

The -c $MC_HOME specifies the directory that the tmux session is started in. -c has a different meaning when used with tmux directly (similar to -L $TMUX_SOCKET) than when used with the new-session sub-comand. Basically this specifies the working directory and a cd $MC_HOME && java -jar ... has the same effect.

moonlight200 commented 3 years ago

I found a way to create a pid file for the minecraft process. Maybe this will help with your problem, as systemd recommends having a PIDFile set if the service type is forking to determine the main process. Determining the pid relies on the tmux session being the only one on the socket, so be careful with using the default socket. I hope this helps :slightly_smiling_face:

mstarks01 commented 3 years ago

Thanks! I have made a couple of posts trying to get to the bottom of the socket issue in an unprivileged LXC container, but have yet to receive any feedback. I suspect that it's a container issue, but don't understand why the user should not be able to access their own socket.

I'll give your change a try when I have a few spare moments.

Glowsome commented 2 years ago

i went for a different approach, on my ProxMox LXC RockyLinux (now 8.6) container and wanted to share it :

Assumptions:

systemd unitfile:

[[Unit]
Description=Minecraft Server

[Service]
WorkingDirectory=/opt/minecraft
User=minecraft
Type=forking

ExecStart=/usr/bin/tmux new -s minecraft -d "/usr/bin/java -Xmx3096M -Xms2048M -XX:+UseG1GC -jar server.jar --nogui"
ExecReload=/usr/bin/tmux send-keys -t minecraft:0.0 'say SERVER RELOADING.' C-m 'reload' C-m
ExecStop=/usr/bin/tmux send-keys -t minecraft:0.0 'say SERVER SHUTTING DOWN. Saving map...' C-m 'save-all' C-m 'stop' C-m
ExecStop=/bin/sleep 2

[Install]
WantedBy=multi-user.target

Additional info: