Compare commits
8 Commits
082282ebe4
...
dab509a234
Author | SHA1 | Date | |
---|---|---|---|
dab509a234 | |||
b1e6604093 | |||
695fae915b | |||
c4e3fc042c | |||
f83c8499e6 | |||
3a943e9397 | |||
4ce50becd2 | |||
813b9de410 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
data
|
/.env
|
||||||
.env
|
/plugins.json
|
||||||
|
scratch
|
||||||
screenlog.0
|
screenlog.0
|
||||||
|
98
Makefile
98
Makefile
@ -1,22 +1,96 @@
|
|||||||
DOCKER_COMPOSE_COMMAND = docker compose -f docker-compose.build.yml
|
DOCKER_BUILDKIT ?= 1
|
||||||
|
BUILDKIT_PROGRESS ?= auto
|
||||||
|
BUILD ?= basic
|
||||||
|
|
||||||
|
DOCKER_COMPOSE_OPTS = BUILDX_GIT_LABELS=full \
|
||||||
|
DOCKER_BUILDKIT=$(DOCKER_BUILDKIT) \
|
||||||
|
BUILDKIT_PROGRESS=$(BUILDKIT_PROGRESS)
|
||||||
|
DOCKER_COMPOSE_BUILD = $(DOCKER_COMPOSE_OPTS) docker compose -f docker-compose.build.yml build
|
||||||
|
DOCKER_COMPOSE_UP = docker compose up -d
|
||||||
|
|
||||||
PRUNE_IMAGES = \
|
PRUNE_IMAGES = \
|
||||||
localhost/minecraft \
|
localhost/minecraft:latest \
|
||||||
localhost/minecraft-base \
|
localhost/minecraft:latest-paper \
|
||||||
localhost/minecraft-spigot
|
localhost/minecraft:latest-spigot \
|
||||||
|
localhost/minecraft:latest-craftbukkit \
|
||||||
|
localhost/minecraft-jre:latest \
|
||||||
|
localhost/minecraft-jdk:latest \
|
||||||
|
localhost/velocity:latest
|
||||||
|
|
||||||
.PHONY: all base vanilla spigot
|
.PHONY: all clean configure craftbukkit default install jdk jre spigot vanilla
|
||||||
default: vanilla
|
default: vanilla
|
||||||
all: vanilla spigot
|
all: vanilla paper spigot craftbukkit velocity
|
||||||
|
|
||||||
base:
|
jre:
|
||||||
$(DOCKER_COMPOSE_COMMAND) build minecraft-base
|
$(DOCKER_COMPOSE_BUILD) minecraft-jre
|
||||||
|
|
||||||
vanilla: base
|
jdk:
|
||||||
$(DOCKER_COMPOSE_COMMAND) build minecraft-vanilla
|
$(DOCKER_COMPOSE_BUILD) minecraft-jdk
|
||||||
|
|
||||||
spigot: base
|
vanilla: jre
|
||||||
$(DOCKER_COMPOSE_COMMAND) build minecraft-spigot
|
$(DOCKER_COMPOSE_BUILD) minecraft-vanilla
|
||||||
|
|
||||||
|
paper: jre
|
||||||
|
$(DOCKER_COMPOSE_BUILD) minecraft-paper
|
||||||
|
|
||||||
|
spigot: jre jdk
|
||||||
|
$(DOCKER_COMPOSE_BUILD) minecraft-spigot
|
||||||
|
|
||||||
|
craftbukkit: jre jdk
|
||||||
|
$(DOCKER_COMPOSE_BUILD) minecraft-craftbukkit
|
||||||
|
|
||||||
|
velocity: jre
|
||||||
|
$(DOCKER_COMPOSE_BUILD) minecraft-velocity
|
||||||
|
|
||||||
|
install:
|
||||||
|
$(DOCKER_COMPOSE_UP)
|
||||||
|
|
||||||
|
# Macro to copy files if they don't already exist or are the same
|
||||||
|
define copy_build_files
|
||||||
|
set -eu; \
|
||||||
|
BUILD=$(1) && \
|
||||||
|
FILE=$(2) && \
|
||||||
|
DIRECTORY=$(if $(3),$(3),builds) && \
|
||||||
|
SRC_FILE="./$${DIRECTORY}/$${BUILD}/$${FILE}" && \
|
||||||
|
DEST_FILE="./$${FILE}" && \
|
||||||
|
TEMP_DEST_FILE="$$(mktemp)" && \
|
||||||
|
if [ -f "$${SRC_FILE}" ]; then \
|
||||||
|
if [ -f "$${DEST_FILE}" ]; then \
|
||||||
|
cp "$${DEST_FILE}" "$${TEMP_DEST_FILE}"; \
|
||||||
|
if [ "$${FILE}" = ".env" ] && grep -q 'EULA=false' "$${SRC_FILE}"; then \
|
||||||
|
sed -i 's/EULA=true/EULA=false/' "$${TEMP_DEST_FILE}"; \
|
||||||
|
fi; \
|
||||||
|
if cmp -s "$${SRC_FILE}" "$${TEMP_DEST_FILE}"; then \
|
||||||
|
echo "[INFO]: \"$${DEST_FILE}\" is up to date."; \
|
||||||
|
rm "$${TEMP_DEST_FILE}"; \
|
||||||
|
else \
|
||||||
|
echo "[ERROR]: \"$${DEST_FILE}\" is different from \"$${SRC_FILE}\""; \
|
||||||
|
diff -u "$${DEST_FILE}" "$${SRC_FILE}"; \
|
||||||
|
rm "$${TEMP_DEST_FILE}"; \
|
||||||
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
cp "$${SRC_FILE}" "$${DEST_FILE}"; \
|
||||||
|
echo "[INFO]: \"$${SRC_FILE}\" copied to \"$${DEST_FILE}\""; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
echo "[WARN]: Source file \"$${SRC_FILE}\" does not exist."; \
|
||||||
|
fi
|
||||||
|
endef
|
||||||
|
|
||||||
|
configure:
|
||||||
|
@if [ -d "./builds/$(BUILD)" ]; then \
|
||||||
|
echo "[INFO]: Configuring build $(BUILD) (./builds/$(BUILD))" && \
|
||||||
|
$(call copy_build_files,$(BUILD),.env); \
|
||||||
|
$(call copy_build_files,$(BUILD),plugins.json); \
|
||||||
|
elif [ -d "./scratch/$(BUILD)" ]; then \
|
||||||
|
echo "[INFO]: Configuring build $(BUILD) (./scratch/$(BUILD))" && \
|
||||||
|
$(call copy_build_files,$(BUILD),.env,scratch); \
|
||||||
|
$(call copy_build_files,$(BUILD),plugins.json,scratch); \
|
||||||
|
else \
|
||||||
|
echo "[ERROR]: Build directory for \"$(BUILD)\" not found"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
docker image rm $(PRUNE_IMAGES) || true
|
docker image rm $(PRUNE_IMAGES) || true
|
||||||
|
105
README.md
105
README.md
@ -1,42 +1,79 @@
|
|||||||
# Minecraft Docker Image
|
# Minecraft Container Image
|
||||||
This Dockerfile sets up a Minecraft server based on the `debian-slim` image.
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
By running the following and building this image, you are agreeing to
|
Assume a clean repository (i.e., without .env and plugins.json in the top
|
||||||
[Minecraft's EULA](https://www.minecraft.net/en-us/eula):
|
directory), and Docker cache. You can use `make clean` to clear specific
|
||||||
|
default images and prune the unused build cache. However, you'll still need to
|
||||||
|
inspect all containers and images to ensure you've removed them all.
|
||||||
|
|
||||||
|
- `make clean` only removes certain images and prunes the builder cache.
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
|
||||||
|
1. **Configure your build:**
|
||||||
|
```
|
||||||
|
make configure
|
||||||
|
```
|
||||||
|
This defaults to `make configure BUILD=basic`, but if you have directories
|
||||||
|
in `./scratch`, you can specify those build names here. Repository-included
|
||||||
|
builds are in`./builds`, but it's advised to copy `./builds/basic` or
|
||||||
|
whichever build configuration you are basing off and copy it into
|
||||||
|
`./scratch/X` to control your settings apart from the repository. This
|
||||||
|
separation allows you to manage your configurations independently and avoid
|
||||||
|
overwriting repository defaults.
|
||||||
|
|
||||||
|
2. **Build the PaperMC server:**
|
||||||
|
|
||||||
|
```
|
||||||
|
make paper
|
||||||
|
```
|
||||||
|
This builds the PaperMC server, which is likely what you want unless you
|
||||||
|
prefer a 100% vanilla server experience. PaperMC is recommended for its
|
||||||
|
performance benefits and support for Bukkit API server mods. Both
|
||||||
|
EssentialsX and WorldGuard suggest using Paper for better performance and
|
||||||
|
stability.
|
||||||
|
|
||||||
|
3. **(Optional) Install for testing:**
|
||||||
|
|
||||||
|
```
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
This runs `docker compose up -d` and brings up a `minecraft-minecraft-1`
|
||||||
|
network/container compose stack using the `.env` and `plugins.json` in the
|
||||||
|
root of the repository. It includes settings for image overrides, the
|
||||||
|
`EULA` agreement, and a `DEBUG` option for the custom `entrypoint.sh`
|
||||||
|
script, Java options (defaulting to `-Xms1G -Xmx2G`), and the ability to
|
||||||
|
set any `server.properties` file entry using the `SETTINGS_` prefix in the
|
||||||
|
compose file. The purpose of `make install` is for testing only, and it is
|
||||||
|
advised not to rely on it for managing an actual server deployment. You
|
||||||
|
will likely want to add other settings not specified in the
|
||||||
|
`docker-compose.yml` and manage your own compose files.
|
||||||
|
|
||||||
|
## Additional Notes
|
||||||
|
|
||||||
|
### Image Management
|
||||||
|
All images are tagged with `localhost/minecraft`, etc. It's acceptable not to
|
||||||
|
override these default image names and just tag your own versions after
|
||||||
|
building the `localhost` images. The images will include git hash information
|
||||||
|
for extra traceability.
|
||||||
|
|
||||||
|
After using the quick start, you'll get something like this:
|
||||||
```
|
```
|
||||||
echo "EULA=true" > .env
|
$ docker image ls
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||||
|
localhost/minecraft 1.20.1-paper 814edda474c4 9 seconds ago 568MB
|
||||||
|
localhost/minecraft-jre latest 50350d8d3947 30 seconds ago 379MB
|
||||||
```
|
```
|
||||||
|
|
||||||
Build the image using the Makefile:
|
It is advisable to tag your own images and push them to a private container
|
||||||
|
repository, as you'll want to avoid pushing these images to a public DockerHub
|
||||||
|
repository due to the Minecraft EULA with typical proprietary software
|
||||||
|
non-redistribution rules.
|
||||||
|
|
||||||
```
|
```
|
||||||
make build
|
docker tag localhost/minecraft:1.20.1-paper example.org/minecraft:1.20.1-paper
|
||||||
|
docker push example.org/minecraft:1.20.1-paper
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, build _and_ run to test it:
|
## License
|
||||||
```
|
This project is licensed under the GPLv3 License. See the LICENSE file for details.
|
||||||
make install
|
|
||||||
```
|
|
||||||
|
|
||||||
Feel free to use `docker compose` directly to build and test:
|
|
||||||
```
|
|
||||||
docker compose build
|
|
||||||
docker compose up -d
|
|
||||||
docker logs -f minecraft-minecraft-1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Copyright and License
|
|
||||||
Copyright (C) 2024 Kris Lamoureux
|
|
||||||
|
|
||||||
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under
|
|
||||||
the terms of the GNU General Public License as published by the Free Software
|
|
||||||
Foundation, version 3 of the License.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with
|
|
||||||
this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
28
builds/basic/.env
Normal file
28
builds/basic/.env
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# While setting EULA to false in 'builds' is potentially annoying, it is done
|
||||||
|
# intentionally to bring to attention the need to manually and explicitly agree
|
||||||
|
# to Minecraft's EULA before being able to run or build these images
|
||||||
|
EULA=false
|
||||||
|
VERSION=1.20.1
|
||||||
|
RUN_TAG=${VERSION}-paper
|
||||||
|
|
||||||
|
# Builds
|
||||||
|
VANILLA_TAG=${VERSION}
|
||||||
|
PAPER_TAG=${VERSION}-paper
|
||||||
|
SPIGOT_TAG=${VERSION}-spigot
|
||||||
|
CRAFTBUKKIT_TAG=${VERSION}-craftbukkit
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Extra image settings #
|
||||||
|
########################
|
||||||
|
#
|
||||||
|
# JAVA_VERSION=latest
|
||||||
|
# JRE_IMAGE=localhost/minecraft-jre
|
||||||
|
# JDK_IMAGE=localhost/minecraft-jdk
|
||||||
|
# JRE_TAG=latest
|
||||||
|
# JDK_TAG=latest
|
||||||
|
#
|
||||||
|
# VANILLA_IMAGE=localhost/minecraft
|
||||||
|
# PAPER_IMAGE=localhost/minecraft
|
||||||
|
# SPIGOT_IMAGE=localhost/minecraft
|
||||||
|
# CRAFTBUKKIT_IMAGE=localhost/minecraft
|
||||||
|
#
|
39
builds/basic/plugins.json
Normal file
39
builds/basic/plugins.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "EssentialsX",
|
||||||
|
"version": "2.20.1",
|
||||||
|
"hash": "sha256:802ea30bda460ca4597e818925816933c123b08d8126a814fac28d03a61bf542",
|
||||||
|
"url": "https://github.com/EssentialsX/Essentials/releases/download/${version}/EssentialsX-${version}.jar",
|
||||||
|
"info_url": "https://essentialsx.net/wiki/Home.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EssentialsXChat",
|
||||||
|
"version": "2.20.1",
|
||||||
|
"hash": "sha256:40aa5c20241ceb3007ebcb5cfbf19bf2c467b0c090ae50e70653ee87ab775ca6",
|
||||||
|
"url": "https://github.com/EssentialsX/Essentials/releases/download/${version}/EssentialsXChat-${version}.jar",
|
||||||
|
"info_url": "https://essentialsx.net/wiki/Module-Breakdown.html#essentialsx-chat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EssentialsXSpawn",
|
||||||
|
"version": "2.20.1",
|
||||||
|
"hash": "sha256:650d7c6a33865a02c5ffa4eb710def28e73d972c9aef85b2b1f4e71b9bd261a0",
|
||||||
|
"url": "https://github.com/EssentialsX/Essentials/releases/download/${version}/EssentialsXSpawn-${version}.jar",
|
||||||
|
"info_url": "https://essentialsx.net/wiki/Module-Breakdown.html#essentialsx-spawn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WorldEdit",
|
||||||
|
"version": "7.3.1",
|
||||||
|
"hash": "md5:c44cd1c16c3d84d8efc57bbf417606cb",
|
||||||
|
"url": "https://dev.bukkit.org/projects/worldedit/files/5326355/download",
|
||||||
|
"info_url": "https://dev.bukkit.org/projects/worldedit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WorldGuard",
|
||||||
|
"version": "7.0.9",
|
||||||
|
"hash": "md5:70d6418dd6a2e4492a9f18e5f9ecb10c",
|
||||||
|
"url": "https://dev.bukkit.org/projects/worldguard/files/4675318/download",
|
||||||
|
"info_url": "https://dev.bukkit.org/projects/worldguard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
18
builds/velocity/.env
Normal file
18
builds/velocity/.env
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# No EULA requirement to run Velocity
|
||||||
|
VERSION=3.3.0-SNAPSHOT
|
||||||
|
VELOCITY_TAG=${VERSION}
|
||||||
|
|
||||||
|
# Run
|
||||||
|
RUN_IMAGE=${VELOCITY_IMAGE:-localhost/velocity}
|
||||||
|
RUN_TAG=${VERSION}
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Extra image settings #
|
||||||
|
########################
|
||||||
|
#
|
||||||
|
# JAVA_VERSION=latest
|
||||||
|
# JRE_IMAGE=localhost/minecraft-jre
|
||||||
|
# JRE_TAG=latest
|
||||||
|
#
|
||||||
|
# VELOCITY_IMAGE=localhost/velocity
|
||||||
|
#
|
@ -1,11 +1,21 @@
|
|||||||
services:
|
services:
|
||||||
minecraft-base:
|
minecraft-jre:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./dockerfiles/Dockerfile.base
|
dockerfile: ./dockerfiles/Dockerfile.java
|
||||||
args:
|
args:
|
||||||
JAVA_VERSION: ${JAVA_VERSION:-latest}
|
JAVA_VERSION: ${JAVA_VERSION:-latest}
|
||||||
image: ${BASE_IMAGE:-localhost/minecraft-base}:${BASE_TAG:-latest}
|
JAVA_RUNTIME: 'true'
|
||||||
|
image: ${JRE_IMAGE:-localhost/minecraft-jre}:${JRE_TAG:-latest}
|
||||||
|
|
||||||
|
minecraft-jdk:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./dockerfiles/Dockerfile.java
|
||||||
|
args:
|
||||||
|
JAVA_VERSION: ${JAVA_VERSION:-latest}
|
||||||
|
JAVA_RUNTIME: 'false'
|
||||||
|
image: ${JDK_IMAGE:-localhost/minecraft-jdk}:${JDK_TAG:-latest}
|
||||||
|
|
||||||
minecraft-vanilla:
|
minecraft-vanilla:
|
||||||
build:
|
build:
|
||||||
@ -13,24 +23,60 @@ services:
|
|||||||
dockerfile: ./dockerfiles/Dockerfile.vanilla
|
dockerfile: ./dockerfiles/Dockerfile.vanilla
|
||||||
args:
|
args:
|
||||||
VERSION: ${VERSION:-latest}
|
VERSION: ${VERSION:-latest}
|
||||||
image: ${IMAGE:-localhost/minecraft}:${TAG:-latest}
|
image: ${VANILLA_IMAGE:-localhost/minecraft}:${VANILLA_TAG:-latest}
|
||||||
depends_on:
|
depends_on:
|
||||||
- minecraft-base
|
- minecraft-jre
|
||||||
|
environment:
|
||||||
|
EULA: "${EULA:-false}"
|
||||||
|
|
||||||
|
minecraft-paper:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./dockerfiles/Dockerfile.paper
|
||||||
|
args:
|
||||||
|
VERSION: ${VERSION:-latest}
|
||||||
|
image: ${PAPER_IMAGE:-localhost/minecraft}:${PAPER_TAG:-latest-paper}
|
||||||
|
depends_on:
|
||||||
|
- minecraft-jre
|
||||||
environment:
|
environment:
|
||||||
EULA: "${EULA:-false}"
|
EULA: "${EULA:-false}"
|
||||||
DEBUG: "${DEBUG:-false}"
|
|
||||||
JVM_OPTS: "${JAVA_OPTS:--Xms1G -Xmx2G}"
|
|
||||||
|
|
||||||
minecraft-spigot:
|
minecraft-spigot:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./dockerfiles/Dockerfile.spigot
|
dockerfile: ./dockerfiles/Dockerfile.bukkit
|
||||||
args:
|
args:
|
||||||
VERSION: ${MINECRAFT_VERSION:-latest}
|
VERSION: ${VERSION:-latest}
|
||||||
image: ${SPIGOT_IMAGE:-localhost/minecraft}:${SPIGOT_TAG:-spigot-latest}
|
SPIGOT: 'true'
|
||||||
|
image: ${SPIGOT_IMAGE:-localhost/minecraft}:${SPIGOT_TAG:-latest-spigot}
|
||||||
depends_on:
|
depends_on:
|
||||||
- minecraft-base
|
- minecraft-jre
|
||||||
|
- minecraft-jdk
|
||||||
|
environment:
|
||||||
|
EULA: "${EULA:-false}"
|
||||||
|
|
||||||
|
minecraft-craftbukkit:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./dockerfiles/Dockerfile.bukkit
|
||||||
|
args:
|
||||||
|
VERSION: ${VERSION:-latest}
|
||||||
|
SPIGOT: 'false'
|
||||||
|
image: ${CRAFTBUKKIT_IMAGE:-localhost/minecraft}:${CRAFTBUKKIT_TAG:-latest-craftbukkit}
|
||||||
|
depends_on:
|
||||||
|
- minecraft-jre
|
||||||
|
- minecraft-jdk
|
||||||
|
environment:
|
||||||
|
EULA: "${EULA:-false}"
|
||||||
|
|
||||||
|
minecraft-velocity:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./dockerfiles/Dockerfile.velocity
|
||||||
|
args:
|
||||||
|
VERSION: ${VELOCITY_VERSION:-latest}
|
||||||
|
image: ${VELOCITY_IMAGE:-localhost/velocity}:${VELOCITY_TAG:-latest}
|
||||||
|
depends_on:
|
||||||
|
- minecraft-jre
|
||||||
environment:
|
environment:
|
||||||
EULA: "${EULA:-false}"
|
EULA: "${EULA:-false}"
|
||||||
DEBUG: "${DEBUG:-false}"
|
|
||||||
JVM_OPTS: "${JAVA_OPTS:--Xms1G -Xmx2G}"
|
|
||||||
|
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
services:
|
||||||
|
minecraft:
|
||||||
|
image: ${RUN_IMAGE:-localhost/minecraft}:${RUN_TAG:-latest}
|
||||||
|
ports:
|
||||||
|
- "0.0.0.0:25565:25565"
|
||||||
|
environment:
|
||||||
|
EULA: "${EULA:-false}"
|
||||||
|
DEBUG: "${DEBUG:-false}"
|
||||||
|
JVM_OPTS: "${JAVA_OPTS:--Xms1G -Xmx2G}"
|
||||||
|
SETTINGS_gamemode: "${GAMEMODE:-survival}"
|
||||||
|
SETTINGS_hardcore: "${HARDCORE:-false}"
|
||||||
|
SETTINGS_motd: "${MOTD:-A Minecraft Server}"
|
||||||
|
SETTINGS_pvp: "${PVP:-true}"
|
@ -1,39 +0,0 @@
|
|||||||
FROM debian:stable-slim
|
|
||||||
|
|
||||||
ARG JAVA_VERSION=latest
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
# Create minecraft user
|
|
||||||
RUN groupadd -g 1000 minecraft && \
|
|
||||||
useradd -m -u 1000 -g 1000 -d /app minecraft
|
|
||||||
|
|
||||||
# Install scripting dependencies
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y curl git gpg jq procps screen strace && \
|
|
||||||
apt-get clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Eclipse Adoptium DEB installer package
|
|
||||||
RUN set -ux && \
|
|
||||||
# Download the Eclipse Adoptium GPG key
|
|
||||||
curl -s https://packages.adoptium.net/artifactory/api/gpg/key/public \
|
|
||||||
| gpg --dearmor | tee /etc/apt/trusted.gpg.d/adoptium.gpg > /dev/null && \
|
|
||||||
# Configure the Eclipse Adoptium apt repository
|
|
||||||
VERSION_CODENAME="$(awk -F= '/^VERSION_CODENAME/{print $2}' /etc/os-release)" && \
|
|
||||||
echo "deb https://packages.adoptium.net/artifactory/deb $VERSION_CODENAME main" \
|
|
||||||
| tee /etc/apt/sources.list.d/adoptium.list
|
|
||||||
|
|
||||||
# Install Adoptium Temurin (OpenJDK Distribution)
|
|
||||||
RUN set -ux && \
|
|
||||||
# Grab latest LTS version if not specified
|
|
||||||
if [ "$JAVA_VERSION" = "latest" ]; then \
|
|
||||||
JAVA_VERSION="$( \
|
|
||||||
curl -s https://api.adoptium.net/v3/info/available_releases \
|
|
||||||
| jq '.most_recent_lts' \
|
|
||||||
)"; \
|
|
||||||
fi && \
|
|
||||||
# Install the Temurin version
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y "temurin-${JAVA_VERSION}-jre" && \
|
|
||||||
apt-get clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
136
dockerfiles/Dockerfile.bukkit
Normal file
136
dockerfiles/Dockerfile.bukkit
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# Build from OpenJDK image
|
||||||
|
FROM "${JDK_IMAGE:-localhost/minecraft-jdk}":"${JDK_TAG:-latest}" as build
|
||||||
|
|
||||||
|
# Minecraft version
|
||||||
|
ARG VERSION=latest
|
||||||
|
|
||||||
|
# Defaults to building Spigot over CraftBukkit
|
||||||
|
ARG SPIGOT=true
|
||||||
|
|
||||||
|
# Plugins prefix
|
||||||
|
ARG PREFIX="PLUGIN_"
|
||||||
|
|
||||||
|
# SpigotMC BuildTools URL
|
||||||
|
ARG BASE_URL="https://hub.spigotmc.org/jenkins/job/BuildTools/"
|
||||||
|
ARG ARTIFACT_PATH="lastSuccessfulBuild/artifact/target/BuildTools.jar"
|
||||||
|
|
||||||
|
# Consider turning bStats (https://bStats.org) on but I'm turning it off by
|
||||||
|
# default because it collects information
|
||||||
|
ARG BSTATS_ENABLED=false
|
||||||
|
|
||||||
|
# Build in common container location
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Download and build Spigot using BuildTools
|
||||||
|
RUN set -eux && \
|
||||||
|
# Grab latest version if not specified
|
||||||
|
if [ "$VERSION" = "latest" ]; then \
|
||||||
|
VERSION="$( \
|
||||||
|
curl -s https://launchermeta.mojang.com/mc/game/version_manifest.json \
|
||||||
|
| jq -r '.latest.release' \
|
||||||
|
)"; \
|
||||||
|
fi && \
|
||||||
|
# Download BuildTools.jar
|
||||||
|
curl -s -o BuildTools.jar "${BASE_URL}${ARTIFACT_PATH}" && \
|
||||||
|
# Build Craftbukkit if SPIGOT is false
|
||||||
|
case "$SPIGOT" in \
|
||||||
|
true) \
|
||||||
|
BUILD_TYPE='SPIGOT' ;; \
|
||||||
|
false) \
|
||||||
|
BUILD_TYPE='CRAFTBUKKIT' ;; \
|
||||||
|
*) \
|
||||||
|
echo "[ERROR]: Invalid value for SPIGOT. Set to 'true' or 'false'"; \
|
||||||
|
exit 1 ;; \
|
||||||
|
esac && \
|
||||||
|
# Run BuildTools to build specified version
|
||||||
|
java -jar BuildTools.jar --rev "$VERSION" --compile "$BUILD_TYPE" && \
|
||||||
|
ln -s \
|
||||||
|
"$(echo "$BUILD_TYPE" | tr '[:upper:]' '[:lower:]')-${VERSION}.jar" \
|
||||||
|
"server.jar"
|
||||||
|
|
||||||
|
# Move into a directory just for storing plugins
|
||||||
|
WORKDIR /plugins
|
||||||
|
|
||||||
|
# Copy in plugins
|
||||||
|
COPY ../plugins.json /plugins/
|
||||||
|
|
||||||
|
# Download defined plugins
|
||||||
|
RUN set -eux && \
|
||||||
|
# Download defined plugins and check against hash
|
||||||
|
tmp_downloads="$(mktemp -d)" && \
|
||||||
|
# Iterate over all plugins in plugins.json
|
||||||
|
jq -c '.plugins[]' plugins.json | while read -r PLUGIN; do \
|
||||||
|
# Set variables from plugins.json
|
||||||
|
name=$(echo "$PLUGIN" | jq -r '.name') && \
|
||||||
|
version=$(echo "$PLUGIN" | jq -r '.version') && \
|
||||||
|
# Interpolate instances of '${version}' in the URL
|
||||||
|
url=$(echo "$PLUGIN" | jq -r '.url' | sed "s/\${version}/$version/g") && \
|
||||||
|
hash=$(echo "$PLUGIN" | jq -r '.hash') && \
|
||||||
|
info=$(echo "$PLUGIN" | jq -r '.info_url') && \
|
||||||
|
# Extract hash type and value, e.g., `md5:6f5902ac237024bdd0c176cb93063dc4`
|
||||||
|
hash_type=$(echo "$hash" | cut -d':' -f1) && \
|
||||||
|
hash_value=$(echo "$hash" | cut -d':' -f2-) && \
|
||||||
|
# Download and compare the hash
|
||||||
|
tmp_file="${tmp_downloads}/${name}-${version}.jar" && \
|
||||||
|
curl -s -L "$url" -o "${tmp_file}" && \
|
||||||
|
case "$hash_type" in \
|
||||||
|
sha256) \
|
||||||
|
echo "${hash_value} $tmp_file" | sha256sum -c - || { \
|
||||||
|
echo "SHA256 hash mismatch for ${name}-${version}.jar"; \
|
||||||
|
rm -rf "$tmp_downloads"; \
|
||||||
|
exit 1; \
|
||||||
|
} \
|
||||||
|
;; \
|
||||||
|
md5) \
|
||||||
|
echo "${hash_value} $tmp_file" | md5sum -c - || { \
|
||||||
|
echo "MD5 hash mismatch for ${name}-${version}.jar"; \
|
||||||
|
rm -rf "$tmp_downloads"; \
|
||||||
|
exit 1; \
|
||||||
|
} \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "Unsupported hash type: ${hash_type}"; \
|
||||||
|
rm -rf "$tmp_downloads"; \
|
||||||
|
exit 1; \
|
||||||
|
;; \
|
||||||
|
esac && \
|
||||||
|
mv "$tmp_file" "${name}-${version}.jar"; \
|
||||||
|
done && \
|
||||||
|
rm -rf "$tmp_downloads"
|
||||||
|
|
||||||
|
# Use OpenJRE image for runtime
|
||||||
|
FROM "${JRE_IMAGE:-localhost/minecraft-jre}":"${JRE_TAG:-latest}" as runtime
|
||||||
|
|
||||||
|
# Run as Minecraft user
|
||||||
|
USER minecraft
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the built bukkit jar from the build stage
|
||||||
|
COPY --from=build /build/server.jar /app/server.jar
|
||||||
|
|
||||||
|
# Copy in plugins
|
||||||
|
COPY --from=build /plugins/ /app/plugins/
|
||||||
|
|
||||||
|
# Generate initial settings
|
||||||
|
RUN java -jar server.jar --initSettings --nogui && \
|
||||||
|
# Disable bStats by default
|
||||||
|
if [ "$BSTATS_ENABLED" = "false" ]; then \
|
||||||
|
mkdir -p /app/plugins/bStats/ && \
|
||||||
|
echo "enabled: false" > /app/plugins/bStats/config.yml; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Back to root to copy the entrypoint in
|
||||||
|
USER root
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy in entrypoint script
|
||||||
|
COPY ../entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
|
# Run app as minecraft user
|
||||||
|
USER minecraft
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Expose port and run entrypoint script
|
||||||
|
EXPOSE 25565
|
||||||
|
ENTRYPOINT ["entrypoint.sh"]
|
@ -0,0 +1,60 @@
|
|||||||
|
FROM debian:stable-slim
|
||||||
|
|
||||||
|
ARG JAVA_RUNTIME
|
||||||
|
ARG JAVA_VERSION=latest
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Install scripting dependencies
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y curl git gpg jq && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Eclipse Adoptium DEB installer package
|
||||||
|
RUN set -eux && \
|
||||||
|
# Download the Eclipse Adoptium GPG key
|
||||||
|
curl -s https://packages.adoptium.net/artifactory/api/gpg/key/public \
|
||||||
|
| gpg --dearmor | tee /etc/apt/trusted.gpg.d/adoptium.gpg > /dev/null && \
|
||||||
|
# Configure the Eclipse Adoptium APT repository
|
||||||
|
VERSION_CODENAME="$(awk -F= '/^VERSION_CODENAME/{print $2}' /etc/os-release)" && \
|
||||||
|
echo "deb https://packages.adoptium.net/artifactory/deb $VERSION_CODENAME main" \
|
||||||
|
| tee /etc/apt/sources.list.d/adoptium.list
|
||||||
|
|
||||||
|
# Install Adoptium Temurin (OpenJDK/OpenJRE)
|
||||||
|
RUN set -eux && \
|
||||||
|
# Grab latest LTS version if not specified
|
||||||
|
if [ "$JAVA_VERSION" = "latest" ]; then \
|
||||||
|
JAVA_VERSION="$( \
|
||||||
|
curl -s https://api.adoptium.net/v3/info/available_releases \
|
||||||
|
| jq '.most_recent_lts' \
|
||||||
|
)"; \
|
||||||
|
fi && \
|
||||||
|
# Install OpenJDK if JAVA_RUNTIME is false
|
||||||
|
case "$JAVA_RUNTIME" in \
|
||||||
|
true) \
|
||||||
|
JAVA_TYPE='jre' ;; \
|
||||||
|
false) \
|
||||||
|
JAVA_TYPE='jdk' ;; \
|
||||||
|
*) \
|
||||||
|
echo "[ERROR]: Invalid value for JAVA_RUNTIME. Set to 'true' or 'false'"; \
|
||||||
|
exit 1 ;; \
|
||||||
|
esac && \
|
||||||
|
# Install the Temurin version
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y "temurin-$JAVA_VERSION-$JAVA_TYPE" && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create minecraft user for runtime
|
||||||
|
RUN if [ "$JAVA_RUNTIME" = "true" ]; then \
|
||||||
|
groupadd -g 1000 minecraft && \
|
||||||
|
useradd -m -u 1000 -g 1000 -d /app minecraft; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install additional runtime dependencies
|
||||||
|
RUN if [ "$JAVA_RUNTIME" = "true" ]; then \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y procps screen strace && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*; \
|
||||||
|
fi
|
120
dockerfiles/Dockerfile.paper
Normal file
120
dockerfiles/Dockerfile.paper
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
FROM "${JRE_IMAGE:-localhost/minecraft-jre}":"${JRE_TAG:-latest}"
|
||||||
|
|
||||||
|
# Minecraft version to download
|
||||||
|
ARG VERSION=latest
|
||||||
|
|
||||||
|
# Plugins prefix
|
||||||
|
ARG PREFIX="PLUGIN_"
|
||||||
|
|
||||||
|
# PaperMC base URL
|
||||||
|
ARG BASE_URL="https://api.papermc.io/v2/projects/paper/versions/${VERSION}"
|
||||||
|
|
||||||
|
# Consider turning bStats (https://bStats.org) on but I'm turning it off by
|
||||||
|
# default because it collects information
|
||||||
|
ARG BSTATS_ENABLED=false
|
||||||
|
|
||||||
|
# Download files
|
||||||
|
USER root
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Download and verify sha256sum for PaperMC server.jar
|
||||||
|
RUN set -eux && \
|
||||||
|
# Grab latest version if not specified
|
||||||
|
if [ "$VERSION" = "latest" ]; then \
|
||||||
|
VERSION="$( \
|
||||||
|
curl -s https://api.papermc.io/v2/projects/paper | \
|
||||||
|
jq -r '.versions[-1]' \
|
||||||
|
)"; \
|
||||||
|
fi && \
|
||||||
|
# Get latest build for the specified version
|
||||||
|
BUILD="$( \
|
||||||
|
curl -s "$BASE_URL" \
|
||||||
|
| jq -r '.builds[-1]' \
|
||||||
|
)" && \
|
||||||
|
URL="${BASE_URL}/builds/${BUILD}/downloads/paper-${VERSION}-${BUILD}.jar" && \
|
||||||
|
curl -s -o /tmp/server.jar "$URL" && \
|
||||||
|
# Get SHA256 hash of server.jar and compare
|
||||||
|
SHA256="$(sha256sum /tmp/server.jar | awk '{print $1}')" && \
|
||||||
|
EXPECTED="$( \
|
||||||
|
curl -s "$BASE_URL/builds/$BUILD" \
|
||||||
|
| jq -r '.downloads.application.sha256' \
|
||||||
|
)" && \
|
||||||
|
if [ ! "$SHA256" = "$EXPECTED" ]; then \
|
||||||
|
echo "[ERROR]: SHA256=\"$SHA256\" expected \"$EXPECTED\""; \
|
||||||
|
exit 1; \
|
||||||
|
fi && \
|
||||||
|
mv /tmp/server.jar /app/server.jar
|
||||||
|
|
||||||
|
# Move into a directory just for storing plugins
|
||||||
|
WORKDIR /app/plugins
|
||||||
|
|
||||||
|
# Copy in plugins
|
||||||
|
COPY ../plugins.json /app/plugins
|
||||||
|
|
||||||
|
# Download defined plugins
|
||||||
|
RUN set -eux && \
|
||||||
|
# Download defined plugins and check against hash
|
||||||
|
tmp_downloads="$(mktemp -d)" && \
|
||||||
|
# Iterate over all plugins in plugins.json
|
||||||
|
jq -c '.plugins[]' plugins.json | while read -r PLUGIN; do \
|
||||||
|
# Set variables from plugins.json
|
||||||
|
name=$(echo "$PLUGIN" | jq -r '.name') && \
|
||||||
|
version=$(echo "$PLUGIN" | jq -r '.version') && \
|
||||||
|
# Interpolate instances of '${version}' in the URL
|
||||||
|
url=$(echo "$PLUGIN" | jq -r '.url' | sed "s/\${version}/$version/g") && \
|
||||||
|
hash=$(echo "$PLUGIN" | jq -r '.hash') && \
|
||||||
|
info=$(echo "$PLUGIN" | jq -r '.info_url') && \
|
||||||
|
# Extract hash type and value, e.g., `md5:6f5902ac237024bdd0c176cb93063dc4`
|
||||||
|
hash_type=$(echo "$hash" | cut -d':' -f1) && \
|
||||||
|
hash_value=$(echo "$hash" | cut -d':' -f2-) && \
|
||||||
|
# Download and compare the hash
|
||||||
|
tmp_file="${tmp_downloads}/${name}-${version}.jar" && \
|
||||||
|
curl -s -L "$url" -o "${tmp_file}" && \
|
||||||
|
case "$hash_type" in \
|
||||||
|
sha256) \
|
||||||
|
echo "${hash_value} $tmp_file" | sha256sum -c - || { \
|
||||||
|
echo "SHA256 hash mismatch for ${name}-${version}.jar"; \
|
||||||
|
rm -rf "$tmp_downloads"; \
|
||||||
|
exit 1; \
|
||||||
|
} \
|
||||||
|
;; \
|
||||||
|
md5) \
|
||||||
|
echo "${hash_value} $tmp_file" | md5sum -c - || { \
|
||||||
|
echo "MD5 hash mismatch for ${name}-${version}.jar"; \
|
||||||
|
rm -rf "$tmp_downloads"; \
|
||||||
|
exit 1; \
|
||||||
|
} \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
echo "Unsupported hash type: ${hash_type}"; \
|
||||||
|
rm -rf "$tmp_downloads"; \
|
||||||
|
exit 1; \
|
||||||
|
;; \
|
||||||
|
esac && \
|
||||||
|
mv "$tmp_file" "${name}-${version}.jar"; \
|
||||||
|
done && \
|
||||||
|
rm -rf "$tmp_downloads" && \
|
||||||
|
chown minecraft:minecraft /app/plugins/
|
||||||
|
|
||||||
|
# Generate initial settings
|
||||||
|
USER minecraft
|
||||||
|
WORKDIR /app
|
||||||
|
RUN java -jar server.jar --initSettings --nogui && \
|
||||||
|
# Disable bStats by default
|
||||||
|
if [ "$BSTATS_ENABLED" = "false" ]; then \
|
||||||
|
mkdir -p /app/plugins/bStats/ && \
|
||||||
|
echo "enabled: false" > /app/plugins/bStats/config.yml; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Back to root to copy the entrypoint in
|
||||||
|
USER root
|
||||||
|
COPY ../entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
|
# Run app as minecraft user
|
||||||
|
USER minecraft
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Expose port and run entrypoint script
|
||||||
|
EXPOSE 25565
|
||||||
|
ENTRYPOINT ["entrypoint.sh"]
|
@ -1,45 +0,0 @@
|
|||||||
FROM "${BASE_IMAGE:-localhost/minecraft-base}":"${BASE_TAG:-latest}"
|
|
||||||
|
|
||||||
# Minecraft version
|
|
||||||
ARG VERSION=latest
|
|
||||||
|
|
||||||
# SpigotMC BuildTools URL
|
|
||||||
ARG BASE_URL="https://hub.spigotmc.org/jenkins/job/BuildTools/"
|
|
||||||
ARG ARTIFACT_PATH="lastSuccessfulBuild/artifact/target/BuildTools.jar"
|
|
||||||
|
|
||||||
# Download files and run as user
|
|
||||||
USER minecraft
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Download and build Spigot using BuildTools
|
|
||||||
RUN set -ux && \
|
|
||||||
# Grab latest version if not specified
|
|
||||||
if [ "$VERSION" = "latest" ]; then \
|
|
||||||
VERSION="$( \
|
|
||||||
curl -s https://launchermeta.mojang.com/mc/game/version_manifest.json \
|
|
||||||
| jq -r '.latest.release' \
|
|
||||||
)"; \
|
|
||||||
fi && \
|
|
||||||
# Download BuildTools.jar
|
|
||||||
curl -s -o BuildTools.jar "${BASE_URL}${ARTIFACT_PATH}" && \
|
|
||||||
## Run BuildTools to build specified version
|
|
||||||
java -jar BuildTools.jar --rev "$VERSION" --compile SPIGOT
|
|
||||||
|
|
||||||
# Generate initial settings
|
|
||||||
RUN java -jar server.jar --initSettings --nogui
|
|
||||||
|
|
||||||
# Back to root to copy the entrypoint in
|
|
||||||
USER root
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy in entrypoint script
|
|
||||||
COPY ../entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
||||||
|
|
||||||
# Run app as minecraft user
|
|
||||||
USER minecraft
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Expose port and run entrypoint script
|
|
||||||
EXPOSE 25565
|
|
||||||
ENTRYPOINT ["entrypoint.sh"]
|
|
@ -1,6 +1,6 @@
|
|||||||
FROM "${BASE_IMAGE:-localhost/minecraft-base}":"${BASE_TAG:-latest}"
|
FROM "${JRE_IMAGE:-localhost/minecraft-jre}":"${JRE_TAG:-latest}"
|
||||||
|
|
||||||
# Minecraft version
|
# Minecraft version to download
|
||||||
ARG VERSION=latest
|
ARG VERSION=latest
|
||||||
|
|
||||||
# Download files and run as user
|
# Download files and run as user
|
||||||
@ -8,7 +8,7 @@ USER minecraft
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Download and verify sha1sum for server.jar
|
# Download and verify sha1sum for server.jar
|
||||||
RUN set -ux && \
|
RUN set -eux && \
|
||||||
# Grab latest version if not specified
|
# Grab latest version if not specified
|
||||||
if [ "$VERSION" = "latest" ]; then \
|
if [ "$VERSION" = "latest" ]; then \
|
||||||
VERSION="$( \
|
VERSION="$( \
|
||||||
@ -25,7 +25,7 @@ RUN set -ux && \
|
|||||||
SHA1="$(sha1sum server.jar | awk '{print $1}')" && \
|
SHA1="$(sha1sum server.jar | awk '{print $1}')" && \
|
||||||
EXPECTED="$(jq -r .sha1 /tmp/dl.json)"; rm /tmp/dl.json && \
|
EXPECTED="$(jq -r .sha1 /tmp/dl.json)"; rm /tmp/dl.json && \
|
||||||
if [ ! "$SHA1" = "$EXPECTED" ]; then \
|
if [ ! "$SHA1" = "$EXPECTED" ]; then \
|
||||||
echo "[ERROR] SHA1=\"$SHA1\" expected \"$EXPECTED\""; \
|
echo "[ERROR]: SHA1=\"$SHA1\" expected \"$EXPECTED\""; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
90
dockerfiles/Dockerfile.velocity
Normal file
90
dockerfiles/Dockerfile.velocity
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
FROM "${JRE_IMAGE:-localhost/minecraft-jre}":"${JRE_TAG:-latest}"
|
||||||
|
|
||||||
|
# Server version to download
|
||||||
|
ARG VERSION=latest
|
||||||
|
|
||||||
|
# PaperMC base URL
|
||||||
|
ARG BASE_URL="https://api.papermc.io/v2/projects/velocity/versions"
|
||||||
|
|
||||||
|
# Consider turning bStats (https://bStats.org) on but I'm turning it off by
|
||||||
|
# default because it collects information
|
||||||
|
ARG BSTATS_ENABLED=false
|
||||||
|
|
||||||
|
# For the entrypoint.sh script
|
||||||
|
ENV VELOCITY=true
|
||||||
|
|
||||||
|
# Download files
|
||||||
|
USER root
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install expect
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y expect && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Download and verify sha256sum for Velocity
|
||||||
|
RUN set -eux && \
|
||||||
|
# Grab latest version if not specified
|
||||||
|
if [ "$VERSION" = "latest" ]; then \
|
||||||
|
VERSION="$( \
|
||||||
|
curl -s https://api.papermc.io/v2/projects/velocity | \
|
||||||
|
jq -r '.versions[-1]' \
|
||||||
|
)"; \
|
||||||
|
fi && \
|
||||||
|
# Get latest build for the specified version
|
||||||
|
BUILD="$( \
|
||||||
|
curl -s "${BASE_URL}/${VERSION}" \
|
||||||
|
| jq -r '.builds[-1]' \
|
||||||
|
)" && \
|
||||||
|
URL="${BASE_URL}/${VERSION}/builds/${BUILD}/downloads/velocity-${VERSION}-${BUILD}.jar" && \
|
||||||
|
curl -s -o /tmp/server.jar "$URL" && \
|
||||||
|
# Get SHA256 hash of server.jar and compare
|
||||||
|
SHA256="$(sha256sum /tmp/server.jar | awk '{print $1}')" && \
|
||||||
|
EXPECTED="$( \
|
||||||
|
curl -s "$BASE_URL/$VERSION/builds/$BUILD" \
|
||||||
|
| jq -r '.downloads.application.sha256' \
|
||||||
|
)" && \
|
||||||
|
if [ ! "$SHA256" = "$EXPECTED" ]; then \
|
||||||
|
echo "[ERROR]: SHA256=\"$SHA256\" expected \"$EXPECTED\""; \
|
||||||
|
exit 1; \
|
||||||
|
fi && \
|
||||||
|
mv /tmp/server.jar /app/velocity.jar
|
||||||
|
|
||||||
|
# Generate files as minecraft user
|
||||||
|
USER minecraft
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Start server to generate initial files
|
||||||
|
RUN set -ux; \
|
||||||
|
expect -c "\
|
||||||
|
set timeout -1; \
|
||||||
|
spawn /usr/bin/java -Xms1G -Xmx1G -XX:+UseG1GC -XX:G1HeapRegionSize=4M \
|
||||||
|
-XX:+UnlockExperimentalVMOptions -XX:+ParallelRefProcEnabled \
|
||||||
|
-XX:+AlwaysPreTouch -XX:MaxInlineLevel=15 -jar velocity.jar; \
|
||||||
|
expect -re {\[[0-9]{2}:[0-9]{2}:[0-9]{2} INFO\]: Done .*!} { \
|
||||||
|
send \"stop\r\"; \
|
||||||
|
expect eof \
|
||||||
|
} \
|
||||||
|
" && \
|
||||||
|
# Disable bStats by default and clear server-uuid
|
||||||
|
cd /app/plugins/bStats/ || exit 1; \
|
||||||
|
sed -i.bak "s/^enabled=.*\$/enabled=${BSTATS_ENABLED}/" config.txt && \
|
||||||
|
diff --unified=1 config.txt.bak config.txt || true && rm config.txt.bak && \
|
||||||
|
sed -i.bak "s/^server-uuid=.*\$/server-uuid=/" config.txt && \
|
||||||
|
diff --unified=1 config.txt.bak config.txt || true && rm config.txt.bak && \
|
||||||
|
# Truncate forwarding secret
|
||||||
|
truncate -s 0 /app/forwarding.secret
|
||||||
|
|
||||||
|
# Back to root to copy the entrypoint in
|
||||||
|
USER root
|
||||||
|
COPY ../entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
|
# Run app as minecraft user
|
||||||
|
USER minecraft
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Expose port and run entrypoint script
|
||||||
|
EXPOSE 25565
|
||||||
|
ENTRYPOINT ["entrypoint.sh"]
|
307
entrypoint.sh
307
entrypoint.sh
@ -1,19 +1,188 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Set eula value in eula.txt
|
||||||
|
set_eula() {
|
||||||
|
local EULA
|
||||||
|
EULA="${1:-false}"
|
||||||
|
EULAFILE="${EULAFILE:-/app/eula.txt}"
|
||||||
|
sed -i.bak "s/^eula=.*\$/eula=${EULA:-false}/" "$EULAFILE"
|
||||||
|
diff --unified=1 "${EULAFILE}.bak" "$EULAFILE" || true
|
||||||
|
rm "${EULAFILE}.bak"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update server.properties using env
|
||||||
|
set_properties() {
|
||||||
|
# Basic settings
|
||||||
|
DEBUG="${DEBUG:-false}"
|
||||||
|
PREFIX="${PREFIX:-SETTINGS_}"
|
||||||
|
FILE="${FILE:-/app/server.properties}"
|
||||||
|
|
||||||
|
# Update server.properties
|
||||||
|
while IFS='=' read -r ENVVAR VALUE ; do
|
||||||
|
if echo "$ENVVAR" | grep -q "^${PREFIX}.*$"; then
|
||||||
|
KEY="${ENVVAR#"$PREFIX"}"
|
||||||
|
if ! grep -q "^${KEY}=" "$FILE"; then
|
||||||
|
echo "[WARN]: \"$KEY\" does not exist in $FILE and was not updated"
|
||||||
|
else
|
||||||
|
[ "$DEBUG" = "true" ] && echo "[DEBUG]: Updating \"$KEY\" to \"$VALUE\""
|
||||||
|
sed -i.bak "s/^${KEY}=.*/${KEY}=${VALUE}/" "$FILE"
|
||||||
|
diff --unified=1 "${FILE}.bak" "$FILE" || true
|
||||||
|
rm "${FILE}.bak"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$DEBUG" = "true" ]; then
|
||||||
|
echo "[DEBUG]: \"$ENVVAR\" doesn't have the prefix \"$PREFIX\""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(env)
|
||||||
|
|
||||||
|
# Show server.properties in DEBUG mode
|
||||||
|
if [ "$DEBUG" = "true" ]; then
|
||||||
|
echo "[DEBUG]: Showing ${FILE}:"
|
||||||
|
cat "$FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set Velocity's forwarding.secret
|
||||||
|
set_forwarding_secret() {
|
||||||
|
local WRITE_FILE
|
||||||
|
local FILE_CONTENT
|
||||||
|
WRITE_FILE=false
|
||||||
|
|
||||||
|
# Check file is not empty
|
||||||
|
if [ -s /app/forwarding.secret ]; then
|
||||||
|
FILE_CONTENT="$(head -c 1025 /app/forwarding.secret)"
|
||||||
|
# Check that FORWARDING_SECRET is blank
|
||||||
|
if [ ! "${#FORWARDING_SECRET}" -gt 0 ]; then
|
||||||
|
# Only the file was set, so FORWARDING_SECRET becomes the file
|
||||||
|
FORWARDING_SECRET="$(head -c 1025 /app/forwarding.secret)"
|
||||||
|
else
|
||||||
|
if [ ! "$FORWARDING_SECRET" = "$FILE_CONTENT" ]; then
|
||||||
|
# You should either bind mount a file in OR set a value
|
||||||
|
echo "[ERROR]: FORWARDING_SECRET is set with an existing file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# If the file is zero, we make sure the variable isn't also zero
|
||||||
|
elif [ "${#FORWARDING_SECRET}" -eq 0 ]; then
|
||||||
|
echo "[ERROR]: You must set FORWARDING_SECRET or set a value in the file"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
# File is zero, so we must write the variable out to the file
|
||||||
|
WRITE_FILE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check length
|
||||||
|
if [ "${#FORWARDING_SECRET}" -lt 32 ]; then
|
||||||
|
echo "[ERROR]: FORWARDING_SECRET needs to be at least 32 characters long"
|
||||||
|
exit 1
|
||||||
|
elif [ "${#FORWARDING_SECRET}" -gt 1024 ]; then
|
||||||
|
echo "[ERROR]: FORWARDING_SECRET is >1024 bytes"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add secret to file
|
||||||
|
if [ "$WRITE_FILE" = "true" ]; then
|
||||||
|
echo "$FORWARDING_SECRET" > /app/forwarding.secret
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unset sensitive value
|
||||||
|
unset FORWARDING_SECRET
|
||||||
|
}
|
||||||
|
|
||||||
# Check if the minecraft screen is still running
|
# Check if the minecraft screen is still running
|
||||||
|
# shellcheck disable=SC2317
|
||||||
check_screen() {
|
check_screen() {
|
||||||
if [ "$(screen -ls | grep -cE '[0-9]+\.minecraft')" -eq 1 ]; then
|
local SCREEN_NAME
|
||||||
|
SCREEN_NAME="$1"
|
||||||
|
if [ "$(screen -ls | grep -cE "[0-9]+\.$SCREEN_NAME")" -eq 1 ]; then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Find Java PID, strace it, and wait for it to exit
|
||||||
|
wait_on_java() {
|
||||||
|
local JAVA_PID
|
||||||
|
local JAVA_EXIT
|
||||||
|
|
||||||
|
# Debug mode
|
||||||
|
[ "$DEBUG" = "true" ] && ps aux
|
||||||
|
|
||||||
|
# Capture PID and test
|
||||||
|
JAVA_PID="$(pgrep java)"
|
||||||
|
if ! kill -0 "$SCREEN_PID" 2>/dev/null; then
|
||||||
|
echo "[ERROR]: Cannot find running Java process (PID: \"$JAVA_PID\")"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# strace Java PID and get return code
|
||||||
|
JAVA_EXIT="$(strace -e trace=exit -p "$JAVA_PID" 2>&1 \
|
||||||
|
| grep -oP '^\+\+\+ exited with \K[0-9]+')"
|
||||||
|
|
||||||
|
# Delay if Java exits non-zero
|
||||||
|
if [ ! "$JAVA_EXIT" = "0" ]; then
|
||||||
|
echo "[ERROR]: Java exited with non-zero status"
|
||||||
|
sleep "${EXIT_DELAY:-5}"
|
||||||
|
exit "$JAVA_EXIT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find screen PID, strace it, and wait for it to exit
|
||||||
|
wait_on_screen() {
|
||||||
|
local SCREEN_PID
|
||||||
|
local SCREEN_NAME
|
||||||
|
local STRACE_PID
|
||||||
|
local TAIL_PID
|
||||||
|
|
||||||
|
SCREEN_NAME="$1"
|
||||||
|
|
||||||
|
# Get screen PID
|
||||||
|
[ "$DEBUG" = "true" ] && screen -ls
|
||||||
|
SCREEN_PID="$(
|
||||||
|
screen -ls | grep -oE "[0-9]+\.$SCREEN_NAME" | cut -d. -f1
|
||||||
|
)"
|
||||||
|
|
||||||
|
# Check screen PID
|
||||||
|
if ! kill -0 "$SCREEN_PID" 2>/dev/null; then
|
||||||
|
echo "[ERROR] Cannot find \"$SCREEN_NAME\" screen (PID: \"$SCREEN_PID\")"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output logs to stdout (touch in case slow to create)
|
||||||
|
touch screenlog.0
|
||||||
|
tail -f screenlog.0 &
|
||||||
|
TAIL_PID="$!"
|
||||||
|
|
||||||
|
# Debug mode
|
||||||
|
[ "$DEBUG" = "true" ] && ps aux
|
||||||
|
|
||||||
|
# Wait for screen to exit
|
||||||
|
strace -e exit -e signal=none -p "$SCREEN_PID" 2>/dev/null &
|
||||||
|
STRACE_PID="$!"
|
||||||
|
|
||||||
|
# Wait on Java PID first
|
||||||
|
wait_on_java
|
||||||
|
|
||||||
|
# Wait if screen is somehow still running
|
||||||
|
wait "$STRACE_PID"
|
||||||
|
|
||||||
|
# Kill tail PID
|
||||||
|
if kill -0 "$TAIL_PID" 2>/dev/null; then
|
||||||
|
kill "$TAIL_PID"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Function to stop the server gracefully
|
# Function to stop the server gracefully
|
||||||
|
# shellcheck disable=SC2317
|
||||||
stop_server() {
|
stop_server() {
|
||||||
if check_screen; then
|
local SCREEN_NAME
|
||||||
|
SCREEN_NAME="$1"
|
||||||
|
if check_screen "$SCREEN_NAME"; then
|
||||||
# Run 'stop' inside screen and wait for the screen to exit
|
# Run 'stop' inside screen and wait for the screen to exit
|
||||||
/usr/bin/screen -p 0 -S minecraft -X eval 'stuff "stop"\015'
|
/usr/bin/screen -p 0 -S "$SCREEN_NAME" -X eval 'stuff "stop"\015'
|
||||||
wait "$STRACE_PID"
|
wait "$STRACE_PID"
|
||||||
|
|
||||||
# Stop tail -f to stdout
|
# Stop tail -f to stdout
|
||||||
@ -24,13 +193,13 @@ stop_server() {
|
|||||||
# Check only this script is running (PID 1) and pgrep (2 PIDs total)
|
# Check only this script is running (PID 1) and pgrep (2 PIDs total)
|
||||||
PGREP_OUTPUT="$(pgrep .)"
|
PGREP_OUTPUT="$(pgrep .)"
|
||||||
if ! [ "$(echo "$PGREP_OUTPUT" | wc -l)" -eq 2 ]; then
|
if ! [ "$(echo "$PGREP_OUTPUT" | wc -l)" -eq 2 ]; then
|
||||||
echo "[WARN] Some processes might not have exited:"
|
echo "[WARN]: Some processes might not have exited:"
|
||||||
echo "$PGREP_OUTPUT"
|
echo "$PGREP_OUTPUT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Exit cleanly
|
# Exit cleanly
|
||||||
echo "[INFO] Server stopped gracefully"
|
echo "[INFO]: Server stopped gracefully"
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
echo "[ERROR]: Can't find which screen to use"
|
echo "[ERROR]: Can't find which screen to use"
|
||||||
@ -39,77 +208,75 @@ stop_server() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Start the Minecraft server
|
||||||
|
minecraft_server() {
|
||||||
|
# Settings
|
||||||
|
JVM_OPTS="${JVM_OPTS:--Xms1G -Xmx2G}"
|
||||||
|
|
||||||
|
# Set EULA
|
||||||
|
set_eula "${EULA:-false}"
|
||||||
|
|
||||||
|
# Update server.properties using env
|
||||||
|
set_properties
|
||||||
|
|
||||||
|
# Set up a SIGTERM signal trap to stop the server
|
||||||
|
trap 'stop_server minecraft' SIGTERM
|
||||||
|
|
||||||
|
# temp
|
||||||
|
ls -al /app
|
||||||
|
ls -al /app/config
|
||||||
|
|
||||||
|
# Run server in screen (without attaching)
|
||||||
|
echo "[INFO]: Starting Minecraft server"
|
||||||
|
/usr/bin/screen -dmS minecraft -L \
|
||||||
|
bash -c "/usr/bin/java $JVM_OPTS -jar server.jar --nogui"
|
||||||
|
|
||||||
|
# Wait for 'minecraft' screen PID to exit
|
||||||
|
wait_on_screen minecraft
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start the Velocity proxy Minecraft server
|
||||||
|
velocity_server() {
|
||||||
|
# Settings
|
||||||
|
JVM_OPTS="${JVM_OPTS:--Xms1G -Xmx2G}"
|
||||||
|
|
||||||
|
# Ensure there is a forwarding.secret
|
||||||
|
set_forwarding_secret
|
||||||
|
|
||||||
|
# Set up a SIGTERM signal trap to stop the server
|
||||||
|
trap 'stop_server velocity' SIGTERM
|
||||||
|
|
||||||
|
# Start server
|
||||||
|
echo "[INFO]: Starting Velocity server"
|
||||||
|
/usr/bin/screen -dmS velocity -L \
|
||||||
|
bash -c "
|
||||||
|
/usr/bin/java $JVM_OPTS -XX:+UseG1GC -XX:G1HeapRegionSize=4M \
|
||||||
|
-XX:+UnlockExperimentalVMOptions -XX:+ParallelRefProcEnabled \
|
||||||
|
-XX:+AlwaysPreTouch -XX:MaxInlineLevel=15 -jar velocity.jar
|
||||||
|
"
|
||||||
|
|
||||||
|
# Wait for 'velocity' screen PID to exit
|
||||||
|
wait_on_screen velocity
|
||||||
|
}
|
||||||
|
|
||||||
# Enable debug mode
|
# Enable debug mode
|
||||||
DEBUG="${DEBUG:-false}"
|
DEBUG="${DEBUG:-false}"
|
||||||
if [ "$DEBUG" = "true" ]; then
|
if [ "$DEBUG" = "true" ]; then
|
||||||
echo "[DEBUG] Running entrypoint script at $(which entrypoint.sh)"
|
echo "[DEBUG]: Running entrypoint script at $(which entrypoint.sh)"
|
||||||
sleep 0.2
|
sleep 0.2
|
||||||
set -ux
|
set -ux
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Settings
|
# Start Velocity proxy if VELOCITY='true' otherwise start a Minecraft server
|
||||||
FILE="${FILE:-/app/server.properties}"
|
VELOCITY="${VELOCITY:-false}"
|
||||||
EULAFILE="${EULAFILE:-/app/eula.txt}"
|
if [ "$VELOCITY" = "true" ]; then
|
||||||
PREFIX="${PREFIX:-SETTINGS_}"
|
# Start Velocity proxy
|
||||||
JVM_OPTS="${JVM_OPTS:--Xms1G -Xmx2G}"
|
velocity_server
|
||||||
|
else
|
||||||
# Set EULA
|
# Start Minecraft
|
||||||
sed -i.bak "s/^eula=.*\$/eula=${EULA:-false}/" "$EULAFILE"
|
minecraft_server
|
||||||
diff --unified=1 "${EULAFILE}.bak" "$EULAFILE"
|
|
||||||
rm "${EULAFILE}.bak"
|
|
||||||
|
|
||||||
# Update server.properties using env
|
|
||||||
while IFS='=' read -r ENVVAR VALUE ; do
|
|
||||||
if echo "$ENVVAR" | grep -q "^${PREFIX}.*$"; then
|
|
||||||
KEY="${ENVVAR#"$PREFIX"}"
|
|
||||||
if ! grep -q "^${KEY}=" "$FILE"; then
|
|
||||||
echo "[WARN]: \"$KEY\" does not exist in $FILE and was not updated"
|
|
||||||
else
|
|
||||||
[ "$DEBUG" = "true" ] && echo "[DEBUG] Updating \"$KEY\" to \"$VALUE\""
|
|
||||||
sed -i.bak "s/^${KEY}=.*/${KEY}=${VALUE}/" "$FILE"
|
|
||||||
diff --unified=1 "${FILE}.bak" "$FILE"
|
|
||||||
rm "${FILE}.bak"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$DEBUG" = "true" ]; then
|
|
||||||
echo "[DEBUG] \"$ENVVAR\" doesn't have the prefix \"$PREFIX\""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done < <(env)
|
|
||||||
|
|
||||||
# Show server.properties in DEBUG mode
|
|
||||||
if [ "$DEBUG" = "true" ]; then
|
|
||||||
echo "[DEBUG] Showing ${FILE}:"
|
|
||||||
cat "$FILE"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set up a SIGTERM signal trap to stop the server
|
# Exit gracefully
|
||||||
trap 'stop_server' SIGTERM
|
exit 0
|
||||||
|
|
||||||
# Run server in screen (without attaching)
|
|
||||||
echo "[INFO] Starting Minecraft server"
|
|
||||||
/usr/bin/screen -dmS minecraft -L \
|
|
||||||
bash -c "/usr/bin/java $JVM_OPTS -jar server.jar --nogui"
|
|
||||||
|
|
||||||
# Get screen PID
|
|
||||||
[ "$DEBUG" = "true" ] && screen -ls
|
|
||||||
SCREEN_PID="$(
|
|
||||||
screen -ls | grep -oE '[0-9]+\.minecraft' | cut -d. -f1
|
|
||||||
)"
|
|
||||||
|
|
||||||
# Check screen PID
|
|
||||||
if ! kill -0 "$SCREEN_PID" 2>/dev/null; then
|
|
||||||
echo "[ERROR] Cannot find Minecraft screen (PID: \"$SCREEN_PID\")"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Output logs to stdout (touch in case slow to create)
|
|
||||||
touch screenlog.0
|
|
||||||
tail -f screenlog.0 &
|
|
||||||
TAIL_PID="$!"
|
|
||||||
|
|
||||||
# Wait for screen to exit
|
|
||||||
strace -e exit -e signal=none -p "$SCREEN_PID" 2>/dev/null &
|
|
||||||
STRACE_PID="$!"
|
|
||||||
[ "$DEBUG" = "true" ] && ps aux
|
|
||||||
wait "$STRACE_PID"
|
|
||||||
|
2
scratch/.gitignore
vendored
Normal file
2
scratch/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
Loading…
Reference in New Issue
Block a user