# minecraft-server

minecraft-server is a wrapper for the Java 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.

[![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-bedrock
    ```

### Uninstallation

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

    or

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

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

3.  Delete the `net.minecraft.JavaServer` 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
minecraft-server-java
```

or

```shell
minecraft-server-bedrock
```

#### with systemd

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

or

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

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-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-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-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

Java JVM arguments: `$XDG_CONFIG_HOME/net.minecraft.JavaServer/jvm-settings.txt`

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

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

Additional arguments: `$XDG_CONFIG_HOME/net.minecraft.JavaServer/additional-arguments.txt`

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

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

### Config Files

`jvm-settings.txt`

>>>
Enter a string of arguments that would go after `java` and before `-jar`
(e.g. `-Xmx4G -Xms1G` which will be equivalent to `java -Xmx4G -Xms1G -jar ...`)
>>>

`additional-arguments.txt`

>>>
Enter a string of arguments that would go after `server.jar` these will be appended to any
arguments passed directly to the executable (e.g. `--nogui` which will be equivalent to
`java -Xmx4G -Xms1G -jar <server.jar> ... --nogui`)
>>>

### Environment Variables

`$JAVA_HOME`

>>>
Should have standard behavior:
<https://docs.oracle.com/en/cloud/saas/enterprise-performance-management-common/diepm/epm_set_java_home_104x6dd63633_106x6dd6441c.html#GUID-7D734C69-2DE8-4E93-A3C8-9C3F6AD12D1B>
>>>

`$CURRENT_DIR`

>>>
Set to a relative or absolute path that will be used to set the current directory context for
the Minecraft server executable. This specifies the Minecraft configuration directory.
To emulate the default behavior of the Mojang Minecraft executables set to `"."` To use the
current directory as the Minecraft configuration directory.
>>>

`$JVM_SETTINGS_FILEPATH`

>>>
Specifies an alternative location to read JVM settings from. This is a path to the actual file
(e.g. `"~/Downloads/my-jvm-settings.txt"`).
>>>

### 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-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

>>>
**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
    1.  Install tito

        ```shell
        dnf install tito
        ```

    2.  Generate buildreqs RPM:

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

        or

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

        (this will fail but it will generate this file
        `/tmp/tito/minecraft-server-{version}-{release}.buildreqs.nosrc.rpm`)
    3.  With that file run this to install the build dependencies:

        `dnf builddep /tmp/tito/minecraft-server-{version}-{release}.buildreqs.nosrc.rpm`
    4.  Run the build command again to test the Fedora sources 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.  There is a recurring pipeline job that will check for new minecraft releases and create a new release.
    This process is for releasing other changes.
1.  Bump dependency versions (make sure that the dependencies are available in the [Fedora and EPEL repositories](https://packages.fedoraproject.org))

    ```shell
    cargo upgrade --incompatible
    ```

    (requires `cargo-edit` install with: `cargo install cargo-edit`)
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`

    `Cargo.toml`
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` (should match the version specified previously)
8.  Wait for the Copr build to succeed
9.  If the README has changes consider adding them to COPR

### Warning On `Cargo.toml` Changes

When making changes to the `Cargo.toml` dependencies section make sure that the versions are
available in for the latest EPEL and Fedora

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

The spec file might also require changes to the license section

<https://docs.fedoraproject.org/en-US/packaging-guidelines/Rust/#_license_tags>

### 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 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 an environment variable to specify that the Minecraft binaries should be re-downloaded
    on launch
-   [ ] 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 $CURRENT_DIR environment variable (and the default settings for the 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 goes 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. See an
    [example user systemd service](example systemd unit files/geyser.service) for Geyser.
13. (Bedrock and Geyser only) Setup something like
    [MCXboxBroacast](https://github.com/rtm516/MCXboxBroadcast)
    or [FriendConnect](https://github.com/Androecia/FriendConnect) to enable console players to join.
    FriendConnect seems to be harder to get working (though there is a new Rust version in development).
    See an [example user systemd service](example systemd unit files/friendconnect.service)
    for MCXboxBroadcast.
14. Perform any desired configuration/modification of the server.
    <!-- markdownlint-enable MD030 -->
