diff --git a/README.md b/README.md index e979da5..a0d18d9 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,32 @@ # Docker Minecraft FTB -A Docker image for running [FTB](https://www.feed-the-beast.com/) Minecraft servers. +A Docker image developed to launch Minecraft [FTB](https://www.feed-the-beast.com/) Modpack servers as safely, quickly and easily as possible. -### Example +## Features + +- Supports all "Feed The Beast" (FTB) [modpacks](https://www.feed-the-beast.com/modpacks?sort=featured) +- Configurable modpack versions + - Allows pinning to a specific modpack version (see `FTB_MODPACK_VERSION_ID`) + - Installs the latest version of the modpack, if no specific version is configured +- Supports automatic modpack upgrades on container start (see `AUTO_UPDATE`) +- Supports unattended installation (see `ACCEPT_MOJANG_EULA`) +- Supports configuration of user-defined JVM arguments as part of the container configuration (see `USER_JVM_ARGS`) +- Drops `root` privileges after setting up the container and runs the server as an unprivileged user + +## Example ```bash docker run -itd --name minecraft-ftb \ -v "/docker_data/minecraft:/var/lib/minecraft" \ -e "FTB_MODPACK_ID=126" \ -e "ACCEPT_MOJANG_EULA=1" \ + -e "USER_JVM_ARGS=-Xms1G -Xmx4G" \ -p "25565:25565" \ --stop-timeout=60 \ ghcr.io/flobernd/minecraft-ftb ``` -> [!WARNING] -> The server process does not shut down gracefully, if no TTY is present. Please pass the `--tty`/`-t` switch (Docker) or use `tty: true` (Docker Compose). - -> [!WARNING] -> It is strongly recommended to set the `stop-timeout` / `stop_grace_period` to at least `60` seconds to avoid data loss when stopping the container. - -> [!NOTE] -> It is recommended to pass the `--interactive`/`-i` switch (Docker) or use `stdin_open: true` (Docker Compose) to be able to use the server console after attaching to the container. - -### Docker Compose Example +## Docker Compose Example ```yaml services: @@ -35,24 +38,36 @@ services: stdin_open: true stop_grace_period: 1m environment: - - "FTB_MODPACK_ID=126" - - "ACCEPT_MOJANG_EULA=1" + FTB_MODPACK_ID: 126 + ACCEPT_MOJANG_EULA: 1 + USER_JVM_ARGS: "-Xms1G -Xmx4G" volumes: - ./minecraft:/var/lib/minecraft:rw ports: - 25565:25565 ``` -### Environment +## Notes -#### `FTB_MODPACK_ID` +> [!WARNING] +> The server process does not shut down gracefully, if no TTY is present. Please pass the `--tty`/`-t` switch (Docker) or use `tty: true` (Docker Compose). + +> [!WARNING] +> It is strongly recommended to set the `stop-timeout` (Docker) / `stop_grace_period` (Docker Compose) to at least `60` seconds to avoid data loss when stopping the container. + +> [!NOTE] +> It is recommended to pass the `--interactive`/`-i` switch (Docker) or use `stdin_open: true` (Docker Compose) to be able to use the server console after attaching to the container. + +## Environment + +### `FTB_MODPACK_ID` The FTB modpack ID (*required*). > [!NOTE] > The modpack ID and the version ID are displayed on the right-hand side of the modpack info page. For example, the [Direwolf20 1.21 modpack](https://www.feed-the-beast.com/modpacks/126-ftb-presents-direwolf20-121) has the ID `126` and the latest version, as of today, is `12599`. -#### `FTB_MODPACK_VERSION_ID` +### `FTB_MODPACK_VERSION_ID` The FTB modpack version ID. @@ -61,7 +76,7 @@ The FTB modpack version ID. Default: Latest version of the configured modpack. -#### `ACCEPT_MOJANG_EULA` +### `ACCEPT_MOJANG_EULA` Set `1` to automatically agree to the [Mojang EULA](https://account.mojang.com/documents/minecraft_eula). @@ -69,7 +84,22 @@ This option enables unattended installation. Otherwise, an interactive session m Default: `0`. -#### `AUTO_UPDATE` +### `USER_JVM_ARGS` + +Optional, user-defined JVM arguments. + +Use the `-Xms` switch to configure the minimum amount of memory used by the JVM. `-Xms1G` sets the minimum amount of memory to 1 GB. The `M` suffix can be used to specify the amount of memory in megabytes instead of gigabytes. + +Use the `-Xmx` switch to configure the maximum amount of memory used by the JVM. `-Xms4G` sets the maximum amount of memory to 4 GB. The `M` suffix can be used to specify the amount of memory in megabytes instead of gigabytes. + +To specify multiple arguments, combine them with spaces: `-Xms1G -Xmx4G`. + +> [!WARNING] +> Using this option causes the `user_jvm_args.txt` file to be overwritten when the container is started. + +Default: *none* + +### `AUTO_UPDATE` Set `1` to automatically update the modpack when the container is started. @@ -77,7 +107,7 @@ If `FTB_MODPACK_VERSION_ID` is set, the configured version number is used, other Default: `0` -#### `FORCE_REINSTALL` +### `FORCE_REINSTALL` Set `1` to force a reinstallation of the modpack when the container is started. diff --git a/minecraft-ftb/data/Dockerfile b/minecraft-ftb/data/Dockerfile index 574c322..b67f5e6 100644 --- a/minecraft-ftb/data/Dockerfile +++ b/minecraft-ftb/data/Dockerfile @@ -4,11 +4,10 @@ FROM debian:bookworm-slim ENV FTB_MODPACK_ID=0 ENV FTB_MODPACK_VERSION_ID=0 - ENV ACCEPT_MOJANG_EULA=0 - -ENV JAVA_MEMORY_MIN= -ENV JAVA_MEMORY_MAX= +ENV USER_JVM_ARGS= +ENV AUTO_UPDATE=0 +ENV FORCE_REINSTALL=0 # Set up runtime dependencies diff --git a/minecraft-ftb/data/docker-entrypoint.sh b/minecraft-ftb/data/docker-entrypoint.sh index c53551e..e6b369f 100755 --- a/minecraft-ftb/data/docker-entrypoint.sh +++ b/minecraft-ftb/data/docker-entrypoint.sh @@ -1,10 +1,20 @@ #!/bin/bash set -o pipefail +set -o nounset set -e +ansi_rst="\e[0m" +ansi_b="\e[94m" +ansi_g="\e[92m" +ansi_r="\e[31m" + echoerr() { - echo "ERROR: $@" 1>&2; + echo -e "${ansi_r}ERROR:${ansi_rst} $@" 1>&2; +} + +echoinfo() { + echo -e "${ansi_b}INFO:${ansi_rst} $@" } check_environment() { @@ -17,7 +27,7 @@ check_environment() { do if [ -z "${!value}" ]; then missing=true - echoerr "Missing mandatory environment variable: '${value}'" + echoerr "Missing mandatory environment variable: '${ansi_b}${value}${ansi_rst}'" fi done @@ -42,7 +52,7 @@ fetch_modpack_info() { response=$(curl --fail --connect-timeout 30 --max-time 30 --no-progress-meter "https://api.modpacks.ch/public/modpack/$1") || exit $? if [ -z "${response}" ] || [ $(echo "${response}" | jq -r '.status') != "success" ]; then - echoerr "Failed to fetch modpack info. Please check if the modpack id [$1] is correct." + echoerr "Failed to fetch modpack info. Please check if the modpack id [${ansi_green}$1${ansi_rst}] is correct." echoerr "$(echo "${response}" | jq -r '.message')" return 1 fi @@ -70,7 +80,7 @@ query_version_info() { local result=$(echo "$1" | jq -r ".versions[] | select(.id == $2)") if [ -z "${result}" ]; then - echoerr "Failed to query version info. Please check if the modpack version id [$2] is correct." + echoerr "Failed to query version info. Please check if the modpack version id [${ansi_green}$2${ansi_rst}] is correct." return 1 fi @@ -108,18 +118,18 @@ patch_start_script() { # We have to make sure the `start.sh` script uses `exec` to launch the server. SIGINT and # other signals would not be forwarded to the Java process otherwise. - regex='^("jre/.+/bin/java" .+ nogui)$' - output="" - success=false + local regex='^("jre/.+/bin/java" .+ nogui)$' + local output="" + local success=false - while IFS="" read -r line || [ -n "$line" ] + while IFS="" read -r line || [ -n "${line}" ] do if [[ $line =~ $regex ]]; then output+="exec " success=true fi - output+="$line\n" + output+="${line}\n" done < start.sh if [ ! success ]; then @@ -128,7 +138,16 @@ patch_start_script() { return 1 fi - printf "$output" > start.sh + printf "${output}" > /var/lib/minecraft/start.sh +} + +update_user_jvm_args() { + if [ -z "${USER_JVM_ARGS}" ]; then + exit 0 + fi + + printf "# Generated by "docker-minecraft-ftb"; DO NOT EDIT.\n\n" > /var/lib/minecraft/user_jvm_args.txt + printf "%s" "${USER_JVM_ARGS}" | xargs -n 1 printf "%s\n" >> /var/lib/minecraft/user_jvm_args.txt } if [ "$1" = "/var/lib/minecraft/start.sh" ]; then @@ -152,7 +171,8 @@ if [ "$1" = "/var/lib/minecraft/start.sh" ]; then fi if [ "${local_pack_id}" -ne "${target_pack_id}" ]; then - echoerr "A modpack is already installed, but the modpack id [${local_pack_id}] does not match the configured modpack id [${target_pack_id}]." + echoerr "A modpack is already installed, but the modpack id [${ansi_green}${local_pack_id}${ansi_rst}] does not " \ + "match the configured modpack id [${ansi_green}${target_pack_id}${ansi_rst}]." exit 1 fi fi @@ -162,7 +182,7 @@ if [ "$1" = "/var/lib/minecraft/start.sh" ]; then modpack_info=$(fetch_modpack_info "${target_pack_id}") modpack_name=$(echo "${modpack_info}" | jq -r '.name') - echo "Selected modpack '${modpack_name}' [${target_pack_id}]" + echo -e "Selected modpack '${ansi_b}${modpack_name}${ansi_rst}' [${ansi_g}${target_pack_id}${ansi_rst}]" # Query latest version @@ -186,7 +206,7 @@ if [ "$1" = "/var/lib/minecraft/start.sh" ]; then # Install modpack if [ "${local_pack_id}" -eq 0 ] || [ "${FORCE_REINSTALL}" == 1 ]; then - echo "Installing modpack version '${target_version_name}' [${target_version_id}]" + echo -e "Installing modpack version '${ansi_b}${target_version_name}${ansi_rst}' [${ansi_g}${target_version_id}${ansi_rst}]" if [ "${FORCE_REINSTALL}" != 1 ] && [ $(ls -A . | wc -l) -ne 0 ]; then echoerr "The destination directory is not empty. Installing the modpack could lead to data loss." @@ -207,7 +227,9 @@ if [ "$1" = "/var/lib/minecraft/start.sh" ]; then # Prevent modpack downgrade if [ "${local_version_id}" -gt "${target_version_id}" ]; then - echoerr "Detected downgrade from version '${local_version_name}' [${local_version_id}] to '${target_version_name}' [${target_version_id}]." + echoerr "Detected downgrade from version " \ + "'${ansi_b}${local_version_name}${ansi_rst}' [${ansi_g}${local_version_id}${ansi_rst}] to " \ + "'${ansi_b}${target_version_name}${ansi_rst}' [${ansi_g}${target_version_id}${ansi_rst}]." echoerr "To continue, set the 'FORCE_REINSTALL' environment variable to '1' and retry." exit 1 fi @@ -215,7 +237,9 @@ if [ "$1" = "/var/lib/minecraft/start.sh" ]; then # Upgrade modpack if [ "${local_version_id}" -lt "${target_version_id}" ]; then - echo "Upgrading modpack from version '${local_version_name}' [${local_version_id}] to '${target_version_name}' [${target_version_id}]." + echo -e "Upgrading modpack from version " \ + "'${ansi_b}${local_version_name}${ansi_rst}' [${ansi_g}${local_version_id}${ansi_rst}] to " \ + "'${ansi_b}${target_version_name}${ansi_rst}' [${ansi_g}${target_version_id}${ansi_rst}]." get_and_run_installer "${target_pack_id}" "${target_version_id}" local_version_id="${target_version_id}" @@ -225,7 +249,7 @@ if [ "$1" = "/var/lib/minecraft/start.sh" ]; then # Show info, if a newer version is available if [ "${local_version_id}" -lt "${latest_version_id}" ]; then - echo "INFO: A newer version '${latest_version_name}' [${latest_version_id}] is available." + echoinfo "A newer version '${ansi_b}${latest_version_name}${ansi_rst}' [${ansi_g}${latest_version_id}${ansi_rst}] is available." fi # Accept Mojang EULA @@ -235,11 +259,14 @@ if [ "$1" = "/var/lib/minecraft/start.sh" ]; then chown minecraft:minecraft eula.txt fi - # TODO: Set- or update memory arguments in user_jvm_args.txt + # Set- or update JVM arguments in `user_jvm_args.txt` + + update_user_jvm_args # Start modpack - echo "Starting modpack '${modpack_name}' (${target_pack_id}) version '${local_version_name}' [${local_version_id}]" + echo -e "Starting modpack '${ansi_b}${modpack_name}${ansi_rst}' [${ansi_g}${target_pack_id}${ansi_rst}] " \ + "version '${ansi_b}${local_version_name}${ansi_rst}' [${ansi_g}${local_version_id}${ansi_rst}]" fi # Execute command on behalf of the 'minecraft' user diff --git a/minecraft-ftb/docker-compose.yml b/minecraft-ftb/docker-compose.yml index 36652cb..8c2e2b9 100644 --- a/minecraft-ftb/docker-compose.yml +++ b/minecraft-ftb/docker-compose.yml @@ -7,8 +7,9 @@ services: stdin_open: true stop_grace_period: 1m environment: - - "FTB_MODPACK_ID=126" - - "ACCEPT_MOJANG_EULA=1" + FTB_MODPACK_ID: 126 + ACCEPT_MOJANG_EULA: 1 + USER_JVM_ARGS: "-Xms1G -Xmx4G" volumes: - ./volume:/var/lib/minecraft:rw ports: diff --git a/minecraft-ftb/run.sh b/minecraft-ftb/run.sh index 305e480..7aa1ab1 100755 --- a/minecraft-ftb/run.sh +++ b/minecraft-ftb/run.sh @@ -6,6 +6,7 @@ docker run -it --rm --name minecraft-ftb \ -v "$SCRIPT_DIR/volume:/var/lib/minecraft" \ -e "FTB_MODPACK_ID=126" \ -e "ACCEPT_MOJANG_EULA=1" \ + -e "USER_JVM_ARGS=-Xms1G -Xmx4G" \ -p "25565:25565" \ --stop-timeout=60 \ flobernd/minecraft-ftb