├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── dailybuild.yml ├── Dockerfile ├── README.md ├── banned.cfg ├── docker-compose.yml ├── necesse-wrapper.js └── server.cfg /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | # Controls when the workflow will run 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - 'build-13035760' 9 | release: 10 | types: 11 | - created 12 | 13 | 14 | jobs: 15 | build-matrix: 16 | strategy: 17 | matrix: 18 | platform: [arm64,amd64] 19 | 20 | runs-on: ubuntu-latest 21 | steps: 22 | # Get the repository's code 23 | - name: Checkout 24 | uses: actions/checkout@v2 25 | 26 | # https://github.com/docker/setup-buildx-action 27 | - name: Set up Docker Buildx 28 | id: buildx 29 | uses: docker/setup-buildx-action@v2 30 | 31 | - name: Login to Docker Hub 32 | if: github.event_name != 'pull_request' 33 | uses: docker/login-action@v2 34 | with: 35 | username: ${{ secrets.DOCKERHUB_USERNAME }} 36 | password: ${{ secrets.DOCKERHUB_TOKEN }} 37 | 38 | - name: Fetch necesse server 39 | run: | 40 | wget https://necessegame.com/wp-content/uploads/2023/12/necesse-server-linux64-0-23-1-13035760.zip 41 | unzip necesse-server-linux64-0-23-1-13035760.zip 42 | mv necesse-server-0-23-1-13035760 necesse-server 43 | 44 | - name: Fetch jre and replace runtime for arm64 45 | if: matrix.platform == 'arm64' 46 | run: | 47 | rm -r necesse-server/jre/* 48 | wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8.1%2B1/OpenJDK17U-jre_aarch64_linux_hotspot_17.0.8.1_1.tar.gz 49 | tar -xf OpenJDK17U-jre_aarch64_linux_hotspot_17.0.8.1_1.tar.gz 50 | cp -r jdk-17.0.8.1+1-jre/* necesse-server/jre/ 51 | 52 | - name: Build and push 53 | uses: docker/build-push-action@v2 54 | with: 55 | context: . 56 | build-args: | 57 | JAVAPLATFORM=linux/${{ matrix.platform }} 58 | platforms: linux/${{ matrix.platform }} 59 | push: ${{ github.event_name != 'pull_request' }} 60 | tags: karyeet/necesse-server-docker:${{ matrix.platform }}-${{ github.ref_name }} 61 | cache-from: type=gha 62 | cache-to: type=gha,mode=max 63 | 64 | build-docker-multiarch: 65 | runs-on: ubuntu-latest 66 | needs: [build-matrix] 67 | steps: 68 | - name: Login to Docker Hub 69 | if: github.event_name != 'pull_request' 70 | uses: docker/login-action@v1 71 | with: 72 | username: ${{ secrets.DOCKERHUB_USERNAME }} 73 | password: ${{ secrets.DOCKERHUB_TOKEN }} 74 | - name: Create and push multiarch image 75 | run: | 76 | docker buildx imagetools create -t karyeet/necesse-server-docker:${GITHUB_REF_NAME} \ 77 | karyeet/necesse-server-docker:arm64-${GITHUB_REF_NAME} \ 78 | karyeet/necesse-server-docker:amd64-${GITHUB_REF_NAME} 79 | - name: Push to latest 80 | if: github.event_name == 'release' 81 | run: | 82 | echo "Pushing to latest tag" 83 | docker buildx imagetools create -t karyeet/necesse-server-docker:latest \ 84 | karyeet/necesse-server-docker:arm64-${GITHUB_REF_NAME} \ 85 | karyeet/necesse-server-docker:amd64-${GITHUB_REF_NAME} 86 | -------------------------------------------------------------------------------- /.github/workflows/dailybuild.yml: -------------------------------------------------------------------------------- 1 | name: Build-daily 2 | 3 | # Controls when the workflow will run 4 | on: 5 | schedule: 6 | - cron: 0 0 * * * 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - 'daily-build' 11 | - 'main' 12 | 13 | 14 | jobs: 15 | build-matrix: 16 | strategy: 17 | matrix: 18 | platform: [arm64,amd64] 19 | 20 | runs-on: ubuntu-latest 21 | steps: 22 | # Get the repository's code 23 | - name: Checkout 24 | uses: actions/checkout@v2 25 | 26 | # https://github.com/docker/setup-buildx-action 27 | - name: Set up Docker Buildx 28 | id: buildx 29 | uses: docker/setup-buildx-action@v2 30 | 31 | - name: Login to Docker Hub 32 | if: github.event_name != 'pull_request' 33 | uses: docker/login-action@v2 34 | with: 35 | username: ${{ secrets.DOCKERHUB_USERNAME }} 36 | password: ${{ secrets.DOCKERHUB_TOKEN }} 37 | 38 | 39 | - name: Scrape latest necesse server download url, download, unzip, and rename 40 | run: | 41 | NURL=$(curl -s https://necessegame.com/server/ | grep -Eo 'content\/server\/[0-9]?[0-9]-[0-9]?[0-9]-[0-9]?[0-9]-[0-9]+\/necesse-server-linux64-[0-9]?[0-9]-[0-9]?[0-9]-[0-9]?[0-9]-[0-9]+.zip' | echo "$(echo 'https://necessegame.com/')$(head -n 1)") 42 | NBuildNum=$(echo $NURL | grep -Eo '\-[0-9]+\.' | grep -Eo '[0-9]+') 43 | echo "NBuildNum=$NBuildNum" >> $GITHUB_ENV 44 | wget $NURL 45 | unzip necesse-server-linux64-* 46 | rm necesse-server-linux64-* 47 | mv necesse-server-* necesse-server 48 | 49 | - name: Fetch jre and replace runtime for arm64 50 | if: matrix.platform == 'arm64' 51 | run: | 52 | rm -r necesse-server/jre/* 53 | wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8.1%2B1/OpenJDK17U-jre_aarch64_linux_hotspot_17.0.8.1_1.tar.gz 54 | tar -xf OpenJDK17U-jre_aarch64_linux_hotspot_17.0.8.1_1.tar.gz 55 | cp -r jdk-17.0.8.1+1-jre/* necesse-server/jre/ 56 | 57 | - name: Build and push 58 | uses: docker/build-push-action@v2 59 | with: 60 | context: . 61 | build-args: | 62 | JAVAPLATFORM=linux/${{ matrix.platform }} 63 | platforms: linux/${{ matrix.platform }} 64 | push: ${{ github.event_name != 'pull_request' }} 65 | tags: karyeet/necesse-server-docker:${{ matrix.platform }}-${{ env.NBuildNum }} 66 | cache-from: type=gha 67 | cache-to: type=gha,mode=max 68 | 69 | build-docker-multiarch: 70 | runs-on: ubuntu-latest 71 | needs: [build-matrix] 72 | steps: 73 | - name: Login to Docker Hub 74 | if: github.event_name != 'pull_request' 75 | uses: docker/login-action@v1 76 | with: 77 | username: ${{ secrets.DOCKERHUB_USERNAME }} 78 | password: ${{ secrets.DOCKERHUB_TOKEN }} 79 | - name: Create and push multiarch image 80 | run: | 81 | NURL=$(curl -s https://necessegame.com/server/ | grep -Eo 'content\/server\/[0-9]?[0-9]-[0-9]?[0-9]-[0-9]?[0-9]-[0-9]+\/necesse-server-linux64-[0-9]?[0-9]-[0-9]?[0-9]-[0-9]?[0-9]-[0-9]+.zip' | echo "$(echo 'https://necessegame.com/')$(head -n 1)") 82 | NBuildNum=$(echo $NURL | grep -Eo '\-[0-9]+\.' | grep -Eo '[0-9]+') 83 | docker buildx imagetools create -t karyeet/necesse-server-docker:$NBuildNum -t karyeet/necesse-server-docker:latest \ 84 | karyeet/necesse-server-docker:arm64-$NBuildNum \ 85 | karyeet/necesse-server-docker:amd64-$NBuildNum 86 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG JAVAPLATFORM 2 | 3 | FROM --platform=$JAVAPLATFORM node:21-bullseye-slim 4 | 5 | LABEL maintainer="https://github.com/karyeet" 6 | 7 | COPY ./necesse-server /necesse-server 8 | 9 | COPY ./necesse-wrapper.js /necesse-server 10 | 11 | RUN chmod -R +x /necesse-server 12 | 13 | ENTRYPOINT ["node", "/necesse-server/necesse-wrapper.js"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # necesse-server-docker 2 | 3 | ## Features: 4 | - amd64, arm64/aarch64 support for the necesse server 5 | - Gracefully shutdown necesse server and save when container is stopped 6 | - Send commands to the server 7 | - Get logs from the server 8 | - Multiple ways to set the config 9 | - [NEW] Daily image builds to latest necesse server version 10 | >To update, just pull the latest image (sudo docker compose pull) and recreate your container. 11 | - Complete [docker-compose.yml](https://github.com/karyeet/Necesse-Server-Docker/blob/main/docker-compose.yml) file provided 12 | 13 | Multiarch image: [karyeet/necesse-server-docker:latest](https://hub.docker.com/r/karyeet/necesse-server-docker) 14 | 15 | ## Install 16 | 17 | 1. `git clone https://github.com/karyeet/necesse-server-docker ` 18 | 19 | ## Start 20 | 1. `cd ./necesse-server-docker` 21 | 2. `sudo docker compose up -d` 22 | 23 | ## Sending Commands and Getting Logs 24 | 25 | ### To send commands: 26 | `sudo docker attach necesse_server` 27 | To detatch, hit ctrl+p then ctrl+q 28 | 29 | ### To get logs: 30 | Any of these commands will work: 31 | 1. `sudo docker compose logs` inside the `necesse-server-docker` folder 32 | 2. `sudo docker logs necesse_server` 33 | 3. To follow logs (view live): `docker logs --follow necesse_server` 34 | 35 | 36 | ## Configure 37 | 38 | - You may set the environment variables in the docker-compose.yml file. 39 | - You may also remove an environment variable from the docker-compose.yml file and edit it directly in the server.cfg 40 | - By default, the server uses port 14159/udp. 41 | - To change the port to, for example, port 1738 you must change the `14159:14159/udp` in the docker-compose.yml to `1738:14159/udp`. 42 | - Explanations to all the configuration options can be found in the server.cfg file. 43 | 44 | ## Saving 45 | The server will save worlds to the `saves` folder. 46 | Logs are in the `logs` folder. 47 | 48 | ## Mods 49 | 1. Run the server once to create file structure: 50 | 1. `sudo docker compose up -d` 51 | 2. count to 3 52 | 3. `sudo docker compose down` 53 | 2. Copy mod .jar into the mod folder created in the `necesse-server-docker` folder. 54 | 3. Start server: `sudo docker compose up -d` 55 | 56 | 57 | ## Importing worlds 58 | You can import a world by copying the .zip into ./saves folder and changing `world=myNewWorld` in the docker-compose.yml to the name of the .zip, whichout the .zip part. 59 | 60 | Ex: If the file is called coolestNecesseWorld.zip, then change `world=myNewWorld` to `world=coolestNecesseWorld`. 61 | 62 | I recommend periodically making a backup of your world outside of that directory. 63 | 64 | 65 | -------------------------------------------------------------------------------- /banned.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karyeet/Necesse-Server-Docker/a6e7013dd1a137879828c4021c748c8b99f4426d/banned.cfg -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | # the port used in ports must match the port specified in env 4 | # changes to environment variables will take precendent over the server.cfg and must be defined 5 | # a description of the environemnt variables is in the README.md and server.cfg 6 | 7 | services: 8 | necesse-server: 9 | container_name: necesse_server 10 | image: karyeet/necesse-server-docker:latest 11 | stdin_open: true 12 | tty: true 13 | ports: 14 | - 14159:14159/udp 15 | volumes: 16 | - ./server.cfg:/root/.config/Necesse/cfg/server.cfg 17 | - ./banned.cfg:/root/.config/Necesse/cfg/banned.cfg 18 | - ./saves:/root/.config/Necesse/saves 19 | - ./logs:/root/.config/Necesse/logs 20 | - ./mods:/root/.config/Necesse/mods 21 | environment: 22 | - world=myNewWorld 23 | - port=14159 24 | - slots=10 25 | - password= 26 | - maxClientLatencySeconds=30 27 | - pauseWhenEmpty=true 28 | - giveClientsPower=true 29 | - logging=true 30 | - language=en 31 | - unloadLevelsCooldown=30 32 | - worldBorderSize=-1 33 | - droppedItemsLifeMinutes=0 34 | - unloadSettlements = false 35 | - maxSettlementsPerPlayer = -1 36 | - maxSettlersPerSettlement = -1 37 | - jobSearchRange=100 38 | - zipSaves=true 39 | - MOTD= -------------------------------------------------------------------------------- /necesse-wrapper.js: -------------------------------------------------------------------------------- 1 | const {spawn} = require("child_process"); 2 | 3 | const fs = require('fs'); 4 | 5 | function editConfigFile(filePath, settingName, newValue) { 6 | fs.readFile(filePath, 'utf8', (err, data) => { 7 | if (err) { 8 | console.error('Error reading file:', err); 9 | return; 10 | } 11 | 12 | // Define the regular expression pattern to match the setting and its value 13 | const pattern = new RegExp(`(${settingName}\\s*=\\s*)([^,\\/\\s]*)`, 'g'); 14 | 15 | // Replace the old value with the new value 16 | const newData = data.replace(pattern, `$1${newValue}`); 17 | 18 | // Write the modified content back to the file 19 | fs.writeFile(filePath, newData, 'utf8', (err) => { 20 | if (err) { 21 | console.error('Error writing to file:', err); 22 | return; 23 | } 24 | console.log(`Setting '${settingName}' updated to '${newValue}' in ${filePath}`); 25 | }); 26 | }); 27 | } 28 | 29 | const configFilePath = "/root/.config/Necesse/cfg/server.cfg" 30 | editConfigFile(configFilePath, "port", process.env.port) 31 | editConfigFile(configFilePath, "slots", process.env.slots) 32 | editConfigFile(configFilePath, "password", process.env.password) 33 | editConfigFile(configFilePath, "maxClientLatencySeconds", process.env.maxClientLatencySeconds) 34 | editConfigFile(configFilePath, "pauseWhenEmpty", process.env.pauseWhenEmpty) 35 | editConfigFile(configFilePath, "giveClientsPower", process.env.giveClientsPower) 36 | editConfigFile(configFilePath, "logging", process.env.logging) 37 | editConfigFile(configFilePath, "language", process.env.language) 38 | editConfigFile(configFilePath, "unloadLevelsCooldown", process.env.unloadLevelsCooldown) 39 | editConfigFile(configFilePath, "worldBorderSize", process.env.worldBorderSize) 40 | editConfigFile(configFilePath, "droppedItemsLifeMinutes", process.env.droppedItemsLifeMinutes) 41 | editConfigFile(configFilePath, "unloadSettlements", process.env.unloadSettlements) 42 | editConfigFile(configFilePath, "maxSettlementsPerPlayer", process.env.maxSettlementsPerPlayer) 43 | editConfigFile(configFilePath, "maxSettlersPerSettlement", process.env.maxSettlersPerSettlement) 44 | editConfigFile(configFilePath, "jobSearchRange", process.env.jobSearchRange) 45 | editConfigFile(configFilePath, "zipSaves", process.env.zipSaves) 46 | editConfigFile(configFilePath, "MOTD", process.env.MOTD) 47 | 48 | // start server 49 | const necesse_server = spawn("/necesse-server/jre/bin/java", ["-jar", "/necesse-server/Server.jar", "-nogui", "-world", process.env.world], { detached: true }) 50 | 51 | // set encoding 52 | necesse_server.stdout.setEncoding('utf8'); 53 | 54 | 55 | necesse_server.on('close', (code) => { 56 | console.log(`Child process exited with code ${code}`); 57 | }); 58 | 59 | // set flag when server is up 60 | let isServerUp = false 61 | necesse_server.stdout.on("data", (message) => { 62 | if (message.match("Type help for list of commands.")) { 63 | isServerUp = true 64 | console.log("Detected Necesse server is up, will attempt to save on SIGTERM and SIGINT.") 65 | } 66 | if (message.match("Exiting in 2 seconds...")) { 67 | isServerUp = false 68 | console.log("Server gracefully shutdown.") 69 | } 70 | }) 71 | // pipe stdin to necesse 72 | process.stdin.pipe(necesse_server.stdin) 73 | // pipe necesse stdout to process 74 | necesse_server.stdout.pipe(process.stdout) 75 | // pipe necesse stderr to process 76 | necesse_server.stderr.pipe(process.stderr) 77 | 78 | 79 | function gracefulShutdown() { 80 | if (isServerUp) { 81 | console.log("Attempting to save and exit necesse...") 82 | necesse_server.stdin.write("exit\n"); 83 | } 84 | } 85 | 86 | // catch terminations 87 | process.once('SIGTERM', () => { 88 | console.info('SIGTERM signal received.'); 89 | gracefulShutdown() 90 | }); 91 | 92 | process.once('SIGINT', () => { 93 | console.info('SIGINT signal received.'); 94 | gracefulShutdown() 95 | }); 96 | 97 | -------------------------------------------------------------------------------- /server.cfg: -------------------------------------------------------------------------------- 1 | SERVER = { 2 | port = 14159, // [0 - 65535] Server default port 3 | slots = 10, // [1 - 250] Server default slots 4 | password = , // Leave blank for no password 5 | maxClientLatencySeconds = 30, 6 | pauseWhenEmpty = true, 7 | giveClientsPower = true, // If true, clients will have much more power over what hits them, their position etc 8 | logging = true, // If true, will create log files for each server start 9 | language = en, 10 | unloadLevelsCooldown = 30, // The number of seconds a level will stay loaded after the last player has left it 11 | worldBorderSize = -1, // The max distance from spawn players can travel. -1 for no border 12 | droppedItemsLifeMinutes = 0, // Minutes that dropped items will stay in the world. 0 or less for indefinite 13 | unloadSettlements = false, // If the server should unload player settlements or keep them loaded 14 | maxSettlementsPerPlayer = -1, // The maximum amount of settlements per player. -1 or less means infinite 15 | maxSettlersPerSettlement = -1, // The maximum amount of settlers per settlement. -1 or less means infinite 16 | jobSearchRange = 100, // The tile search range of settler jobs 17 | zipSaves = true, // If true, will create new saves uncompressed 18 | MOTD = // Message of the day 19 | } --------------------------------------------------------------------------------