A daemon for launching and managing a network of Minecraft (Fabric) servers in separate tmux windows behind a single Velocity proxy. Includes BlueMap support.
  • Rust 89.5%
  • Shell 10.5%
Find a file
Lucca Pellegrini 3bc03bde94
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
refactor(socket): replace bool and HashMap with SocketMode enum and
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.
2025-11-12 23:05:22 -03:00
.github/workflows fix(ci): change docs workflow trigger from tags to master branch 2025-11-10 18:31:12 -03:00
src refactor(socket): replace bool and HashMap with SocketMode enum and 2025-11-12 23:05:22 -03:00
.gitignore initial commit 2025-06-23 14:59:11 -03:00
AGENTS.md docs: clarify version bump procedure and importance of build 2025-11-10 23:25:02 -03:00
Cargo.lock chore: bump version to 0.4.0 2025-11-12 15:00:45 -03:00
Cargo.toml chore: bump version to 0.4.0 2025-11-12 15:00:45 -03:00
CHANGELOG.md chore: bump version to 0.4.0 2025-11-12 15:00:45 -03:00
config.toml.example feat(config): add configurable server status response fields 2025-11-12 13:55:06 -03:00
fabric-servers-wrapper-bluemap.socket add second socket to statically serve BlueMap when server is not running 2025-07-12 04:26:08 -03:00
fabric-servers-wrapper.service add the systemd units to the repo 2025-06-23 16:37:01 -03:00
fabric-servers-wrapper.socket add the systemd units to the repo 2025-06-23 16:37:01 -03:00
LICENSE add LICENSE and draft of README.md 2025-06-26 00:16:22 -03:00
README.md docs: update README for standalone mode operation 2025-11-12 14:57:17 -03:00
STRUCTURED_LOGGING.md refactor(observability): improve log viewer color scheme clarity 2025-11-10 19:50:05 -03:00
view-structured-logs.sh fix(bluemap): include request context in HTTP response log events 2025-11-11 14:22:41 -03:00

fabric-servers-tmux-wrapper

CI Release Latest Release License Rust

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 + .service units
    • Standalone mode: Creates own listeners, ideal for containers or non-systemd environments
    • Mode automatically detected based on environment
  • 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
  • nix crate 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)

  1. Clone & build the binary:

    git clone https://github.com/lucca-pellegrini/minecraft-servers-tmux-wrapper.git
    cd minecraft-servers-tmux-wrapper
    cargo build --release
    
  2. 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
    
  3. Create your server directories and startup scripts:

    ~/.local/share/srv/
    ├── lobby/
    │   └── run       # +x script to launch your server
    ├── world1/
    │   └── run
    └── world2/
        └── run
    
  4. 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
      
  5. 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)

  1. Clone & build the binary:
    git clone https://github.com/lucca-pellegrini/minecraft-servers-tmux-wrapper.git
    cd minecraft-servers-tmux-wrapper
    cargo build --release
    
  2. 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
    
  3. Create your server directories (same as systemd mode):
    ~/.local/share/srv/
    ├── lobby/
    │   └── run
    ├── world1/
    │   └── run
    └── world2/
        └── run
    
  4. Run directly:
    ~/.local/bin/minecraft-servers-tmux-wrapper
    
    Or with custom ports via config.toml (see Configuration section).

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

  1. 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.)
  2. 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
  3. Startup & tmux Session

    • Reads $XDG_RUNTIME_DIR (defaults to /tmp) and creates a tmux socket fabric-servers.sock.
    • Kills any existing fabric-servers session on that socket.
    • If [proxy] is configured, starts the proxy server first.
    • Scans configured servers_dir for 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).
  4. Proxy Health-check

    • Waits up to buffer_timeout seconds (default: 90) for the proxy to accept connections on 127.0.0.1:<proxy_port> (default: 25564).
    • If the proxy fails to start, the daemon exits with error.
  5. 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.
  6. 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_mod port).
    • 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.
  7. Cleanup on Proxy Exit

    • Continuously polls the proxy every 5 seconds.
    • On proxy failure, spawns shutdown tasks for each tmuxmanaged server:
      • Grace period: 10 seconds
      • Retries SIGTERM, SIGINT, SIGHUP (3× each, 10 seconds in between)
      • Finally SIGKILL if still alive
  8. 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: 90 seconds
  • init_timeout: 60 seconds

[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: 25565
  • bluemap: 31898
  • proxy: 25564
  • bluemap_mod: 8100

Notes:

  • In systemd mode: Ensure systemd socket units match the client and bluemap ports
  • In standalone mode: Daemon creates listeners on client and bluemap ports 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 within servers_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 within servers_dir (required)
  • startup_command: Custom startup command (required)
  • working_dir: Optional working directory override

Use cases:

  • Servers with different startup scripts (e.g., start.sh vs ./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: true
  • exclude: [] (empty list)

Auto-discovery behavior:

  • When enabled = true, scans servers_dir for subdirectories
  • Each discovered directory becomes a server (unless excluded)
  • Uses default_startup_script for 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: 763
  • description: Uses embedded default from server_description.json
  • favicon: 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:
    1. Create directory under ~/.local/share/srv/
    2. Place a run script (executable) that starts your Vanilla/Fabric/Paper/Spigot/whatever server
  • 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>
    

This project is licensed under the Apache License, Version 2.0. See the LICENSE file for more details.


See also