# minecraft-server

minecraft-server is a wrapper for the Java, Fabric, and Bedrock Minecraft servers that adds
proper systemd support. The core functionality includes proper signal handling for
`SIGINT` and `SIGTERM`, systemd user services, support for the XDG Base Directory
Specification, and a Fedora Copr package for seamless upgrades.

The repository also ships matching Kotlin/JVM wrapper jars for the version-locked
Minecraft Java server, Minecraft Fabric server, Geyser Standalone, and
MCXboxBroadcast Standalone. Each wrapper downloads and verifies its application
jar on first use, then uses Java's Foreign Function and Memory API and a small
Rust library to replace itself with the
final Java process.

[![Latest Release](https://gitlab.com/kaidelorenzo/minecraft-server-rs/-/badges/release.svg)](https://gitlab.com/kaidelorenzo/minecraft-server-rs/-/releases)
[![Copr build status](https://copr.fedorainfracloud.org/coprs/kaidelorenzo/minecraft-server/package/minecraft-server/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/kaidelorenzo/minecraft-server/package/minecraft-server/)

Mozilla Public License Version 2.0

[TOC]

## Release Schedule

Each release is tied to a specific version of the Minecraft server binaries provided by Mojang.

New releases are built and deployed on Copr for every server binary update released by Mojang.
Copr automatically deletes old builds so the latest version is the only one that's
guaranteed to be available.

Builds are made available for/build against the latest [Fedora](https://fedoraproject.org/) and
[EPEL](https://docs.fedoraproject.org/en-US/epel/) versions on the x86_64 and AArch64 platforms.

## Features

-   Standard input and output are passed through to the Minecraft server executables
    (works almost identically to the Mojang released CLI)
-   Proper signal handling for `SIGTERM` and `SIGINT` (including ctrl+c) for safe server shutdown
-   systemd user services
-   Support for the XDG Base Directory Specification
-   Copr package for seamless upgrades built against latest Fedora and EPEL
-   Arguments passed to the minecraft-server executables are passed on to the Minecraft server binaries

## Installation

1.  ```shell
    dnf copr enable kaidelorenzo/minecraft-server
    ```

2.  ```shell
    dnf install minecraft-server-java
    ```

    or

    ```shell
    dnf install minecraft-server-fabric
    ```

    or

    ```shell
    dnf install minecraft-server-bedrock
    ```

    The standalone launchers can also be installed directly:

    ```shell
    dnf install minecraft-java-server minecraft-fabric-server geyser-standalone mcxboxbroadcast-standalone
    ```

    RPM descriptions record the exact upstream application version locked by
    each wrapper. The same information is exposed through `wrapped(...)`
    virtual capabilities:

    ```shell
    rpm -qi geyser-standalone
    rpm -q --provides geyser-standalone | grep 'wrapped('
    rpm -q --provides minecraft-server-fabric | grep 'wrapped('
    ```

### Uninstallation

1.  ```shell
    dnf remove minecraft-server-java
    ```

    or

    ```shell
    dnf remove minecraft-server-fabric
    ```

    or

    ```shell
    dnf remove minecraft-server-bedrock
    ```

2.  ```shell
    dnf copr remove kaidelorenzo/minecraft-server
    ```

3.  Delete the `net.minecraft.JavaServer`, `net.fabricmc.FabricServer`, and
    `net.minecraft.BedrockServer` directories
    from the `$XDG_DATA_HOME` and `$XDG_CONFIG_HOME` directories (see
    [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html))
4.  Delete any manually created configuration files

## Simple Usage

### Start the server

#### from the terminal shell

```shell
java <JVM options> -jar /usr/lib/java/minecraft-server-java.jar <Minecraft options>
```

or

```shell
java <JVM options> -jar /usr/lib/java/minecraft-server-fabric.jar <Minecraft options>
```

or

```shell
minecraft-server-bedrock <Bedrock options>
```

The low-level version-locked wrapper jars can be run directly with:

```shell
java <JVM options> -jar /usr/lib/java/minecraft-java-server-wrapper.jar --nogui
java <JVM options> -jar /usr/lib/java/minecraft-fabric-server-wrapper.jar --nogui
java <JVM options> -jar /usr/lib/java/geyser-standalone-wrapper.jar <Geyser options>
java <JVM options> -jar /usr/lib/java/mcxboxbroadcast-standalone-wrapper.jar <MCXboxBroadcast options>
```

The wrapper jars use standard Java command-line syntax: JVM options go before
`-jar`, and application arguments go after the wrapper jar. Both sets of
arguments are preserved when the version-locked application jar is launched:

```text
java <JVM options> -jar wrapper.jar <args>
  -> java <JVM options> -jar <version-locked.jar> <args>
```

The Bedrock wrapper passes all arguments directly to the version-locked server
executable. On first launch it downloads the exact locked archive with an
application-specific HTTP user agent, verifies its SHA-256 checksum, and stages
the complete distribution before publishing the executable. Distribution
updates preserve `server.properties`, `allowlist.json`, `permissions.json`, and
`packetlimitconfig.json` while refreshing upstream-owned runtime assets.

#### with systemd

```shell
systemctl --user enable --now minecraft-server-java
```

or

```shell
systemctl --user enable --now minecraft-server-fabric
```

or

```shell
systemctl --user enable --now minecraft-server-bedrock
```

Geyser and MCXboxBroadcast do not install systemd units because their upstream
standalone applications do not provide the same verified shutdown behavior as
the Minecraft supervisors. Basic, unsupported reference units are available in
`example systemd unit files/` for users who accept that limitation.

To enable auto launch on system start for the currently logged in user:

```shell
loginctl enable-linger
```

<https://wiki.archlinux.org/title/Systemd/User#Automatic_start-up_of_systemd_user_instances>

To view output:

```shell
journalctl --user-unit minecraft-server-java
```

or

```shell
journalctl --user-unit minecraft-server-fabric
```

or

```shell
journalctl --user-unit minecraft-server-bedrock
```

Use the `--follow` option to continually view messages

To send commands:

-   One at a time:

    ```shell
    echo "help" > $XDG_RUNTIME_DIR/minecraft-server-java.stdin
    ```

    or

    ```shell
    echo "help" > $XDG_RUNTIME_DIR/minecraft-server-fabric.stdin
    ```

    or

    ```shell
    echo "help" > $XDG_RUNTIME_DIR/minecraft-server-bedrock.stdin
    ```

-   One after the other (with history via up/down arrows) while viewing the server output:

    ```shell
    journalctl --user-unit minecraft-server-java --follow & rlwrap cat > $XDG_RUNTIME_DIR/minecraft-server-java.stdin
    help
    help list
    list
    <ctrl+c/d>/^C/D
    # exiting the shell should stop the journalctl job (exit)
    # to manually stop it do the following
    # find the id of the background journalctl job (it's 1 in this case)
    jobs 
    # connect to it
    fg 1
    <ctrl+c>/^C
    ```

    or

    ```shell
    journalctl --user-unit minecraft-server-fabric --follow & rlwrap cat > $XDG_RUNTIME_DIR/minecraft-server-fabric.stdin
    help
    help list
    list
    <ctrl+c/d>/^C/D
    # exiting the shell should stop the journalctl job (exit)
    # to manually stop it do the following
    # find the id of the background journalctl job (it's 1 in this case)
    jobs 
    # connect to it
    fg 1
    <ctrl+c>/^C
    ```

    or

    ```shell
    journalctl --user-unit minecraft-server-bedrock --follow & rlwrap cat > $XDG_RUNTIME_DIR/minecraft-server-bedrock.stdin
    help
    help list
    list
    <ctrl+c/d>/^C/D
    # exiting the shell should stop the journalctl job (exit)
    # to manually stop it do the following
    # find the id of the background journalctl job (it's 1 in this case)
    jobs 
    # connect to it
    fg 1
    <ctrl+c>/^C
    ```

## Configuration

### Locations

Minecraft Java configuration: `$XDG_CONFIG_HOME/net.minecraft.JavaServer/minecraft`

Minecraft Fabric configuration: `$XDG_CONFIG_HOME/net.fabricmc.FabricServer/minecraft`

Minecraft Bedrock configuration: `$XDG_CONFIG_HOME/net.minecraft.BedrockServer/minecraft`

Geyser and MCXboxBroadcast read their configuration relative to the working
directory from which their wrapper jar is launched.

Minecraft Mojang binaries: `$XDG_DATA_HOME/net.minecraft.JavaServer`
and `$XDG_DATA_HOME/net.minecraft.BedrockServer`

Fabric binaries: `$XDG_DATA_HOME/net.fabricmc.FabricServer`

Geyser binaries: `$XDG_DATA_HOME/org.geysermc.GeyserStandalone`

MCXboxBroadcast binaries: `$XDG_DATA_HOME/com.rtm516.MCXboxBroadcastStandalone`

systemd user service standard input: `$XDG_RUNTIME_DIR/minecraft-server-java.stdin`
and `$XDG_RUNTIME_DIR/minecraft-server-fabric.stdin`
and `$XDG_RUNTIME_DIR/minecraft-server-bedrock.stdin`

### Environment Variables

`$MINECRAFT_SERVER_WORKING_DIRECTORY`

>>>
Sets the working directory used by the Minecraft server executable. Relative paths are resolved
from the wrapper's current directory. When unset, each package uses its XDG configuration
directory. Set it to `"."` to use the directory from which the wrapper was launched.
>>>

### systemd Service Configuration

To override the installed service use this command:

```shell
systemctl --user edit --full minecraft-server-java
```

or

```shell
systemctl --user edit --full minecraft-server-fabric
```

or

```shell
systemctl --user edit --full minecraft-server-bedrock
```

### Managing The systemd Service

See [with systemd](#with-systemd)

## Version Interpretation

Given the version format `x.y.z` used for the project. x is reserved for major changes in
the future. y increments for any changes made other than Minecraft version changes. z increments
whenever there is a Minecraft version change.

## Issue Reporting

Please [create an issue](https://gitlab.com/kaidelorenzo/minecraft-server-rs/-/issues/new). They
will be looked at and quickly fixed.

## Contribution

Contributions are welcome. However please run large changes through review by reaching out
first to make sure that it aligns with the vision of the project.

Only [signed commits](https://docs.gitlab.com/ee/user/project/repository/signed_commits/)
are allowed. Pull requests with unsigned commits will not be merged.

The project is written and maintained by me [Kai DeLorenzo](https://gitlab.com/kaidelorenzo),
so don't be intimidated and if there are things that you imagine would be better if different
then give a holler.

### Building

The local test entry points are:

```shell
jar-launchers/gradlew --gradle-user-home .gradle-user-home --project-dir jar-launchers test
cargo test --workspace
cargo test --package minecraft-server --test cli -- --ignored
```

The last command downloads and launches the locked upstream Minecraft Bedrock server and therefore
requires network access. The first two are the default test suites and do not download upstream
server binaries. Gradle stores its distribution and dependency cache in the ignored
`.gradle-user-home/` directory and may resolve those dependencies on the first run.

See [CI Design](docs/ci-design.md) for the pipeline architecture, cache strategy,
and related GitLab repository configuration.
See [Dependency Maintenance](docs/dependency-management.md) for the stable-version
policy and the commands used to update Rust, Kotlin, Gradle, and the Gradle Wrapper.

>>>
**NOTE**: It's recomended to do development and building in a container like
[distrobox](https://distrobox.it/) to isolate development from the host system and prevent
the need to clear clutter later. If only building test RPMs then using
[Mock](https://rpm-software-management.github.io/mock/) is a great way to isolate the
installation of build dependenceies. Copr has instructions for
[replicating builds locally](https://docs.pagure.org/copr.copr/user_documentation/reproducing_builds.html).
>>>

1.  Install development dependencies

    ```shell
    dnf install cargo java-latest-openjdk-headless openssl-devel rust-src rustfmt
    ```

2.  Clone the repo and use [cargo](https://doc.rust-lang.org/cargo/getting-started/first-steps.html)
    for development. There are included VSCodium [Run and Debug](.vscode/launch.json) configurations
    for use with the[rust-analyzer](https://open-vsx.org/extension/rust-lang/rust-analyzer) and
    [CodeLLDB](https://open-vsx.org/extension/vadimcn/vscode-lldb) extensions.
3.  Building a test RPM with Fedora sources and tito

    The RPM currently resolves the pinned Gradle distribution and Kotlin build
    dependencies online. Enable **Internet access during builds** for the Copr
    project or build, until these dependencies are provisioned for an offline
    build.

    1.  Install tito

        ```shell
        dnf install tito
        ```

    2.  Install the static RPM build dependencies:

        ```shell
        sudo dnf builddep minecraft-server.spec
        ```

    3.  Generate the dynamic build-requirements RPM:

        ```shell
        tito build --rpm
        ```

        or

        ```shell
        tito build --test --rpm
        ```

        The command exits after generating
        `/tmp/tito/minecraft-server-{version}-{release}.buildreqs.nosrc.rpm`
        when its dynamic build dependencies are not installed.
    4.  Install the dynamic build dependencies from that file:

        ```shell
        sudo dnf builddep /tmp/tito/minecraft-server-{version}-{release}.buildreqs.nosrc.rpm
        ```

    5.  Run the build command again to test the Fedora source build:

        ```shell
        tito build --rpm
        ```

        or

        ```shell
        tito build --test --rpm
        ```

#### Cleaning Up After development

These are instructions to delete/remove/uninstall files created during the development process

1.  Uninstall the Rust development dependencies with `dnf remove` installed during step 1
    of [Building](#building)
2.  Remove development dependencies installed via `dnf builddep`
    -   list your dnf history to find the instance when you installed the build dependencies

        ```shell
        dnf history list
        ```

    -   undo that installation to remove the build dependencies

        ```shell
        dnf history undo <transaction id>
        ```

3.  Delete the `XDG_CONFIG/DATA_HOME` directories used when running the programming

    >>>
    **NOTE**: Tests use temporary directories for the XDG directories so they don't need
    to be manually cleared
    >>>

4.  Delete the repo folder cloned in step 2 of [Building](#building)
5.  To remove programs/data installed from a development build of an RPM or from
    `cargo install` follow some other guide

### Release Process

0.  A recurring pipeline job checks for upstream server releases and creates a GitLab Release.
    The release webhook then triggers its Copr mirror build.
    This process is for releasing other changes.
1.  Review [Dependency Maintenance](docs/dependency-management.md) and update
    selected dependencies that satisfy its stability and Fedora/EPEL eligibility
    policy. The upstream dependency report is not an instruction to upgrade every
    listed crate.
2.  On the release candidate commit run

    ```shell
    tito build --test --rpm
    ```

3.  Version bump (they should all be the same)

    `.tito/packages/minecraft-server`

    `minecraft-server.spec`

    `minecraft-server/Cargo.toml`

    `Cargo.lock`
4.  Commit and create a pull request
5.  Wait for the pipeline to pass
6.  Merge the PR
7.  Create a new release with the tag `x.y.z` (it should match the version specified previously)
8.  Wait for the release-triggered Copr build to succeed
9.  COPR's build targets and project description are reconciled from the repository by CI

### General Architecture

As described earlier the purpose of the software is to wrap the Minecraft server executables with
code that will properly handle signals while maintaining as similar as possible an API to the
orginal executable. This makes it easy to write a systemd service to run a Minecraft service because
systemd uses signals to manage processes.

The `minecraft-server-java` and `minecraft-server-fabric` packages provide
top-level runnable jars for systemd-aware process handling. Each jar captures the
JVM and server arguments, then replaces its short-lived JVM with the packaged Rust
supervisor. The supervisors depend on the download-only `minecraft-java-server`
and `minecraft-fabric-server` packages, which own downloading and checksum
verification. The complete flow is:

```text
java <JVM options> -jar minecraft-server-{java,fabric}.jar <args>
  -> Rust systemd supervisor
  -> java <JVM options> -jar <download-wrapper.jar> <args>
  -> java <JVM options> -jar <version-locked.jar> <args>
```

Only the supervisor packages own the corresponding service and FIFO socket
units; the download-only packages do not install competing systemd units. The
scheduled version updater regenerates the RPM version metadata whenever a locked
Minecraft, Fabric, Geyser, or MCXboxBroadcast version changes.

All native wrapper jars share the Rust exec library supplied by the
`minecraft-server-wrapper-native` package. RPM dependencies install it
in the platform library directory, and the wrappers load it through Java's
standard `java.library.path` lookup. Source tests point that lookup at the locally
built library.

The Minecraft executable is run as a child process. That is straightforward enough. The hardest part
and I think the only section of code that requires explanation is the `SelfManage` trait. Everything
else is for downloading files, reading setting environment variables, loading settings, CLI
arguments, and launching the child process correctly.

The main process needs to be doing three things at the same tine

1.  Listening for commands sent to standard input and forwarding them to the Minecraft child process
2.  Listening for interprocess signals like `SIGTERM` and `SIGINT` then sending the appropriate
    "stop" message to the Minecraft child process
3.  Listening for when the child Minecraft executable process exits at which point the main process
    should also exit.

The crate mio let's us perform all of these in a non-blocking way. The last piece is correctly
handle errors and cleanup. This part probably has some bugs.

Mapping mio event handling to the main activities listed above

1.  We register a listener to stdin via mio

    ```rust
    poll.registry().register(
        &mut mio::unix::SourceFd(&parent_stdin.as_raw_fd()),
        STDIN_TOKEN,
        mio::Interest::READABLE,
    )?;
    ```

2.  Adding handlers for `SIGTERM` and `SIGINT`
3.  Handling the `SIGCHLD` signal should inform us when the child process exits

## Known Issues

None

## TO-DO

### New Rust Features

None

### Not actively being worked on

-   [ ] Support other platforms
-   [ ] Create a const function to generate the server.jar download URL at compile time
-   [ ] Migrate from [tempfile](https://packages.fedoraproject.org/pkgs/rust-tempfile/) to
    [assert_fs](https://packages.fedoraproject.org/pkgs/rust-assert_fs/). The problem is that there
    currently are not builds of assert_fs for EPEL.
-   [ ] Add more tests
-   [ ] Add additional variables to the spec file to prevent typos when making changes
-   [ ] Add better error handling (currently most errors just result in the programming exiting)
-   [ ] Improve the logging messages
-   [ ] Improve the manuals. Standardize the syntax used and flesh out the options and details
-   [ ] Add examples of using the environment variables
-   [ ] Support additional languages
-   [ ] Generate help documentation from the Minecraft binaries
-   [ ] Add an assertion of the expected contents of the bedrock zip file so that Mojang changes can
    be detected
-   [ ] The real way that the auto release process should work is that is should create a branch off
    of the tag of the last release commit the changes to that branch and create a new release from
    that branch. If we don't use that process then it's very easy for changes merged into main to
    get included with an auto release which is probably not what we want.

## FAQ

### What about the PID file `--pidFile`?

>>>
The `$MINECRAFT_SERVER_WORKING_DIRECTORY` environment variable and the default Minecraft
configuration directory affect relative paths passed to the `--pidFile` option for the
Minecraft Java server.

However note that the PID saved to the file with this option will be for the Java process
not for the Rust wrapper process. Depending on the goals of using the `--pidFile` option the
PID of the Rust process might be more useful. There is no built-in option to get that PID.
Use the operating system or other software to get the PID of the Rust process.
>>>

### Are there packages for other other operating systems?

>>>
Not currently. If built from source there should be no issue running on other Linux
based operating systems. Windows and macOS likely don't work unfortunately. There is a
chance the Java version will work.
>>>

### Does this provide auto updates to the latest Minecraft version?

>>>
No. Enable auto updates via dnf/rpm-ostree.

<https://dnf.readthedocs.io/en/latest/automatic.html>

<https://docs.fedoraproject.org/en-US/iot/applying-updates-UG/#_automatic_updates>

Or use dnf offline-upgrade. There isn't a package to use but here are some
[example system systemd unit files](example systemd unit files)
>>>

### Why doesn't `minecraft-server-bedrock` work on AArch64!?

>>>
Mojang only makes x86_64 builds for the Bedrock server. QEMU or Box64 might work but likely
very slowly

<https://www.qemu.org/docs/master/index.html>

<https://packages.fedoraproject.org/pkgs/qemu/qemu-user/>

<https://github.com/ptitSeb/box64>

<https://packages.fedoraproject.org/pkgs/box64/box64/>
>>>

## Inspiration/References

<https://github.com/sbstp/minecraftd>

<https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs>

## Minecraft Server Setup Guide

0.  Assumes a prexisting server with Fedora Linux or AlmaLinux and a user account that
    can elevate to root privileges.
1.  Enable the Copr repo (see [Installation](#installation))
2.  Install the Minecraft Bedrock server and/or the Minecraft Java server (see [Installation]   (#installation))
3.  Enable and start the service (see [Installation](#installation))
4.  (Java only) approve the EULA at `~/net.minecraft.JavaServer/minecraft.eula.txt`
    -   Start the service again
5.  Enable the user service to start on boot

    ```shell
    loginctl enable-linger
    ```

6.  Open the appropriate ports with these firewall-cmd commands (assumes that firewalld is the
    firewall for the server)
    -   for Java

        ```shell
        # allow incomming tcp traffic on port 25565
        firewall-cmd --zone=public --add-port=25565/tcp
        # make it permanent (survive reboots)
        firewall-cmd --zone=public --permanent --add-port=25565/tcp
        # allow incomming udp traffic on port 25565
        firewall-cmd --zone=public --add-port=25565/udp
        # make it permanent (survive reboots)
        firewall-cmd --zone=public --permanent --add-port=25565/udp
        ```

    -   for Bedrock

        ```shell
        # allow incomming udp ipv4 traffic on port 19132
        firewall-cmd --zone=public --add-rich-rule='rule family=ipv4 port port=19132 protocol=udp accept'
        # make it permanent (survive reboots)
        firewall-cmd --zone=public --permanent --add-rich-rule='rule family=ipv4 port port=19132 protocol=udp accept'
        # allow incomming udp ipv6 traffic on port 19133
        firewall-cmd --zone=public --add-rich-rule='rule family=ipv6 port port=19133 protocol=udp accept'
        # make it permanent (survive reboots)
        firewall-cmd --zone=public --permanent --add-rich-rule='rule family=ipv6 port port=19133 protocol=udp accept'
        ```

7.  Consider enabling zram for low ram computers if it doesn't come pre-installed:

    <https://github.com/systemd/zram-generator>
8.  Enable [automatic updates](#does-this-provide-auto-updates-to-the-latest-minecraft-version)
9.  As an alternative to rebooting for updates just restart the service if
    it was updated. So, after running dnf upgrade run:

    ```shell
    systemctl --user daemon-reload
    systemctl --user reload-or-restart --marked
    ```

    <!-- markdownlint-disable MD030 -->
10. Test by pointing a Minecraft client to the public IP of the server. It should connect.
11. Setup SRV record to communicate the port for the client to connect to:

    <https://minecraft.wiki/w/Tutorials/Setting_up_a_server#The_SRV_record>
12. (Java only) Setup [Geyser](https://geysermc.org/) so that Bedrock players can join. An
    [unsupported example user service](example systemd unit files/geyser.service) is available
    for basic process supervision, but it does not guarantee graceful shutdown.
13. (Bedrock and Geyser only) Configure
    [MCXboxBroadcast Standalone](https://github.com/MCXboxBroadcast/Broadcaster)
    to let console friends join the server. The `mcxboxbroadcast-standalone` package
    provides the version-locked wrapper jar. An
    [unsupported example user service](example systemd unit files/mcxboxbroadcast-standalone.service)
    is available for basic process supervision, but it does not guarantee graceful shutdown.
14. Perform any desired configuration/modification of the server.
    <!-- markdownlint-enable MD030 -->
