diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..033b1ec7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM gradle:jdk11-alpine as builder + +WORKDIR /app +COPY ./build.gradle /app/build.gradle +COPY ./src /app/src +COPY ./version.txt /app/version.txt +COPY ./www /app/www + +RUN gradle jarCli && \ + gradle copyWww + +FROM adoptopenjdk/openjdk11:alpine-jre + +COPY --from=builder /app/build/libs/MinecraftStatsCLI.jar /app/MinecraftStatsCLI.jar +# copy from raw_www to www in entrypoint.sh +COPY --from=builder /app/build/www /app/raw_www + +COPY ./stats /app/raw_stats +COPY ./entrypoint.sh /app/entrypoint.sh + +ENTRYPOINT [ "/app/entrypoint.sh" ] + diff --git a/README.md b/README.md index 6498e117..f37f1c4d 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,18 @@ When using the CLI, the data for the web frontend needs to be updated by executi java -jar MinecraftStatsCLI.jar config.json ``` +#### CLI in Docker + +Instead of running the CLI on the host you can place it neatly separated into multiple container using Docker. +The [Dockerfile](./Dockerfile) builds the image [chrisbesch/minecraft_stats](https://hub.docker.com/r/chrisbesch/minecraft_stats). +Here is an example deployment using Docker Compose: [example_docker_compose](./example_docker_compose) +Run it with `docker compose up` and you should have a Minecraft Server (on localhost:25565), MinecraftStats with the accompanying Cron job and web server running on [http://localhost:80](http://localhost:80). + +If you want to [add custom awards](#custom-awards), run the docker compose project without your custom awards first. +The `./stats` and `./web_server` dirs will be populated. +Now you can add your custom awards. +To update the container, delete these dirs, update and start the container and add your custom awards again. + ##### Automatic Updates The CLI does not include any means for automatic updates - you need to take care of this yourself. The following lists some possibilities you might have. @@ -236,7 +248,7 @@ As a hint, if you wish to create a custom award, it is a good idea to find an al Readers define how an award score is determined from a player's statistics JSON. There are different types of readers that can be selected using the **`$type`** field. The other fields that are available depend on the selected type. The following is a listing of the available reader types, each with an example award (ID) for reference. * **`int`**: Reads a single integer from the nested object given by the `path` array. The final entry in `path` is the name of the field to read. Example: `jump`. -* **`match-sum`**: Reads multiple integers from the nested bject given by `path`. The fields that are read are given in the `patterns` array, which may contain [regular expressions](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html). Example: `break_tools`. +* **`match-sum`**: Reads multiple integers from the nested object given by `path`. The fields that are read are given in the `patterns` array, which may contain [regular expressions](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html). Example: `break_tools`. * **`set-count`**: Counts the number of distinct entries in the nested object or array given by `path`. Example: `biomes`. #### Advancements @@ -269,6 +281,7 @@ Here is some help troubleshooting common issues. * Any file not found (code 404) errors may hint that data has not been generated. Make sure there is a `data` folder in your webserver's document root. If not, chances are you did not run any update, or the data was written to a wrong location (double-check the configuration). * If there is anything logged about a *CORS Policy*, chances are you are trying to open `index.html` from your filesystem directly. As a security measure, most browsers do not support loading JavaScript from the filesystem, hence the frontend will not work. Please use a local webserver instead. * In case you have configured your webserver to compress files before transfer to the client, this may cause it to compress the `.json.gz` files of *MinecraftStats* and confuse the frontend, rendering it unable to properly decompress them. Please make sure that there is an exception for `*.gz` files – they are already compressed! + * If you're getting an error like this in the browser console `Uncaught incorrect header check` there might not be any active players. Try setting `minPlaytime` to `0` and `inactiveDays` to something large. ## History diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000..d0949449 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# This is the entrypoint for the Docker installation. +# It waits patiently for a SIGHUP from the DockerCron container. +# Once it receives one it performs an update. + +mkdir -p /app/www +# only copy the localization files if /app/www is empty +if [ -z "$(ls -A /app/www)" ]; then + echo 'copy static html files into /app/www' + cp -fr /app/raw_www/* /app/www +else + echo '/app/www is already filled; delete that dir to repopulate' +fi + +echo 'copy stat files into /app/stats' +mkdir -p /app/stats +cp -fr /app/raw_stats/* /app/stats + +echo 'running initial update after boot' +java -jar /app/MinecraftStatsCLI.jar /app/config.json || true + +# make sure that you don't `set -e` because that causes the trap to exit the script +# await SIGHUP and then run the actual job +trap 'java -jar /app/MinecraftStatsCLI.jar /app/config.json' HUP +while :; do + sleep 10 & wait ${!} +done diff --git a/example_docker_compose/.gitignore b/example_docker_compose/.gitignore new file mode 100644 index 00000000..d5cb543c --- /dev/null +++ b/example_docker_compose/.gitignore @@ -0,0 +1,3 @@ +data +web_server +stats diff --git a/example_docker_compose/config.json b/example_docker_compose/config.json new file mode 100644 index 00000000..95a938da --- /dev/null +++ b/example_docker_compose/config.json @@ -0,0 +1,36 @@ +{ + "server": { + "sources": [ + { + "path": "/app/serverfiles", + "worldName": "world" + } + ] + }, + "data": { + "documentRoot": "/app/www", + "eventsDir": "/app/events", + "statsDir": "/app/stats" + }, + "crown": { + "silver": 2, + "gold": 4, + "bronze": 1 + }, + "client": { + "defaultLanguage": "en", + "playersPerPage": 100, + "playerCacheUUIDPrefix": 2, + "serverName": null, + "showLastOnline": true + }, + "players": { + "inactiveDays": 7, + "updateInactive": false, + "profileUpdateInterval": 3, + "minPlaytime": 0, + "excludeBanned": true, + "excludeOps": false, + "excludeUUIDs": [] + } +} diff --git a/example_docker_compose/docker-compose.yaml b/example_docker_compose/docker-compose.yaml new file mode 100644 index 00000000..8aa2291d --- /dev/null +++ b/example_docker_compose/docker-compose.yaml @@ -0,0 +1,53 @@ +version: "3.4" + +services: + # You can use any minecraft server—if you like even running without Docker on the host. + MinecraftJava: + image: ghcr.io/gameservermanagers/gameserver:mc + environment: + - TZ=Europe/Berlin + volumes: + # expose the serverfiles on the host machine + - ./data:/data + ports: + - 25565:25565 + restart: unless-stopped + + MinecraftStats: + image: chrisbesch/minecraft_stats + environment: + - TZ=Europe/Berlin + # configures the DockerCron container to send a SIGHUP to this container every five minutes + - "CRON_TIME=*/5 * * * *" + volumes: + # make sure this points to the location of the serverfiles on the host + - ./data/serverfiles:/app/serverfiles:ro + # You can configure the application here. + # But remember that all paths (like for documentRoot) are inside the Docker container. + # So you probably don't have to touch any of the paths + - ./config.json:/app/config.json:ro + # this is where the static web files end up + # if you want any custom stats, put them here after running the docker compose setup once + - ./web_server:/app/www + # this is only needed if you want custom stats + # if you want any custom stats, put them here after running the docker compose setup once + - ./stats:/app/stats + restart: unless-stopped + + # You can use any webserver—if you like even running on the host without Docker. + WebServer: + image: nginx + volumes: + - ./web_server:/usr/share/nginx/html:ro + ports: + - 80:80 + restart: unless-stopped + + # This is used to send a SIGHUP to the MinecraftStats container in regular intervals. + DockerCron: + image: chrisbesch/docker_cron + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:rw" + environment: + - TZ=Europe/Berlin + restart: unless-stopped