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
`minecraft.service` pairs with `minecraft.socket`, a console FIFO at
`/run/minecraft-console`. That's what makes a clean shutdown possible:
`minecraft.service` holds a console FIFO at `/run/minecraft/console`. That's
what makes a clean shutdown possible:
```bash
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
```
Without the FIFO, systemd would SIGTERM the JVM and risk cutting a world save
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
`/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
#
# Creates a `minecraft` system user, optionally installs the NeoForge server,
# and writes minecraft.service + minecraft.socket. The socket is a console FIFO
# at /run/minecraft-console, which is what lets systemd stop the server with a
# real `stop` command so the world saves instead of being killed mid-write.
# and writes minecraft.service. The service holds a console FIFO at
# /run/minecraft/console, which lets systemd stop the server with a real `stop`
# command so the world saves instead of being killed mid-write.
#
# 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
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
ACCEPT_EULA=0
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; }
info() { echo ">>> $*"; }
@ -287,22 +287,14 @@ fi
# ----------------------------------------------------------------- units ----
info "writing $UNIT_DIR/minecraft.socket"
cat > "$UNIT_DIR/minecraft.socket" <<EOF
[Unit]
Description=Minecraft server console FIFO
# No Service= and no PartOf= here on purpose. minecraft.service already declares
# Requires=/After=/Sockets= on this unit, and pointing this one back at the
# service as well forms an ordering cycle that systemd resolves by failing the
# job — which surfaces as "a dependency job for minecraft.service failed".
[Socket]
ListenFIFO=$FIFO
SocketUser=$MCUSER
SocketGroup=$MCUSER
SocketMode=0660
RemoveOnStop=yes
EOF
# A previous version shipped a minecraft.socket that had systemd itself open
# the FIFO. SELinux denies init_t read/write on a var_run_t fifo_file, so that
# never worked; remove it if it is still around.
if [ -e "$UNIT_DIR/minecraft.socket" ]; then
info "removing obsolete minecraft.socket"
systemctl disable --now minecraft.socket >/dev/null 2>&1 || true
rm -f "$UNIT_DIR/minecraft.socket"
fi
info "writing $UNIT_DIR/minecraft.service"
cat > "$UNIT_DIR/minecraft.service" <<EOF
@ -311,8 +303,6 @@ Description=Minecraft Server (NeoForge)
Documentation=https://docs.neoforged.net/
After=network-online.target zfs-mount.service
Wants=network-online.target
Requires=minecraft.socket
After=minecraft.socket
RequiresMountsFor=$SHARE
[Service]
@ -321,15 +311,20 @@ User=$MCUSER
Group=$MCUSER
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
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'
# Generous: a big world with many chunks loaded can take a while to save.