- Rust 89.5%
- Shell 10.5%
|
Some checks failed
Cargo Build & Test / beta - aarch64-unknown-linux-gnu (push) Has been cancelled
Cargo Build & Test / nightly - aarch64-unknown-linux-gnu (push) Has been cancelled
Cargo Build & Test / stable - aarch64-unknown-linux-gnu (push) Has been cancelled
Cargo Build & Test / beta - aarch64-unknown-linux-musl (push) Has been cancelled
Cargo Build & Test / nightly - aarch64-unknown-linux-musl (push) Has been cancelled
Cargo Build & Test / stable - aarch64-unknown-linux-musl (push) Has been cancelled
Cargo Build & Test / beta - x86_64-unknown-linux-gnu (push) Has been cancelled
Cargo Build & Test / nightly - x86_64-unknown-linux-gnu (push) Has been cancelled
Cargo Build & Test / stable - x86_64-unknown-linux-gnu (push) Has been cancelled
Cargo Build & Test / beta - x86_64-unknown-linux-musl (push) Has been cancelled
Cargo Build & Test / nightly - x86_64-unknown-linux-musl (push) Has been cancelled
Cargo Build & Test / stable - x86_64-unknown-linux-musl (push) Has been cancelled
Deploy Documentation / build (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
Listeners struct Replace the ambiguous bool return value with an explicit SocketMode enum (Systemd/Standalone). Replace the perhaps overkill HashMap with a simple Listeners struct containing named fields for minecraft and bluemap listeners. |
||
|---|---|---|
| .github/workflows | ||
| src | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| config.toml.example | ||
| fabric-servers-wrapper-bluemap.socket | ||
| fabric-servers-wrapper.service | ||
| fabric-servers-wrapper.socket | ||
| LICENSE | ||
| README.md | ||
| STRUCTURED_LOGGING.md | ||
| view-structured-logs.sh | ||
fabric-servers-tmux-wrapper
A daemon for launching a network of Minecraft ("Fabric") servers in separate tmux windows behind a single Velocity proxy. Supports both systemd socket activation and standalone mode (for containers or non-systemd environments). When the proxy (Velocity) goes down, the daemon attempts a graceful shutdown of all game servers, escalating to SIGKILL if necessary.
Features
- Dual-mode operation: Systemd socket activation or standalone mode
- Systemd mode: Socket-activated via
.socket+.serviceunits - Standalone mode: Creates own listeners, ideal for containers or non-systemd environments
- Mode automatically detected based on environment
- Systemd mode: Socket-activated via
- Optional TOML configuration with XDG Base Directory support
(
~/.config/fabric-servers/config.toml) - Lazily loads each server directory under
~/.local/share/srv/ - Auto-discovery of server directories with glob pattern exclusions
- Per-server custom startup commands for flexible configurations
- One tmux session (
fabric-servers) with one window per server - Proxy-required architecture: all game-server traffic is forwarded through Velocity on port 25564
- Configuration validation on startup with helpful error messages
- Graceful and forcible cleanup of server processes if the proxy dies
- BlueMap web server integration with automatic proxy fallback to static files
- Real IP detection from reverse proxy headers (X-Real-IP, X-Forwarded-For)
- Built with Rust, Tokio, serde, toml, xdg, nix (signals), glob, dirs
Requirements
- Linux (systemd optional - see "Operation Modes" below)
- tmux
- Rust toolchain (stable)
- Minecraft servers in
~/.local/share/srv/<server-name>/run - Velocity proxy listening on port
25564 - (Optional) lazymc for on-demand server startup
nixcrate for POSIX signals
Operation Modes
The daemon automatically detects which mode to run in:
Systemd Mode (socket activation):
- Requires systemd ≥ v229
- Uses systemd socket activation for zero-downtime restarts
- Logs to journald
- Exits after server shutdown (systemd handles restart)
- Familiarity with socket activation helpful (
systemd.socket(5),sd_listen_fds(3))
Standalone Mode (direct listening):
- Works without systemd (containers, traditional init systems, development)
- Creates own TCP listeners on configured ports (default: 25565, 31898)
- Logs to stderr with ANSI colors
- Automatically resets and resumes after server shutdown
- No special setup required - just run the binary
Installation
Systemd Mode (socket activation)
-
Clone & build the binary:
git clone https://github.com/lucca-pellegrini/minecraft-servers-tmux-wrapper.git cd minecraft-servers-tmux-wrapper cargo build --release -
Install the binary:
install -D target/release/minecraft-servers-tmux-wrapper ~/.local/bin/minecraft-servers-tmux-wrapper chmod +x ~/.local/bin/minecraft-servers-tmux-wrapper -
Create your server directories and startup scripts:
~/.local/share/srv/ ├── lobby/ │ └── run # +x script to launch your server ├── world1/ │ └── run └── world2/ └── run -
Copy the example systemd units into
~/.config/systemd/user/:-
fabric-servers-wrapper.socket
[Unit] Description=Socket-activated Minecraft proxy listener [Socket] ListenStream=25565 NoDelay=true Service=fabric-servers-wrapper.service [Install] WantedBy=sockets.target -
fabric-servers-wrapper-bluemap.socket (optional, for BlueMap support)
[Unit] Description=Socket-activated proxy for BlueMap [Socket] ListenStream=31898 NoDelay=true Service=fabric-servers-wrapper.service [Install] WantedBy=sockets.target -
fabric-servers-wrapper.service
[Unit] Description=Launch Minecraft servers in tmux & forward traffic After=network.target [Service] ExecStart=%h/.local/bin/fabric-servers-tmux-wrapper StandardInput=socket StandardOutput=journal StandardError=journal Restart=on-failure [Install] WantedBy=default.target
-
-
Enable and start:
systemctl --user daemon-reload systemctl --user enable --now fabric-servers-wrapper.socket # Optional: enable BlueMap socket systemctl --user enable --now fabric-servers-wrapper-bluemap.socket
Standalone Mode (direct listening)
- Clone & build the binary:
git clone https://github.com/lucca-pellegrini/minecraft-servers-tmux-wrapper.git cd minecraft-servers-tmux-wrapper cargo build --release - Install the binary (optional - can run directly from target/release/):
install -D target/release/minecraft-servers-tmux-wrapper ~/.local/bin/minecraft-servers-tmux-wrapper chmod +x ~/.local/bin/minecraft-servers-tmux-wrapper - Create your server directories (same as systemd mode):
~/.local/share/srv/ ├── lobby/ │ └── run ├── world1/ │ └── run └── world2/ └── run - Run directly:
Or with custom ports via config.toml (see Configuration section).~/.local/bin/minecraft-servers-tmux-wrapper
The daemon automatically detects standalone mode and creates its own listeners on the configured ports (default: 25565 for Minecraft, 31898 for BlueMap).
How It Works
-
Configuration Loading
- Attempts to load
~/.config/fabric-servers/config.toml(XDG Base Directory specification) - Falls back to sensible defaults if no configuration file exists
- Validates configuration for correctness (ports, paths, patterns, etc.)
- Attempts to load
-
Mode Detection & Socket Setup
- Systemd mode: Detects systemd socket activation (LISTEN_FDS environment variable) and receives listeners on FD 3+ from systemd
- Standalone mode: Creates own TCP listeners on configured ports when systemd sockets are not available
- Mode detection is automatic - no configuration required
- See
systemd.socket(5),sd_listen_fds(3), and Lennart Poettering's blog: https://0pointer.de/blog/projects/socket-activation.html
-
Startup & tmux Session
- Reads
$XDG_RUNTIME_DIR(defaults to/tmp) and creates a tmux socketfabric-servers.sock. - Kills any existing
fabric-serverssession on that socket. - If
[proxy]is configured, starts the proxy server first. - Scans configured
servers_dirfor subdirectories (if discovery enabled). - Filters discovered servers using glob exclusion patterns
(e.g.,
*.backup,template-*). - Creates one tmux window per server, using configured or default startup scripts.
- The proxy window will always have ID 0 (or 1, depending on your tmux configuration).
- Reads
-
Proxy Health-check
- Waits up to
buffer_timeoutseconds (default: 90) for the proxy to accept connections on127.0.0.1:<proxy_port>(default: 25564). - If the proxy fails to start, the daemon exits with error.
- Waits up to
-
Traffic Forwarding
- Accepts incoming Minecraft client connections on the configured port (default: 25565).
- For each client, opens a TCP tunnel to
127.0.0.1:<proxy_port>and proxies all bytes bidirectionally.
-
BlueMap Web Server
- Listens for HTTP connections on configured port (default: 31898).
- Automatically discovers BlueMap web directory in server installations.
- Attempts to proxy requests to live BlueMap mod backend on port 8100
(configurable via
bluemap_modport). - Falls back to serving static files when backend is unavailable.
- Returns HTTP 204 for missing tiles/live data (prevents client errors).
- Supports gzip-compressed static files for bandwidth efficiency.
-
Cleanup on Proxy Exit
- Continuously polls the proxy every 5 seconds.
- On proxy failure, spawns shutdown tasks for each tmux‐managed server:
- Grace period: 10 seconds
- Retries SIGTERM, SIGINT, SIGHUP (3× each, 10 seconds in between)
- Finally SIGKILL if still alive
-
Shutdown & Restart Behavior
- Systemd mode: Daemon exits after server shutdown; systemd handles restart on next connection
- Standalone mode: Daemon resets state and resumes listening for new connections without exiting
Configuration
The daemon supports TOML configuration via ~/.config/fabric-servers/config.toml
(following the XDG Base Directory specification). Configuration is entirely
optional — without a config file, sensible defaults are used that match
the original hardcoded behavior.
Configuration File Location
- Path:
$XDG_CONFIG_HOME/fabric-servers/config.toml - Default:
~/.config/fabric-servers/config.toml
If the configuration file doesn't exist, the daemon uses built-in defaults.
Configuration Validation
The configuration is validated on startup. Common errors detected include:
- Empty or zero values for required fields
- Port conflicts (same port used multiple times)
- Duplicate server names
- Invalid glob patterns in discovery exclusions
- Missing required fields in server/proxy configuration
Configuration Sections
[global] - Global Settings
[global]
servers_dir = ".local/share/srv" # Server directories (relative to ~)
tmux_session = "fabric-servers" # tmux session name
default_startup_script = "./run" # Default server startup script
buffer_timeout = 90 # Proxy startup timeout (seconds)
init_timeout = 60 # Client connection timeout (seconds)
Defaults:
servers_dir:".local/share/srv"(relative to home directory)tmux_session:"fabric-servers"default_startup_script:"./run"buffer_timeout:90secondsinit_timeout:60seconds
[ports] - Port Configuration
[ports]
client = 25565 # Minecraft client connections (systemd or standalone)
bluemap = 31898 # BlueMap web interface (systemd or standalone)
proxy = 25564 # Velocity proxy backend
bluemap_mod = 8100 # BlueMap mod backend
Defaults:
client:25565bluemap:31898proxy:25564bluemap_mod:8100
Notes:
- In systemd mode: Ensure systemd socket units match the
clientandbluemapports - In standalone mode: Daemon creates listeners on
clientandbluemapports directly
[proxy] - Explicit Proxy Configuration
[proxy]
path = "Velocity" # Proxy directory name (in servers_dir)
startup_command = "./run" # Proxy startup script
working_dir = "Velocity" # Optional: custom working directory
Optional section. If specified, this directory is started first as the proxy server. If omitted and discovery is enabled, the first discovered server is used (with a warning logged).
Fields:
path: Relative path withinservers_dir(required)startup_command: Command to start the proxy (required)working_dir: Optional working directory override
[[servers]] - Per-Server Configuration
[[servers]]
name = "survival" # Server name (must match directory)
path = "survival" # Directory path (in servers_dir)
startup_command = "./run-custom.sh" # Custom startup command
working_dir = "survival" # Optional: custom working directory
[[servers]]
name = "creative"
path = "creative"
startup_command = "./run"
Optional section. Define custom startup commands for specific servers.
Servers not listed here use default_startup_script from [global].
Fields:
name: Server name - must match directory name (required)path: Relative path withinservers_dir(required)startup_command: Custom startup command (required)working_dir: Optional working directory override
Use cases:
- Servers with different startup scripts (e.g.,
start.shvs./run) - Servers requiring special launch arguments
- Testing environments with custom configurations
[discovery] - Auto-Discovery Settings
[discovery]
enabled = true # Enable auto-discovery
exclude = ["*.backup", "template-*"] # Exclude patterns (glob syntax)
Defaults:
enabled:trueexclude:[](empty list)
Auto-discovery behavior:
- When
enabled = true, scansservers_dirfor subdirectories - Each discovered directory becomes a server (unless excluded)
- Uses
default_startup_scriptfor discovered servers (unless overridden in[[servers]]) - Supports glob patterns for exclusions:
*.backup,test-*,*-old, etc.
Disable discovery when you want strict control over which servers run:
[discovery]
enabled = false
With discovery disabled, only servers explicitly listed in [[servers]] are
started.
[status] - Server Status Response
[status]
version_name = "1.21.1" # Minecraft version string
protocol = 763 # Protocol version number
description = "A Minecraft Server" # Plain text or JSON string
favicon = ".local/share/srv/server-icon.png" # Path to 64x64 PNG icon
Optional section. Customize the server status information returned to Minecraft clients when they ping the server (shown in the multiplayer server list).
Defaults:
version_name:"1.21.1"protocol:763description: Uses embedded default fromserver_description.jsonfavicon: Uses embedded default 64x64 PNG icon
Fields:
version_name: Version string displayed to clients (optional)protocol: Minecraft protocol version number (optional, must be positive)description: Server description/MOTD - supports plain text or JSON format:{"text": "My Server", "color": "gold", "bold": true}favicon: Path to PNG icon file, relative to home directory (optional)- Recommended: 64x64 pixels
- Automatically Base64-encoded for protocol
- Falls back to embedded icon if file missing
Behavior:
- All fields are optional - omitted fields use embedded defaults
- Invalid or missing files log warnings and fall back gracefully
- Supports both plain text and JSON-formatted descriptions
- Favicon path is relative to home directory (
~)
Complete Example Configuration
[global]
servers_dir = ".local/share/minecraft"
tmux_session = "mc-servers"
default_startup_script = "./start.sh"
buffer_timeout = 120
init_timeout = 45
[ports]
client = 25565
bluemap = 8080
proxy = 25564
bluemap_mod = 8100
[proxy]
path = "Velocity"
startup_command = "./run"
[[servers]]
name = "survival"
path = "survival"
startup_command = "./run-survival.sh"
[[servers]]
name = "creative"
path = "creative"
startup_command = "./run"
[discovery]
enabled = true
exclude = ["*.backup", "template-*", "test-*"]
[status]
version_name = "1.21.1"
protocol = 763
description = "{\"text\": \"My Awesome Server\", \"color\": \"gold\"}"
favicon = ".local/share/minecraft/server-icon.png"
Minimal Configuration Examples
Change only the servers directory:
[global]
servers_dir = "minecraft/servers"
Define explicit proxy (disable regex matching):
[proxy]
path = "Velocity"
startup_command = "./run"
Exclude backup servers from auto-discovery:
[discovery]
exclude = ["*.backup", "*-old"]
Disable auto-discovery entirely:
[discovery]
enabled = false
[[servers]]
name = "production"
path = "production"
startup_command = "./run"
Customize server status response:
[status]
version_name = "1.21.1"
description = "Welcome to my server!"
favicon = ".local/share/srv/icon.png"
Usage
- Add new servers:
- Create directory under
~/.local/share/srv/ - Place a
runscript (executable) that starts your Vanilla/Fabric/Paper/Spigot/whatever server
- Create directory under
- The next socket activation (first client connect) will spin up all servers.
Monitoring & logs:
journalctl --user -u fabric-servers-wrapper.service -f- tmux session:
tmux -S $XDG_RUNTIME_DIR/fabric-servers.sock ls - Attach to a server pane:
tmux -S $XDG_RUNTIME_DIR/fabric-servers.sock attach -t fabric-servers:<window-name>
Copyright
This project is licensed under the Apache License, Version 2.0. See the LICENSE file for more details.
See also
- systemd.unit(5)
- systemd.service(5)
- systemd.socket(5)
- sd_listen_fds(3)
- Lennart Poettering’s blog post
- lazymc
- Velocity proxy
- ViaVersion for protocol compatibility