Fix console FIFO blocked by SELinux

minecraft.socket had systemd open the FIFO, which SELinux refuses:

  avc: denied { read write } for pid=1 comm="systemd" name="minecraft-console"
  scontext=...:init_t:s0 tcontext=...:var_run_t:s0 tclass=fifo_file

init_t has no read/write on a var_run_t fifo_file under the stock Fedora
targeted policy, so the socket unit never started and the service failed with
"a dependency job for minecraft.service failed".

The socket unit is gone. The service now creates the FIFO in its own
RuntimeDirectory and opens it as fd 3 read-write before exec'ing the JVM, so
the open never blocks, the reader never sees EOF when a writer disconnects, and
MAINPID stays the JVM. Nothing goes through PID 1, so the policy gap does not
apply. An obsolete minecraft.socket is removed on upgrade.

Console path moves to /run/minecraft/console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-08-06 00:37:50 +02:00
parent a141a31547
commit 6c85e30cc8
2 changed files with 31 additions and 32 deletions

View File

@ -131,18 +131,22 @@ up on a half-synced pack. Edit the config block at the top first — `PACK_DIR`,
## The server unit ## The server unit
`minecraft.service` pairs with `minecraft.socket`, a console FIFO at `minecraft.service` holds a console FIFO at `/run/minecraft/console`. That's
`/run/minecraft-console`. That's what makes a clean shutdown possible: what makes a clean shutdown possible:
```bash ```bash
sudo systemctl stop minecraft # sends "stop", world saves properly sudo systemctl stop minecraft # sends "stop", world saves properly
echo "say hello" | sudo tee /run/minecraft-console echo "say hello" | sudo tee /run/minecraft/console
journalctl -u minecraft -f # console output journalctl -u minecraft -f # console output
``` ```
Without the FIFO, systemd would SIGTERM the JVM and risk cutting a world save Without the FIFO, systemd would SIGTERM the JVM and risk cutting a world save
in half. in half.
The service creates and opens the FIFO itself rather than delegating to a
socket unit. SELinux denies `init_t` read/write on a `fifo_file` in `/run`, so
anything routed through PID 1 fails with `EACCES` on a stock Fedora policy.
## Web server ## Web server
`/minecraft` needs to be served over HTTP. Serve `pack.toml` and `index.toml` `/minecraft` needs to be served over HTTP. Serve `pack.toml` and `index.toml`

View File

@ -6,12 +6,12 @@
# sudo ./mc-service-setup.sh --accept-eula -N 21.1.72 -X 12G # sudo ./mc-service-setup.sh --accept-eula -N 21.1.72 -X 12G
# #
# Creates a `minecraft` system user, optionally installs the NeoForge server, # Creates a `minecraft` system user, optionally installs the NeoForge server,
# and writes minecraft.service + minecraft.socket. The socket is a console FIFO # and writes minecraft.service. The service holds a console FIFO at
# at /run/minecraft-console, which is what lets systemd stop the server with a # /run/minecraft/console, which lets systemd stop the server with a real `stop`
# real `stop` command so the world saves instead of being killed mid-write. # command so the world saves instead of being killed mid-write.
# #
# systemctl start|stop|restart minecraft # systemctl start|stop|restart minecraft
# echo "say hello" | sudo tee /run/minecraft-console # console commands # echo "say hello" | sudo tee /run/minecraft/console # console commands
# journalctl -u minecraft -f # console output # journalctl -u minecraft -f # console output
set -euo pipefail set -euo pipefail
@ -29,7 +29,7 @@ MCVER="1.21.1" # only used to pick the matching NeoForge series
ADMIN="" # human user who authors the pack on the share ADMIN="" # human user who authors the pack on the share
ACCEPT_EULA=0 ACCEPT_EULA=0
UNIT_DIR="/etc/systemd/system" UNIT_DIR="/etc/systemd/system"
FIFO="/run/minecraft-console" FIFO="/run/minecraft/console" # console FIFO, inside the unit's RuntimeDirectory
die() { echo "error: $*" >&2; exit 1; } die() { echo "error: $*" >&2; exit 1; }
info() { echo ">>> $*"; } info() { echo ">>> $*"; }
@ -287,22 +287,14 @@ fi
# ----------------------------------------------------------------- units ---- # ----------------------------------------------------------------- units ----
info "writing $UNIT_DIR/minecraft.socket" # A previous version shipped a minecraft.socket that had systemd itself open
cat > "$UNIT_DIR/minecraft.socket" <<EOF # the FIFO. SELinux denies init_t read/write on a var_run_t fifo_file, so that
[Unit] # never worked; remove it if it is still around.
Description=Minecraft server console FIFO if [ -e "$UNIT_DIR/minecraft.socket" ]; then
info "removing obsolete minecraft.socket"
# No Service= and no PartOf= here on purpose. minecraft.service already declares systemctl disable --now minecraft.socket >/dev/null 2>&1 || true
# Requires=/After=/Sockets= on this unit, and pointing this one back at the rm -f "$UNIT_DIR/minecraft.socket"
# service as well forms an ordering cycle that systemd resolves by failing the fi
# job — which surfaces as "a dependency job for minecraft.service failed".
[Socket]
ListenFIFO=$FIFO
SocketUser=$MCUSER
SocketGroup=$MCUSER
SocketMode=0660
RemoveOnStop=yes
EOF
info "writing $UNIT_DIR/minecraft.service" info "writing $UNIT_DIR/minecraft.service"
cat > "$UNIT_DIR/minecraft.service" <<EOF cat > "$UNIT_DIR/minecraft.service" <<EOF
@ -311,8 +303,6 @@ Description=Minecraft Server (NeoForge)
Documentation=https://docs.neoforged.net/ Documentation=https://docs.neoforged.net/
After=network-online.target zfs-mount.service After=network-online.target zfs-mount.service
Wants=network-online.target Wants=network-online.target
Requires=minecraft.socket
After=minecraft.socket
RequiresMountsFor=$SHARE RequiresMountsFor=$SHARE
[Service] [Service]
@ -321,15 +311,20 @@ User=$MCUSER
Group=$MCUSER Group=$MCUSER
WorkingDirectory=$SHARE WorkingDirectory=$SHARE
# The console FIFO becomes the server's stdin, so ExecStop can hand it a real
# 'stop' command. Without this systemd would SIGTERM the JVM and risk a world
# save being cut in half.
Sockets=minecraft.socket
StandardInput=socket
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
ExecStart=/usr/bin/java @$SHARE/user_jvm_args.txt @$ARGS_FILE nogui # Console FIFO. The service creates and opens it itself rather than having a
# socket unit do it: SELinux denies init_t read/write on a fifo_file in /run,
# so anything routed through PID 1 fails with EACCES.
#
# fd 3 is opened read-write so the open does not block and the reader never
# sees EOF when a writer disconnects; java then inherits it as stdin. The
# second exec replaces the shell, so MAINPID is the JVM.
RuntimeDirectory=minecraft
RuntimeDirectoryMode=0755
ExecStartPre=/bin/sh -c 'rm -f $FIFO && mkfifo -m 0660 $FIFO'
ExecStart=/bin/bash -c 'exec 3<>$FIFO; exec /usr/bin/java @$SHARE/user_jvm_args.txt @$ARGS_FILE nogui <&3'
ExecStop=/bin/sh -c '/bin/echo stop > $FIFO' ExecStop=/bin/sh -c '/bin/echo stop > $FIFO'
# Generous: a big world with many chunks loaded can take a while to save. # Generous: a big world with many chunks loaded can take a while to save.