release: yet another Nix Minecraft Module
This commit is contained in:
parent
9fdb947c2a
commit
839bb7de43
13 changed files with 1621 additions and 1 deletions
119
README.md
119
README.md
|
|
@ -1 +1,118 @@
|
||||||
# Nyx-Minecraft
|
# Yet another Nix Minecraft module
|
||||||
|
|
||||||
|
This NixOS module extends [Infinidoge/nix-minecraft](https://github.com/Infinidoge/nix-minecraft) with additional features for managing multiple Minecraft servers declaratively.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Declarative configuration of multiple Minecraft servers
|
||||||
|
|
||||||
|
- Control over memory, operators, whitelist, symlinks, and server properties
|
||||||
|
|
||||||
|
- Automatic **systemd services** and **timers** for scheduled jobs
|
||||||
|
|
||||||
|
- Prebuilt helper scripts for administration:
|
||||||
|
|
||||||
|
- `rcon`
|
||||||
|
- `query`
|
||||||
|
- `backup`
|
||||||
|
- `say`
|
||||||
|
- `backup-routine`
|
||||||
|
|
||||||
|
- Support for:
|
||||||
|
|
||||||
|
- Backups
|
||||||
|
- Logging
|
||||||
|
- Scheduled maintenance tasks
|
||||||
|
|
||||||
|
## Helper Scripts
|
||||||
|
|
||||||
|
For each server defined, helper scripts are generated into your system `$PATH`.
|
||||||
|
They follow the naming convention:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
minecraft-<SERVERNAME>-<TASK>
|
||||||
|
# Example:
|
||||||
|
minecraft-myserver-say "yellow" "hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can inspect the generated scripts under: `minecraft/Scripts`
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nyx-minecraft.service = {
|
||||||
|
enable = true; # Enable the enhanced servers (boolean)
|
||||||
|
eula = true; # I can't accept this for you
|
||||||
|
user = "minecraft"; # System user that owns/runs servers - DON'T CHANGE THIS
|
||||||
|
group = "minecraft"; # System group that owns/runs servers - DON'T CHANGE THIS
|
||||||
|
dataDir = "/srv/minecraft"; # Directory for Minecraft server data
|
||||||
|
|
||||||
|
servers = {
|
||||||
|
myserver = {
|
||||||
|
enable = true;
|
||||||
|
memory.min = "2G"; # JVM minimum memory
|
||||||
|
memory.max = "4G"; # JVM maximum memory
|
||||||
|
|
||||||
|
# This will be directly exposed to [Nix-Minecraft](https://github.com/Infinidoge/nix-minecraft/tree/master?tab=readme-ov-file#packages)
|
||||||
|
# Check their documentation for available options.
|
||||||
|
package = pkgs.minecraftServers.vanilla-1_20_4;
|
||||||
|
|
||||||
|
autoStart = true; # Start server on boot
|
||||||
|
|
||||||
|
whitelist = { # Declarative whitelist (UUIDs per user)
|
||||||
|
alice = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
|
||||||
|
bob = "ffffffff-1111-2222-3333-444444444444";
|
||||||
|
};
|
||||||
|
|
||||||
|
operators = { # Declarative operator list
|
||||||
|
alice = {
|
||||||
|
uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
|
||||||
|
level = 4;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
properties = { # Declarative `server.properties`
|
||||||
|
serverPort = 25565;
|
||||||
|
motd = "Welcome to my NixOS server!";
|
||||||
|
maxPlayers = 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
schedules.backup = { # Systemd timer + service job
|
||||||
|
timer = "daily";
|
||||||
|
code = "minecraft-myserver-backup";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
For more details, refer to the [official nix-minecraft documentation](https://github.com/Infinidoge/nix-minecraft), since most arguments are passed through.
|
||||||
|
|
||||||
|
### Server Options
|
||||||
|
|
||||||
|
- **memory.min / memory.max** — JVM memory allocation (e.g. `"2G"`)
|
||||||
|
- **package** — Minecraft server package (default: `pkgs.minecraft-server`)
|
||||||
|
- **autoStart** — Start on boot (`true` / `false`)
|
||||||
|
- **whitelist** — Declarative whitelist keyed by username with UUID
|
||||||
|
- **operators** — Operator list with permission levels (`0–4`)
|
||||||
|
- **symlinks** — Files/packages symlinked into server data directory
|
||||||
|
- **properties** — Declarative `server.properties` values (ports, motd, difficulty, etc.)
|
||||||
|
- **schedules** — Declarative scheduled jobs (with systemd timers and services)
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
See the examples in:
|
||||||
|
|
||||||
|
- `./example/full`
|
||||||
|
- `./example/small`
|
||||||
|
|
||||||
|
## Warnings
|
||||||
|
|
||||||
|
- **Use at your own risk** — verify backups and test schedules before relying on them
|
||||||
|
- Pin a known working version for stable usage
|
||||||
|
- Perform **manual backups before updates**
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
I am happy to help with issues and welcome pull requests for improvements.
|
||||||
|
Since I use this module personally, you can expect frequent updates.
|
||||||
|
|
|
||||||
112
example/full/configuration.nix
Normal file
112
example/full/configuration.nix
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
|
||||||
|
# ... your imports
|
||||||
|
inputs.nyx-minecraft.nixosModules.minecraft-servers
|
||||||
|
];
|
||||||
|
|
||||||
|
nyx-minecraft.service = {
|
||||||
|
enable = true;
|
||||||
|
eula = true;
|
||||||
|
# user # don't change this
|
||||||
|
# group # don't change this
|
||||||
|
dataDir = "srv/minecraft";
|
||||||
|
|
||||||
|
servers = {
|
||||||
|
testingServer = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
memory = {
|
||||||
|
min = "2G";
|
||||||
|
max = "4G";
|
||||||
|
};
|
||||||
|
|
||||||
|
package = pkgs.minecraftServers.vanilla-1_20_4;
|
||||||
|
autoStart = true;
|
||||||
|
|
||||||
|
whitelist = {
|
||||||
|
player1 = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
|
||||||
|
player2 = "ffffffff-1111-2222-3333-444444444444";
|
||||||
|
};
|
||||||
|
|
||||||
|
operators = {
|
||||||
|
admin = {
|
||||||
|
uuid = "99999999-aaaa-bbbb-cccc-dddddddddddd";
|
||||||
|
level = 4;
|
||||||
|
bypassesPlayerLimit = true;
|
||||||
|
};
|
||||||
|
mod = {
|
||||||
|
uuid = "88888888-aaaa-bbbb-cccc-eeeeeeeeeeee";
|
||||||
|
level = 2;
|
||||||
|
bypassesPlayerLimit = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
properties = {
|
||||||
|
serverPort = 25565;
|
||||||
|
difficulty = 2;
|
||||||
|
gamemode = 0;
|
||||||
|
maxPlayers = 20;
|
||||||
|
motd = "Welcome to the testingServer!";
|
||||||
|
rconPassword = "superSecret123";
|
||||||
|
hardcore = false;
|
||||||
|
levelSeed = "8675309";
|
||||||
|
};
|
||||||
|
|
||||||
|
schedules = {
|
||||||
|
# note schedule can be enabled without the server being enabled
|
||||||
|
backup-hourly = {
|
||||||
|
enable = true;
|
||||||
|
# this is using systemD timers check the official Documentation
|
||||||
|
timer = "hourly";
|
||||||
|
code = ''
|
||||||
|
minecraft-testingServer-backup-routine \
|
||||||
|
--sleep 16 \
|
||||||
|
--destination /srv/minecraft/backups/testingServer/hourly \
|
||||||
|
--pure
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
backup-daily = {
|
||||||
|
enable = true;
|
||||||
|
timer = "daily";
|
||||||
|
code = ''
|
||||||
|
minecraft-testingServer-backup-routine \
|
||||||
|
--sleep 60 \
|
||||||
|
--destination /srv/minecraft/backups/testingServer/daily \
|
||||||
|
--format zip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
backup-weekly = {
|
||||||
|
enable = true;
|
||||||
|
timer = "weekly";
|
||||||
|
code = ''
|
||||||
|
minecraft-testingServer-backup-routine \
|
||||||
|
--sleep 600 \
|
||||||
|
--full \
|
||||||
|
--destination /srv/minecraft/backups/testingServer/weekly \
|
||||||
|
--format zip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
backup-monthly = {
|
||||||
|
enable = true;
|
||||||
|
timer = "monthly";
|
||||||
|
code = ''
|
||||||
|
minecraft-testingServer-backup-routine \
|
||||||
|
--sleep 960 \
|
||||||
|
--full \
|
||||||
|
--destination /srv/minecraft/backups/testingServer/monthly \
|
||||||
|
--format zip
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
34
example/full/flake.nix
Normal file
34
example/full/flake.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
description = "EXAMPLE - Flake";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
|
# your own imports
|
||||||
|
nyx-minecraft.url = "github:Peritia-System/Nyx-Minecraft";
|
||||||
|
nyx-minecraft.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs @ {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
nix-minecraft,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
nixosConfigurations = {
|
||||||
|
yourSystem = nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
|
||||||
|
specialArgs = {
|
||||||
|
inherit inputs self;
|
||||||
|
host = "yourSystem";
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
nixos95.nixosModules.default
|
||||||
|
|
||||||
|
./configuration.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
58
example/small/configuration.nix
Normal file
58
example/small/configuration.nix
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
|
||||||
|
# ... your imports
|
||||||
|
inputs.nyx-minecraft.nixosModules.minecraft-servers
|
||||||
|
];
|
||||||
|
|
||||||
|
nyx-minecraft.service = {
|
||||||
|
enable = true;
|
||||||
|
eula = true;
|
||||||
|
# user # don't change this
|
||||||
|
# group # don't change this
|
||||||
|
dataDir = "srv/minecraft";
|
||||||
|
|
||||||
|
servers = {
|
||||||
|
testingServer = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
memory = {
|
||||||
|
min = "2G";
|
||||||
|
max = "4G";
|
||||||
|
};
|
||||||
|
|
||||||
|
package = pkgs.minecraftServers.vanilla-1_20_4;
|
||||||
|
|
||||||
|
# leaving whitelis out just deactivates whitelist
|
||||||
|
# leaving Operators out will just not set any operator
|
||||||
|
|
||||||
|
# Leaving out a property will just set the default
|
||||||
|
properties = {
|
||||||
|
serverPort = 25565;
|
||||||
|
# note you don't need to set query or rcon port
|
||||||
|
# since they will be set 200 and 100 above the Serverport
|
||||||
|
};
|
||||||
|
# you can leave them out than but here a simple example
|
||||||
|
schedules = {
|
||||||
|
# Hourly world-only, pure rsync, no restart
|
||||||
|
greeting-hourly = {
|
||||||
|
enable = true;
|
||||||
|
# note schedule can be enabled without the server being enabled
|
||||||
|
timer = "hourly";
|
||||||
|
code = ''
|
||||||
|
minecraft-testingServer-say "yellow" "hello"
|
||||||
|
# now once an hour it will greet everyone in the server
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
34
example/small/flake.nix
Normal file
34
example/small/flake.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
description = "EXAMPLE - Flake";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
|
# your own imports
|
||||||
|
nyx-minecraft.url = "github:Peritia-System/Nyx-Minecraft";
|
||||||
|
nyx-minecraft.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs @ {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
nix-minecraft,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
nixosConfigurations = {
|
||||||
|
yourSystem = nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
|
||||||
|
specialArgs = {
|
||||||
|
inherit inputs self;
|
||||||
|
host = "yourSystem";
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
nixos95.nixosModules.default
|
||||||
|
|
||||||
|
./configuration.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
23
flake.nix
Normal file
23
flake.nix
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
description = "Nyx-Modules";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
nix-minecraft.url = "github:Infinidoge/nix-minecraft";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, nix-minecraft, ... }: {
|
||||||
|
nixosModules.minecraft-servers = {
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
imports = [
|
||||||
|
./minecraft
|
||||||
|
nix-minecraft.nixosModules.minecraft-servers
|
||||||
|
];
|
||||||
|
nixpkgs.overlays = [nix-minecraft.overlay];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
420
minecraft/Scripts/minecraft-template-backup-routine.sh
Normal file
420
minecraft/Scripts/minecraft-template-backup-routine.sh
Normal file
|
|
@ -0,0 +1,420 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Injected by Nix
|
||||||
|
RSYNC_BIN="@RSYNC_BIN@"
|
||||||
|
DATA_DIR="@DATA_DIR@"
|
||||||
|
MCSTATUS_BIN="@MCSTATUS_BIN@"
|
||||||
|
MCRCON_BIN="@MCRCON_BIN@"
|
||||||
|
AWK_BIN="@AWK_BIN@"
|
||||||
|
QUERY_PORT="@QUERY_PORT@"
|
||||||
|
RCON_PORT="@RCON_PORT@"
|
||||||
|
RCON_PASSWORD="@RCON_PASSWORD@"
|
||||||
|
SERVER_NAME="@SERVER_NAME@"
|
||||||
|
TAR_BIN="@TAR_BIN@"
|
||||||
|
ZIP_BIN="@ZIP_BIN@"
|
||||||
|
UNZIP_BIN="@UNZIP_BIN@"
|
||||||
|
GZIP_BIN="@GZIP_BIN@"
|
||||||
|
ZSTD_BIN="@ZSTD_BIN@"
|
||||||
|
PV_BIN="@PV_BIN@"
|
||||||
|
DU_BIN="@DU_BIN@"
|
||||||
|
BZIP2_BIN="@BZIP2_BIN@"
|
||||||
|
XZ_BIN="@XZ_BIN@"
|
||||||
|
|
||||||
|
# Convenience wrappers
|
||||||
|
rsync_cmd="$RSYNC_BIN"
|
||||||
|
awk_cmd="$AWK_BIN"
|
||||||
|
mcstatus_cmd="$MCSTATUS_BIN 127.0.0.1:${QUERY_PORT}"
|
||||||
|
mcrcon_cmd="$MCRCON_BIN -H 127.0.0.1 -P ${RCON_PORT} -p ${RCON_PASSWORD}"
|
||||||
|
tar_cmd="$TAR_BIN"
|
||||||
|
zip_cmd="$ZIP_BIN"
|
||||||
|
unzip_cmd="$UNZIP_BIN"
|
||||||
|
gzip_cmd="$GZIP_BIN"
|
||||||
|
zstd_cmd="$ZSTD_BIN"
|
||||||
|
pv_cmd="$PV_BIN"
|
||||||
|
du_cmd="$DU_BIN"
|
||||||
|
bzip2_cmd="$BZIP2_BIN"
|
||||||
|
xz_cmd="$XZ_BIN"
|
||||||
|
|
||||||
|
# PATH extension
|
||||||
|
# (only figured that out later if you add it here it can actually just use the bin)
|
||||||
|
# So you can easily just switch out the "*_cmd" with the "normal" name
|
||||||
|
export PATH="$(dirname "$GZIP_BIN")":"$(dirname "$ZSTD_BIN")":"$(dirname "$PV_BIN")":"$(dirname "$DU_BIN")":"$(dirname "$BZIP2_BIN")":"$(dirname "$XZ_BIN")":"$PATH"
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
REBOOT=false
|
||||||
|
SLEEP_TIME=0
|
||||||
|
FULL=false
|
||||||
|
DESTINATION="/srv/minecraft/backups/unknown"
|
||||||
|
PURE=false
|
||||||
|
FORMAT="tar"
|
||||||
|
COMPRESSION="gzip"
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 [--reboot] [--sleep <seconds>] [--full] [--destination <path>] [--pure]
|
||||||
|
[--format <tar|zip>] [--compression <gzip|bzip2|xz|zstd>]
|
||||||
|
|
||||||
|
--reboot Stop server before backup and start afterwards [DOES NOT WORK]
|
||||||
|
--sleep N Wait N seconds with countdown announcements
|
||||||
|
--full Backup entire server directory (default: world only)
|
||||||
|
--destination X Backup target directory (default: /srv/minecraft/backups/unknown)
|
||||||
|
--pure Use rsync to copy files (no compression, symlinks resolved)
|
||||||
|
--format X Archive format: tar or zip (ignored if --pure)
|
||||||
|
--compression X Compression for tar (default: gzip)
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Argument parsing
|
||||||
|
echo "[DEBUG] Parsing command-line arguments..."
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--reboot) echo "[DEBUG] Flag: --reboot"; REBOOT=true; shift ;;
|
||||||
|
--sleep) echo "[DEBUG] Flag: --sleep $2"; SLEEP_TIME="$2"; shift 2 ;;
|
||||||
|
--full) echo "[DEBUG] Flag: --full"; FULL=true; shift ;;
|
||||||
|
--destination) echo "[DEBUG] Flag: --destination $2"; DESTINATION="$2"; shift 2 ;;
|
||||||
|
--pure) echo "[DEBUG] Flag: --pure"; PURE=true; shift ;;
|
||||||
|
--format) echo "[DEBUG] Flag: --format $2"; FORMAT="$2"; shift 2 ;;
|
||||||
|
--compression) echo "[DEBUG] Flag: --compression $2"; COMPRESSION="$2"; shift 2 ;;
|
||||||
|
--help) usage ;;
|
||||||
|
*) echo "[ERROR] Unknown option: $1"; usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Restart if rebooting
|
||||||
|
if [[ "$REBOOT" == true ]]; then
|
||||||
|
echo "[DEBUG] Restarting server does not work"
|
||||||
|
echo "[DEBUG] The sudo can't be enabled due to it not being used by the same path as for the systemd"
|
||||||
|
echo "[DEBUG] and just using systemctl won't work either due to it not having the rights to stop it"
|
||||||
|
echo "[DEBUG] if you fix this pls make a PR"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Helpers
|
||||||
|
|
||||||
|
say_with_color() {
|
||||||
|
local color="$1"
|
||||||
|
shift
|
||||||
|
local message="$*"
|
||||||
|
local code
|
||||||
|
|
||||||
|
case "$color" in
|
||||||
|
black) code="§0" ;;
|
||||||
|
dark_blue) code="§1" ;;
|
||||||
|
dark_green) code="§2" ;;
|
||||||
|
dark_aqua) code="§3" ;;
|
||||||
|
dark_red) code="§4" ;;
|
||||||
|
dark_purple) code="§5" ;;
|
||||||
|
gold) code="§6" ;;
|
||||||
|
gray) code="§7" ;;
|
||||||
|
dark_gray) code="§8" ;;
|
||||||
|
blue) code="§9" ;;
|
||||||
|
green) code="§a" ;;
|
||||||
|
aqua) code="§b" ;;
|
||||||
|
red) code="§c" ;;
|
||||||
|
light_purple|pink) code="§d" ;;
|
||||||
|
yellow) code="§e" ;;
|
||||||
|
white) code="§f" ;;
|
||||||
|
obfuscated) code="§k" ;;
|
||||||
|
bold) code="§l" ;;
|
||||||
|
strikethrough) code="§m" ;;
|
||||||
|
underline) code="§n" ;;
|
||||||
|
italic) code="§o" ;;
|
||||||
|
reset) code="§r" ;;
|
||||||
|
*) code="" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local full_message="${code}${message}§r"
|
||||||
|
# echo "[DEBUG] Sending RCON say: $full_message"
|
||||||
|
$mcrcon_cmd "say $full_message"
|
||||||
|
}
|
||||||
|
|
||||||
|
say() {
|
||||||
|
echo "[INFO] $1"
|
||||||
|
say_with_color yellow "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
countdown() {
|
||||||
|
local seconds="$1"
|
||||||
|
|
||||||
|
#echo "[DEBUG] Starting countdown of $seconds seconds..."
|
||||||
|
say "Backup will start in $seconds seconds"
|
||||||
|
|
||||||
|
while [ "$seconds" -gt 0 ]; do
|
||||||
|
#echo " $seconds"
|
||||||
|
|
||||||
|
# Logic for when to speak updates
|
||||||
|
if [ "$seconds" -le 15 ]; then
|
||||||
|
say "$seconds"
|
||||||
|
elif [ "$seconds" -le 60 ] && (( seconds % 10 == 0 )); then
|
||||||
|
say "$seconds seconds remaining"
|
||||||
|
elif [ "$seconds" -le 120 ] && (( seconds % 30 == 0 )); then
|
||||||
|
say "$seconds seconds remaining"
|
||||||
|
elif [ "$seconds" -le 300 ] && (( seconds % 60 == 0 )); then
|
||||||
|
say "$seconds seconds remaining"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
((seconds--))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
say "Countdown finished. Starting backup now."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
do_backup() {
|
||||||
|
#echo "[DEBUG] Entering do_backup with args: $*"
|
||||||
|
local source=""
|
||||||
|
local destination=""
|
||||||
|
local compression="gzip"
|
||||||
|
local format="tar"
|
||||||
|
local pure=false
|
||||||
|
|
||||||
|
# parse args
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--source) source="$2"; shift 2 ;;
|
||||||
|
--destination) destination="$2"; shift 2 ;;
|
||||||
|
--compression) compression="$2"; shift 2 ;;
|
||||||
|
--format) format="$2"; shift 2 ;;
|
||||||
|
--pure) pure=true; shift ;;
|
||||||
|
|
||||||
|
*) echo "[ERROR] Unknown option to do_backup: $1"; return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
if [[ -z "$source" || -z "$destination" ]]; then
|
||||||
|
echo "[ERROR] Missing --source or --destination"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local timestamp="$(date +%Y%m%d-%H%M%S)"
|
||||||
|
local full_source="$DATA_DIR/$source"
|
||||||
|
local basename="$(basename "$source")"
|
||||||
|
local archive=""
|
||||||
|
local ext=""
|
||||||
|
|
||||||
|
|
||||||
|
if [[ ! -d "$full_source" ]]; then
|
||||||
|
echo "[ERROR] Source directory not found: $full_source"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$destination"
|
||||||
|
|
||||||
|
if [[ "$pure" == true ]]; then
|
||||||
|
local target_dir="$destination/${basename}-${timestamp}"
|
||||||
|
echo "[INFO] Performing pure rsync backup to $target_dir"
|
||||||
|
echo "#####"
|
||||||
|
|
||||||
|
local last_percentage=-1
|
||||||
|
|
||||||
|
"$rsync_cmd" -rptgoDL --delete --info=progress2 --stats \
|
||||||
|
"$full_source/" "$target_dir/" 2>&1 | \
|
||||||
|
while IFS= read -r -d $'\r' chunk; do
|
||||||
|
# Extract percentage
|
||||||
|
if [[ $chunk =~ ([0-9]{1,3})% ]]; then
|
||||||
|
current_percentage=${BASH_REMATCH[1]}
|
||||||
|
# Print only if percentage changed
|
||||||
|
if [[ $current_percentage -ne $last_percentage ]]; then
|
||||||
|
echo -e "Progress: ${current_percentage}%\r" | say "$@"
|
||||||
|
last_percentage=$current_percentage
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
#echo "[DEBUG] Using archive mode: format=$format compression=$compression"
|
||||||
|
# Archive/compression backup
|
||||||
|
|
||||||
|
case "$format" in
|
||||||
|
tar)
|
||||||
|
# Map compression → extension and tar invocation (using only *_cmd vars)
|
||||||
|
case "$compression" in
|
||||||
|
none)
|
||||||
|
ext="tar"
|
||||||
|
tar_create=( "$tar_cmd" -cvf )
|
||||||
|
;;
|
||||||
|
gzip)
|
||||||
|
ext="tar.gz"
|
||||||
|
tar_create=( "$tar_cmd" --use-compress-program="$gzip_cmd" -cvf )
|
||||||
|
;;
|
||||||
|
bzip2)
|
||||||
|
ext="tar.bz2"
|
||||||
|
tar_create=( "$tar_cmd" --use-compress-program="$bzip2_cmd" -cvf )
|
||||||
|
;;
|
||||||
|
xz)
|
||||||
|
ext="tar.xz"
|
||||||
|
tar_create=( "$tar_cmd" --use-compress-program="$xz_cmd" -cvf )
|
||||||
|
;;
|
||||||
|
zstd)
|
||||||
|
ext="tar.zst"
|
||||||
|
tar_create=( "$tar_cmd" --use-compress-program="$zstd_cmd" -cvf )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unsupported tar compression: $compression" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
archive="$destination/${basename}-${timestamp}.${ext}"
|
||||||
|
sources=( $source )
|
||||||
|
for s in "${sources[@]}"; do
|
||||||
|
if [[ ! -e "$DATA_DIR/$s" ]]; then
|
||||||
|
echo "[ERROR] Source not found under DATA_DIR: $DATA_DIR/$s" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Count total items for progress (files + dirs)
|
||||||
|
total_items=0
|
||||||
|
for s in "${sources[@]}"; do
|
||||||
|
count=$(find "$DATA_DIR/$s" | wc -l)
|
||||||
|
total_items=$(( total_items + count ))
|
||||||
|
done
|
||||||
|
current=0
|
||||||
|
|
||||||
|
echo "[INFO] Creating $archive"
|
||||||
|
|
||||||
|
last_percent=-1
|
||||||
|
last_info_time=$(date +%s)
|
||||||
|
# seconds between info logs
|
||||||
|
interval=2
|
||||||
|
|
||||||
|
(
|
||||||
|
"${tar_create[@]}" "$archive" -C "$DATA_DIR" "${sources[@]}"
|
||||||
|
) 2>&1 | while read -r line; do
|
||||||
|
if [[ -n "$line" ]]; then
|
||||||
|
current=$(( current + 1 ))
|
||||||
|
if (( total_items > 0 )); then
|
||||||
|
percent=$(( current * 100 / total_items ))
|
||||||
|
|
||||||
|
# echo full percent:
|
||||||
|
#echo "[DEBUG] Progress: ${percent}%"
|
||||||
|
now=$(date +%s)
|
||||||
|
if (( percent != last_percent )) && (( now - last_info_time >= interval )); then
|
||||||
|
#echo "[INFO] Progress: ${percent}%"
|
||||||
|
say "Progress: ${percent}%"
|
||||||
|
last_percent=$percent
|
||||||
|
last_info_time=$now
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Ensure 100% gets printed once at the end
|
||||||
|
if (( last_percent < 100 )); then
|
||||||
|
#echo "[INFO] Progress: 100%"
|
||||||
|
say "Progress: 100%"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Tar archive created: $archive"
|
||||||
|
;;
|
||||||
|
|
||||||
|
zip)
|
||||||
|
ext="zip"
|
||||||
|
archive="$destination/${basename}-${timestamp}.${ext}"
|
||||||
|
echo "[INFO] Creating zip archive $archive"
|
||||||
|
|
||||||
|
# Count both files and directories
|
||||||
|
total_items=$(find "$DATA_DIR/$source" | wc -l)
|
||||||
|
current=0
|
||||||
|
|
||||||
|
last_percent=-1
|
||||||
|
last_info_time=$(date +%s)
|
||||||
|
interval=2 # seconds between info logs
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$DATA_DIR"
|
||||||
|
"$zip_cmd" -r "$archive" "$source"
|
||||||
|
) 2>&1 | while read -r line; do
|
||||||
|
if [[ $line =~ adding: ]]; then
|
||||||
|
current=$((current+1))
|
||||||
|
if (( total_items > 0 )); then
|
||||||
|
percent=$(( current * 100 / total_items ))
|
||||||
|
|
||||||
|
#echo "[DEBUG] Progress: ${percent}%"
|
||||||
|
|
||||||
|
now=$(date +%s)
|
||||||
|
if (( percent != last_percent )) && (( now - last_info_time >= interval )); then
|
||||||
|
#echo "[INFO] Progress: ${percent}%"
|
||||||
|
say "Progress: ${percent}%"
|
||||||
|
last_percent=$percent
|
||||||
|
last_info_time=$now
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Ensure 100% gets printed once at the end
|
||||||
|
if (( last_percent < 100 )); then
|
||||||
|
#echo "[INFO] Progress: 100%"
|
||||||
|
say "Progress: 100%"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Zip archive created: $archive"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unsupported format: $format"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "[INFO] Backup completed: $archive"
|
||||||
|
return 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# MAIN
|
||||||
|
|
||||||
|
#echo "[DEBUG] FULL=$FULL"
|
||||||
|
|
||||||
|
if [[ "$FULL" == true ]]; then
|
||||||
|
BACKUP_SOURCE="${SERVER_NAME}"
|
||||||
|
BACKUP_MODE="full server directory"
|
||||||
|
DESTINATION="${DESTINATION}/Full"
|
||||||
|
else
|
||||||
|
BACKUP_SOURCE="${SERVER_NAME}/world"
|
||||||
|
BACKUP_MODE="world folder only"
|
||||||
|
DESTINATION="${DESTINATION}/World"
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "Backup for ($BACKUP_MODE) initiated"
|
||||||
|
|
||||||
|
# Pre-backup wait
|
||||||
|
if (( SLEEP_TIME > 0 )); then
|
||||||
|
countdown "$SLEEP_TIME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
mkdir -p "$DESTINATION"
|
||||||
|
|
||||||
|
echo "[INFO] Running backup of $BACKUP_MODE to $DESTINATION..."
|
||||||
|
if do_backup \
|
||||||
|
--source "$BACKUP_SOURCE" \
|
||||||
|
--destination "$DESTINATION" \
|
||||||
|
$([[ "$PURE" == true ]] && echo "--pure") \
|
||||||
|
--compression "$COMPRESSION" \
|
||||||
|
--format "$FORMAT"; then
|
||||||
|
echo "[INFO] Backup finished successfully."
|
||||||
|
else
|
||||||
|
echo "[ERROR] Backup failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
say "Backup ($BACKUP_MODE) completed successfully."
|
||||||
134
minecraft/Scripts/minecraft-template-backup.sh
Normal file
134
minecraft/Scripts/minecraft-template-backup.sh
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Injected by Nix
|
||||||
|
RSYNC_BIN="@RSYNC_BIN@"
|
||||||
|
DATA_DIR="@DATA_DIR@"
|
||||||
|
MCSTATUS_BIN="@MCSTATUS_BIN@"
|
||||||
|
MCRCON_BIN="@MCRCON_BIN@"
|
||||||
|
AWK_BIN="@AWK_BIN@"
|
||||||
|
QUERY_PORT="@QUERY_PORT@"
|
||||||
|
RCON_PORT="@RCON_PORT@"
|
||||||
|
RCON_PASSWORD="@RCON_PASSWORD@"
|
||||||
|
SERVER_NAME="@SERVER_NAME@"
|
||||||
|
TAR_BIN="@TAR_BIN@"
|
||||||
|
ZIP_BIN="@ZIP_BIN@"
|
||||||
|
UNZIP_BIN="@UNZIP_BIN@"
|
||||||
|
GZIP_BIN="@GZIP_BIN@"
|
||||||
|
ZSTD_BIN="@ZSTD_BIN@"
|
||||||
|
PV_BIN="@PV_BIN@"
|
||||||
|
DU_BIN="@DU_BIN@"
|
||||||
|
BZIP2_BIN="@BZIP2_BIN@"
|
||||||
|
XZ_BIN="@XZ_BIN@"
|
||||||
|
|
||||||
|
# Convenience wrappers
|
||||||
|
rsync_cmd="$RSYNC_BIN"
|
||||||
|
awk_cmd="$AWK_BIN"
|
||||||
|
mcstatus_cmd="$MCSTATUS_BIN 127.0.0.1:${QUERY_PORT}"
|
||||||
|
mcrcon_cmd="$MCRCON_BIN -H 127.0.0.1 -P ${RCON_PORT} -p ${RCON_PASSWORD}"
|
||||||
|
tar_cmd="$TAR_BIN"
|
||||||
|
zip_cmd="$ZIP_BIN"
|
||||||
|
unzip_cmd="$UNZIP_BIN"
|
||||||
|
gzip_cmd="$GZIP_BIN"
|
||||||
|
zstd_cmd="$ZSTD_BIN"
|
||||||
|
pv_cmd="$PV_BIN"
|
||||||
|
du_cmd="$DU_BIN"
|
||||||
|
bzip2_cmd="$BZIP2_BIN"
|
||||||
|
xz_cmd="$XZ_BIN"
|
||||||
|
|
||||||
|
# PATH extension
|
||||||
|
# (only figured that out later if you add it here it can actually just use the bin)
|
||||||
|
# So you can easily just switch out the "*_cmd" with the "normal" name
|
||||||
|
# export PATH="$(dirname "$GZIP_BIN")":"$(dirname "$ZSTD_BIN")":"$(dirname "$PV_BIN")":"$(dirname "$DU_BIN")":"$(dirname "$BZIP2_BIN")":"$(dirname "$XZ_BIN")":"$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
SOURCE=""
|
||||||
|
DESTINATION=""
|
||||||
|
COMPRESSION="gzip"
|
||||||
|
FORMAT="tar"
|
||||||
|
PURE=false
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 --source <subfolder> --destination <path>
|
||||||
|
[--compression <gzip|bzip2|xz|zstd>] [--format <tar|zip>] [--pure]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--source Subfolder under \$DATA_DIR to back up (required)
|
||||||
|
--destination Backup destination path (required)
|
||||||
|
--compression Compression method for tar archives (default: gzip)
|
||||||
|
--format Archive format: tar or zip (default: tar)
|
||||||
|
--pure Perform plain rsync copy without compression
|
||||||
|
--help Show this help
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--source) SOURCE="$2"; shift 2;;
|
||||||
|
--destination) DESTINATION="$2"; shift 2;;
|
||||||
|
--compression) COMPRESSION="$2"; shift 2;;
|
||||||
|
--format) FORMAT="$2"; shift 2;;
|
||||||
|
--pure) PURE=true; shift 1;;
|
||||||
|
--help) usage;;
|
||||||
|
*) echo "Unknown option: $1"; usage;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validation
|
||||||
|
if [[ -z "$SOURCE" || -z "$DESTINATION" ]]; then
|
||||||
|
echo "Error: --source and --destination are required."
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
FULL_SOURCE="$DATA_DIR/$SOURCE"
|
||||||
|
|
||||||
|
if [[ ! -d "$FULL_SOURCE" ]]; then
|
||||||
|
echo "Error: Source directory '$FULL_SOURCE' does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$DESTINATION"
|
||||||
|
|
||||||
|
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
||||||
|
BASENAME="$(basename "$SOURCE")"
|
||||||
|
|
||||||
|
# Pure rsync backup
|
||||||
|
if [[ "$PURE" == true ]]; then
|
||||||
|
TARGET_DIR="$DESTINATION/${BASENAME}-${TIMESTAMP}"
|
||||||
|
echo "Performing pure rsync backup to $TARGET_DIR"
|
||||||
|
"$rsync_cmd" -rptgoDL --delete "$FULL_SOURCE/" "$TARGET_DIR/"
|
||||||
|
echo "Backup completed (pure): $TARGET_DIR"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Archive/compression backup
|
||||||
|
case "$FORMAT" in
|
||||||
|
tar)
|
||||||
|
case "$COMPRESSION" in
|
||||||
|
gzip) EXT="tar.gz"; TAR_ARGS="-czf";;
|
||||||
|
bzip2) EXT="tar.bz2"; TAR_ARGS="-cjf";;
|
||||||
|
xz) EXT="tar.xz"; TAR_ARGS="-cJf";;
|
||||||
|
zstd) EXT="tar.zst"; TAR_ARGS="--zstd -cf";;
|
||||||
|
*) echo "Unsupported compression for tar: $COMPRESSION"; exit 1;;
|
||||||
|
esac
|
||||||
|
ARCHIVE="$DESTINATION/${BASENAME}-${TIMESTAMP}.${EXT}"
|
||||||
|
"$tar_cmd" -C "$DATA_DIR" $TAR_ARGS "$ARCHIVE" "$SOURCE"
|
||||||
|
;;
|
||||||
|
zip)
|
||||||
|
EXT="zip"
|
||||||
|
ARCHIVE="$DESTINATION/${BASENAME}-${TIMESTAMP}.${EXT}"
|
||||||
|
(cd "$DATA_DIR" && "$zip_cmd" -r "$ARCHIVE" "$SOURCE")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported format: $FORMAT"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
echo "Backup completed: $ARCHIVE"
|
||||||
47
minecraft/Scripts/minecraft-template-query.sh
Normal file
47
minecraft/Scripts/minecraft-template-query.sh
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Injected by Nix
|
||||||
|
RSYNC_BIN="@RSYNC_BIN@"
|
||||||
|
DATA_DIR="@DATA_DIR@"
|
||||||
|
MCSTATUS_BIN="@MCSTATUS_BIN@"
|
||||||
|
MCRCON_BIN="@MCRCON_BIN@"
|
||||||
|
AWK_BIN="@AWK_BIN@"
|
||||||
|
QUERY_PORT="@QUERY_PORT@"
|
||||||
|
RCON_PORT="@RCON_PORT@"
|
||||||
|
RCON_PASSWORD="@RCON_PASSWORD@"
|
||||||
|
SERVER_NAME="@SERVER_NAME@"
|
||||||
|
TAR_BIN="@TAR_BIN@"
|
||||||
|
ZIP_BIN="@ZIP_BIN@"
|
||||||
|
UNZIP_BIN="@UNZIP_BIN@"
|
||||||
|
GZIP_BIN="@GZIP_BIN@"
|
||||||
|
ZSTD_BIN="@ZSTD_BIN@"
|
||||||
|
PV_BIN="@PV_BIN@"
|
||||||
|
DU_BIN="@DU_BIN@"
|
||||||
|
BZIP2_BIN="@BZIP2_BIN@"
|
||||||
|
XZ_BIN="@XZ_BIN@"
|
||||||
|
|
||||||
|
# Convenience wrappers
|
||||||
|
rsync_cmd="$RSYNC_BIN"
|
||||||
|
awk_cmd="$AWK_BIN"
|
||||||
|
mcstatus_cmd="$MCSTATUS_BIN 127.0.0.1:${QUERY_PORT}"
|
||||||
|
mcrcon_cmd="$MCRCON_BIN -H 127.0.0.1 -P ${RCON_PORT} -p ${RCON_PASSWORD}"
|
||||||
|
tar_cmd="$TAR_BIN"
|
||||||
|
zip_cmd="$ZIP_BIN"
|
||||||
|
unzip_cmd="$UNZIP_BIN"
|
||||||
|
gzip_cmd="$GZIP_BIN"
|
||||||
|
zstd_cmd="$ZSTD_BIN"
|
||||||
|
pv_cmd="$PV_BIN"
|
||||||
|
du_cmd="$DU_BIN"
|
||||||
|
bzip2_cmd="$BZIP2_BIN"
|
||||||
|
xz_cmd="$XZ_BIN"
|
||||||
|
|
||||||
|
# PATH extension
|
||||||
|
# (only figured that out later if you add it here it can actually just use the bin)
|
||||||
|
# So you can easily just switch out the "*_cmd" with the "normal" name
|
||||||
|
# export PATH="$(dirname "$GZIP_BIN")":"$(dirname "$ZSTD_BIN")":"$(dirname "$PV_BIN")":"$(dirname "$DU_BIN")":"$(dirname "$BZIP2_BIN")":"$(dirname "$XZ_BIN")":"$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Query the server
|
||||||
|
exec $mcstatus_cmd query
|
||||||
47
minecraft/Scripts/minecraft-template-rcon.sh
Normal file
47
minecraft/Scripts/minecraft-template-rcon.sh
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Injected by Nix
|
||||||
|
RSYNC_BIN="@RSYNC_BIN@"
|
||||||
|
DATA_DIR="@DATA_DIR@"
|
||||||
|
MCSTATUS_BIN="@MCSTATUS_BIN@"
|
||||||
|
MCRCON_BIN="@MCRCON_BIN@"
|
||||||
|
AWK_BIN="@AWK_BIN@"
|
||||||
|
QUERY_PORT="@QUERY_PORT@"
|
||||||
|
RCON_PORT="@RCON_PORT@"
|
||||||
|
RCON_PASSWORD="@RCON_PASSWORD@"
|
||||||
|
SERVER_NAME="@SERVER_NAME@"
|
||||||
|
TAR_BIN="@TAR_BIN@"
|
||||||
|
ZIP_BIN="@ZIP_BIN@"
|
||||||
|
UNZIP_BIN="@UNZIP_BIN@"
|
||||||
|
GZIP_BIN="@GZIP_BIN@"
|
||||||
|
ZSTD_BIN="@ZSTD_BIN@"
|
||||||
|
PV_BIN="@PV_BIN@"
|
||||||
|
DU_BIN="@DU_BIN@"
|
||||||
|
BZIP2_BIN="@BZIP2_BIN@"
|
||||||
|
XZ_BIN="@XZ_BIN@"
|
||||||
|
|
||||||
|
# Convenience wrappers
|
||||||
|
rsync_cmd="$RSYNC_BIN"
|
||||||
|
awk_cmd="$AWK_BIN"
|
||||||
|
mcstatus_cmd="$MCSTATUS_BIN 127.0.0.1:${QUERY_PORT}"
|
||||||
|
mcrcon_cmd="$MCRCON_BIN -H 127.0.0.1 -P ${RCON_PORT} -p ${RCON_PASSWORD}"
|
||||||
|
tar_cmd="$TAR_BIN"
|
||||||
|
zip_cmd="$ZIP_BIN"
|
||||||
|
unzip_cmd="$UNZIP_BIN"
|
||||||
|
gzip_cmd="$GZIP_BIN"
|
||||||
|
zstd_cmd="$ZSTD_BIN"
|
||||||
|
pv_cmd="$PV_BIN"
|
||||||
|
du_cmd="$DU_BIN"
|
||||||
|
bzip2_cmd="$BZIP2_BIN"
|
||||||
|
xz_cmd="$XZ_BIN"
|
||||||
|
|
||||||
|
# PATH extension
|
||||||
|
# (only figured that out later if you add it here it can actually just use the bin)
|
||||||
|
# So you can easily just switch out the "*_cmd" with the "normal" name
|
||||||
|
# export PATH="$(dirname "$GZIP_BIN")":"$(dirname "$ZSTD_BIN")":"$(dirname "$PV_BIN")":"$(dirname "$DU_BIN")":"$(dirname "$BZIP2_BIN")":"$(dirname "$XZ_BIN")":"$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Pass arguments directly to mcrcon
|
||||||
|
exec $mcrcon_cmd "$@"
|
||||||
109
minecraft/Scripts/minecraft-template-say.sh
Normal file
109
minecraft/Scripts/minecraft-template-say.sh
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Injected by Nix
|
||||||
|
RSYNC_BIN="@RSYNC_BIN@"
|
||||||
|
DATA_DIR="@DATA_DIR@"
|
||||||
|
MCSTATUS_BIN="@MCSTATUS_BIN@"
|
||||||
|
MCRCON_BIN="@MCRCON_BIN@"
|
||||||
|
AWK_BIN="@AWK_BIN@"
|
||||||
|
QUERY_PORT="@QUERY_PORT@"
|
||||||
|
RCON_PORT="@RCON_PORT@"
|
||||||
|
RCON_PASSWORD="@RCON_PASSWORD@"
|
||||||
|
SERVER_NAME="@SERVER_NAME@"
|
||||||
|
TAR_BIN="@TAR_BIN@"
|
||||||
|
ZIP_BIN="@ZIP_BIN@"
|
||||||
|
UNZIP_BIN="@UNZIP_BIN@"
|
||||||
|
GZIP_BIN="@GZIP_BIN@"
|
||||||
|
ZSTD_BIN="@ZSTD_BIN@"
|
||||||
|
PV_BIN="@PV_BIN@"
|
||||||
|
DU_BIN="@DU_BIN@"
|
||||||
|
BZIP2_BIN="@BZIP2_BIN@"
|
||||||
|
XZ_BIN="@XZ_BIN@"
|
||||||
|
|
||||||
|
# Convenience wrappers
|
||||||
|
rsync_cmd="$RSYNC_BIN"
|
||||||
|
awk_cmd="$AWK_BIN"
|
||||||
|
mcstatus_cmd="$MCSTATUS_BIN 127.0.0.1:${QUERY_PORT}"
|
||||||
|
mcrcon_cmd="$MCRCON_BIN -H 127.0.0.1 -P ${RCON_PORT} -p ${RCON_PASSWORD}"
|
||||||
|
tar_cmd="$TAR_BIN"
|
||||||
|
zip_cmd="$ZIP_BIN"
|
||||||
|
unzip_cmd="$UNZIP_BIN"
|
||||||
|
gzip_cmd="$GZIP_BIN"
|
||||||
|
zstd_cmd="$ZSTD_BIN"
|
||||||
|
pv_cmd="$PV_BIN"
|
||||||
|
du_cmd="$DU_BIN"
|
||||||
|
bzip2_cmd="$BZIP2_BIN"
|
||||||
|
xz_cmd="$XZ_BIN"
|
||||||
|
|
||||||
|
# PATH extension
|
||||||
|
# (only figured that out later if you add it here it can actually just use the bin)
|
||||||
|
# So you can easily just switch out the "*_cmd" with the "normal" name
|
||||||
|
# export PATH="$(dirname "$GZIP_BIN")":"$(dirname "$ZSTD_BIN")":"$(dirname "$PV_BIN")":"$(dirname "$DU_BIN")":"$(dirname "$BZIP2_BIN")":"$(dirname "$XZ_BIN")":"$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Argument parsing
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Usage: $0 <color|format> <message...>"
|
||||||
|
echo "Example: $0 red 'Server restarting soon!'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CODE_NAME="$1"
|
||||||
|
shift
|
||||||
|
MESSAGE="$*"
|
||||||
|
|
||||||
|
# Map color/format names to Minecraft § codes
|
||||||
|
case "$CODE_NAME" in
|
||||||
|
# Colors
|
||||||
|
black) CODE="§0" ;;
|
||||||
|
dark_blue) CODE="§1" ;;
|
||||||
|
dark_green) CODE="§2" ;;
|
||||||
|
dark_aqua) CODE="§3" ;;
|
||||||
|
dark_red) CODE="§4" ;;
|
||||||
|
dark_purple) CODE="§5" ;;
|
||||||
|
gold) CODE="§6" ;;
|
||||||
|
gray) CODE="§7" ;;
|
||||||
|
dark_gray) CODE="§8" ;;
|
||||||
|
blue) CODE="§9" ;;
|
||||||
|
green) CODE="§a" ;;
|
||||||
|
aqua) CODE="§b" ;;
|
||||||
|
red) CODE="§c" ;;
|
||||||
|
light_purple|pink) CODE="§d" ;;
|
||||||
|
yellow) CODE="§e" ;;
|
||||||
|
white) CODE="§f" ;;
|
||||||
|
|
||||||
|
# Bedrock-only extras
|
||||||
|
minecoin_gold) CODE="§g" ;;
|
||||||
|
material_quartz) CODE="§h" ;;
|
||||||
|
material_iron) CODE="§i" ;;
|
||||||
|
material_netherite) CODE="§j" ;;
|
||||||
|
material_redstone) CODE="§m" ;;
|
||||||
|
material_copper) CODE="§n" ;;
|
||||||
|
material_gold) CODE="§p" ;;
|
||||||
|
material_emerald) CODE="§q" ;;
|
||||||
|
material_diamond) CODE="§s" ;;
|
||||||
|
material_lapis) CODE="§t" ;;
|
||||||
|
material_amethyst) CODE="§u" ;;
|
||||||
|
|
||||||
|
# Formatting
|
||||||
|
obfuscated) CODE="§k" ;;
|
||||||
|
bold) CODE="§l" ;;
|
||||||
|
strikethrough) CODE="§m" ;;
|
||||||
|
underline) CODE="§n" ;;
|
||||||
|
italic) CODE="§o" ;;
|
||||||
|
reset) CODE="§r" ;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Unknown code: $CODE_NAME"
|
||||||
|
echo "Available colors: black, dark_blue, dark_green, dark_aqua, dark_red, dark_purple, gold, gray, dark_gray, blue, green, aqua, red, light_purple, yellow, white"
|
||||||
|
echo "Formats: obfuscated, bold, strikethrough, underline, italic, reset"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
FULL_MESSAGE="${CODE}${MESSAGE}§r"
|
||||||
|
|
||||||
|
# Send via RCON
|
||||||
|
exec $mcrcon_cmd "say $FULL_MESSAGE"
|
||||||
11
minecraft/default.nix
Normal file
11
minecraft/default.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
imports = [
|
||||||
|
./minecraft.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
474
minecraft/minecraft.nix
Normal file
474
minecraft/minecraft.nix
Normal file
|
|
@ -0,0 +1,474 @@
|
||||||
|
# Enhanced Minecraft Server Module
|
||||||
|
#
|
||||||
|
# This module provides:
|
||||||
|
# - A wrapper around the Infinidoge/nix-minecraft module
|
||||||
|
# - Declarative configuration for multiple servers, including memory, operators, whitelist, and symlinks
|
||||||
|
# - Automatic systemd service and timer generation for scheduled jobs
|
||||||
|
# - Prebuilt helper scripts (rcon, query, backup, say, backup-routine) for server administration
|
||||||
|
# - Support for backups, logging, and scheduled maintenance tasks
|
||||||
|
#
|
||||||
|
# Configuration Options:
|
||||||
|
# enable – Enable the enhanced Minecraft servers (boolean)
|
||||||
|
# user – System user that owns and runs the servers (string) - DON'T CHANGE THIS
|
||||||
|
# group – System group that owns and runs the servers (string) - DON'T CHANGE THIS
|
||||||
|
# dataDir – Directory to store Minecraft server data (path)
|
||||||
|
# servers – Attribute set of servers, keyed by name. Each server can define:
|
||||||
|
# memory.min / memory.max – JVM memory allocation (strings, e.g. "2G")
|
||||||
|
# package – Minecraft server package to use (package)
|
||||||
|
# autoStart – Start server at boot (boolean)
|
||||||
|
# whitelist – Declarative whitelist (UUIDs per user)
|
||||||
|
# operators – Declarative operator list with permission levels
|
||||||
|
# symlinks – Files or packages symlinked into the server data directory
|
||||||
|
# properties – Declarative `server.properties` values (ports, motd, difficulty, etc.)
|
||||||
|
# schedules – Declarative scheduled jobs with systemd timers and services
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Info:
|
||||||
|
# I am happy to help if you have Issues and am happy to see a PR for any change
|
||||||
|
# I ll use it personally too so you can expect frequent updates
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# ToDo:
|
||||||
|
# Schedule to restart a server using systemD. I have not figured that out yet.
|
||||||
|
# what i do know is that `sudo` does not work neither does it work if you just tell the server to restart.
|
||||||
|
#
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with lib; let
|
||||||
|
cfg = config.nyx-minecraft.service;
|
||||||
|
minecraftUUID = types.strMatching "[0-9a-fA-F-]{36}";
|
||||||
|
|
||||||
|
# Setup the Scripts for the server
|
||||||
|
scriptDir = ./Scripts;
|
||||||
|
mkScript = serverName: serverCfg: scriptType: let
|
||||||
|
templateFile = scriptDir + "/minecraft-template-${scriptType}.sh";
|
||||||
|
rawText = builtins.readFile templateFile;
|
||||||
|
scriptText =
|
||||||
|
builtins.replaceStrings
|
||||||
|
[
|
||||||
|
"@DATA_DIR@"
|
||||||
|
"@RSYNC_BIN@"
|
||||||
|
"@MCSTATUS_BIN@"
|
||||||
|
"@MCRCON_BIN@"
|
||||||
|
"@AWK_BIN@"
|
||||||
|
"@QUERY_PORT@"
|
||||||
|
"@RCON_PORT@"
|
||||||
|
"@RCON_PASSWORD@"
|
||||||
|
"@SERVER_NAME@"
|
||||||
|
"@TAR_BIN@"
|
||||||
|
"@ZIP_BIN@"
|
||||||
|
"@UNZIP_BIN@"
|
||||||
|
"@GZIP_BIN@"
|
||||||
|
"@ZSTD_BIN@"
|
||||||
|
"@PV_BIN@"
|
||||||
|
"@DU_BIN@"
|
||||||
|
"@BZIP2_BIN@"
|
||||||
|
"@XZ_BIN@"
|
||||||
|
]
|
||||||
|
# If you add anything here make sure to add it at the systemd service too
|
||||||
|
[
|
||||||
|
cfg.dataDir
|
||||||
|
"${pkgs.rsync}/bin/rsync"
|
||||||
|
"${pkgs.mcstatus}/bin/mcstatus"
|
||||||
|
"${pkgs.mcrcon}/bin/mcrcon"
|
||||||
|
"${pkgs.gawk}/bin/awk"
|
||||||
|
(toString (serverCfg.properties.serverPort + 200))
|
||||||
|
(toString (serverCfg.properties.serverPort + 100))
|
||||||
|
serverCfg.properties.rconPassword
|
||||||
|
serverName
|
||||||
|
"${pkgs.gnutar}/bin/tar"
|
||||||
|
"${pkgs.zip}/bin/zip"
|
||||||
|
"${pkgs.unzip}/bin/unzip"
|
||||||
|
"${pkgs.gzip}/bin/gzip"
|
||||||
|
"${pkgs.zstd}/bin/zstd"
|
||||||
|
"${pkgs.pv}/bin/pv"
|
||||||
|
"${pkgs.coreutils}/bin/du"
|
||||||
|
"${pkgs.bzip2}/bin/bzip2"
|
||||||
|
"${pkgs.xz}/bin/xz"
|
||||||
|
]
|
||||||
|
rawText;
|
||||||
|
in
|
||||||
|
pkgs.writeShellScriptBin "minecraft-${serverName}-${scriptType}" scriptText;
|
||||||
|
in {
|
||||||
|
# Note most of the options get directly exposed
|
||||||
|
# to nix-minecraft which makes it almost an
|
||||||
|
# Drop in replacement
|
||||||
|
options.nyx-minecraft.service = {
|
||||||
|
enable = mkEnableOption "Enable enhanced Minecraft servers with backup, logging, and admin helpers.";
|
||||||
|
|
||||||
|
eula = mkEnableOption ''
|
||||||
|
Whether you agree to
|
||||||
|
<link xlink:href="https://account.mojang.com/documents/minecraft_eula">
|
||||||
|
Mojang's EULA</link>. This option must be set to
|
||||||
|
<literal>true</literal> to run Minecraft server.
|
||||||
|
'';
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "minecraft";
|
||||||
|
description = ''
|
||||||
|
Name of the user to create and run servers under.
|
||||||
|
It is recommended to leave this as the default, as it is
|
||||||
|
the same user as <option>services.minecraft-server</option>.
|
||||||
|
'';
|
||||||
|
internal = true;
|
||||||
|
visible = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "minecraft";
|
||||||
|
description = ''
|
||||||
|
Name of the group to create and run servers under.
|
||||||
|
In order to modify the server files your user must be a part of this
|
||||||
|
group. If you are using the tmux management system (the default), you also need to be a part of this group to attach to the tmux socket.
|
||||||
|
It is recommended to leave this as the default, as it is
|
||||||
|
the same group as <option>services.minecraft-server</option>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
dataDir = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
default = "/srv/minecraft";
|
||||||
|
description = ''
|
||||||
|
Directory to store the Minecraft servers.
|
||||||
|
Each server will be under a subdirectory named after
|
||||||
|
the server name in this directory, such as <literal>/srv/minecraft/servername</literal>. '';
|
||||||
|
};
|
||||||
|
|
||||||
|
servers = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||||
|
options = {
|
||||||
|
enable = mkEnableOption "Enable this Server";
|
||||||
|
|
||||||
|
memory = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
min = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "2G";
|
||||||
|
description = "Min JVM memory.";
|
||||||
|
};
|
||||||
|
max = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "2G";
|
||||||
|
description = "Max JVM memory.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = {
|
||||||
|
min = "2G";
|
||||||
|
max = "2G";
|
||||||
|
};
|
||||||
|
description = "JVM memory settings for this server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
package = mkOption {
|
||||||
|
description = "The Minecraft server package to use.";
|
||||||
|
type = types.package;
|
||||||
|
default = pkgs.minecraft-server;
|
||||||
|
defaultText = literalExpression "pkgs.minecraft-server";
|
||||||
|
example = "pkgs.minecraftServers.vanilla-1_18_2";
|
||||||
|
};
|
||||||
|
|
||||||
|
autoStart = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to start this server on boot.
|
||||||
|
If set to <literal>false</literal>, it can still be started with
|
||||||
|
<literal>systemctl start minecraft-server-servername</literal>.
|
||||||
|
Requires the server to be enabled.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
whitelist = mkOption {
|
||||||
|
type = types.attrsOf minecraftUUID;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Whitelisted players, only has an effect when
|
||||||
|
enabled via <option>services.minecraft-servers.<name>.serverProperties</option>
|
||||||
|
by setting <literal>white-list</literal> to <literal>true</literal>.
|
||||||
|
|
||||||
|
To use a non-declarative whitelist, enable the whitelist and don't fill in this value.
|
||||||
|
As long as it is empty, no whitelist file is generated.
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
||||||
|
username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
symlinks = mkOption {
|
||||||
|
type = types.attrsOf (types.either types.path types.package);
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Things to symlink into this server's data directory.
|
||||||
|
Can be used to declaratively manage arbitrary files (e.g., mods, configs).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
operators = mkOption {
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.coercedTo minecraftUUID (v: {uuid = v;}) (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
uuid = mkOption {
|
||||||
|
type = minecraftUUID;
|
||||||
|
description = "The operator's UUID";
|
||||||
|
example = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
||||||
|
};
|
||||||
|
level = mkOption {
|
||||||
|
type = types.ints.between 0 4;
|
||||||
|
description = "The operator's permission level";
|
||||||
|
default = 4;
|
||||||
|
};
|
||||||
|
bypassesPlayerLimit = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
description = "If true, the operator can join the server even if the player limit has been reached";
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
default = {};
|
||||||
|
description = "Operators with permission levels.";
|
||||||
|
};
|
||||||
|
|
||||||
|
properties = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
serverPort = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 25565;
|
||||||
|
description = "Server Port";
|
||||||
|
};
|
||||||
|
difficulty = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 2;
|
||||||
|
description = "Difficulty in numbers: 0=Peaceful, 1=Easy, 2=Normal, 3=Hard";
|
||||||
|
};
|
||||||
|
gamemode = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 0;
|
||||||
|
description = "Gamemode: 0=Survival, 1=Creative";
|
||||||
|
};
|
||||||
|
maxPlayers = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 5;
|
||||||
|
description = "How many players can join the server";
|
||||||
|
};
|
||||||
|
motd = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "NixOS Minecraft server!";
|
||||||
|
description = "Message displayed when selecting the server";
|
||||||
|
};
|
||||||
|
rconPassword = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "superSecret";
|
||||||
|
description = "Password for Rcon";
|
||||||
|
};
|
||||||
|
hardcore = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable Hardcore mode";
|
||||||
|
};
|
||||||
|
levelSeed = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "42";
|
||||||
|
description = "World seed (default is the answer to the universe)";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = {};
|
||||||
|
description = "Declarative Minecraft server.properties values.";
|
||||||
|
};
|
||||||
|
|
||||||
|
schedules = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether this schedule is active.";
|
||||||
|
};
|
||||||
|
|
||||||
|
timer = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "hourly";
|
||||||
|
description = "Systemd timer unit specifier (e.g., hourly, daily, weekly).";
|
||||||
|
};
|
||||||
|
|
||||||
|
code = mkOption {
|
||||||
|
type = types.lines;
|
||||||
|
description = "Shell code to execute when the schedule fires.";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Not properly enough tested
|
||||||
|
#customEnviorment = mkOption {
|
||||||
|
# type = types.lines;
|
||||||
|
# description = "to expose binaries or other things to the SystemD service";
|
||||||
|
# example = "ZSTD_BIN=${pkgs.zstd}/bin/zstd";
|
||||||
|
#};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = {};
|
||||||
|
description = "Scheduled jobs for this Minecraft server.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = {};
|
||||||
|
description = "Servers to run under the enhanced Minecraft service.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Wrapper for nix-minecraft
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.minecraft-servers = {
|
||||||
|
enable = true;
|
||||||
|
eula = cfg.eula;
|
||||||
|
openFirewall = true;
|
||||||
|
user = cfg.user;
|
||||||
|
group = cfg.group;
|
||||||
|
dataDir = cfg.dataDir;
|
||||||
|
|
||||||
|
servers =
|
||||||
|
lib.mapAttrs (serverName: serverCfg: {
|
||||||
|
enable = serverCfg.enable;
|
||||||
|
package = serverCfg.package;
|
||||||
|
jvmOpts = "-Xmx${serverCfg.memory.max} -Xms${serverCfg.memory.min}";
|
||||||
|
autoStart = serverCfg.autoStart;
|
||||||
|
|
||||||
|
symlinks = serverCfg.symlinks;
|
||||||
|
whitelist = serverCfg.whitelist;
|
||||||
|
operators = serverCfg.operators;
|
||||||
|
|
||||||
|
serverProperties = {
|
||||||
|
enable-rcon = true;
|
||||||
|
enable-command-block = true;
|
||||||
|
allow-flight = true;
|
||||||
|
enable-query = true;
|
||||||
|
server-port = serverCfg.properties.serverPort;
|
||||||
|
difficulty = serverCfg.properties.difficulty;
|
||||||
|
gamemode = serverCfg.properties.gamemode;
|
||||||
|
max-players = serverCfg.properties.maxPlayers;
|
||||||
|
motd = serverCfg.properties.motd;
|
||||||
|
"rcon.password" = serverCfg.properties.rconPassword;
|
||||||
|
"rcon.port" = serverCfg.properties.serverPort + 100;
|
||||||
|
"query.port" = serverCfg.properties.serverPort + 200;
|
||||||
|
|
||||||
|
hardcore = serverCfg.properties.hardcore;
|
||||||
|
level-seed = serverCfg.properties.levelSeed;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
cfg.servers;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Schedule logic
|
||||||
|
systemd.services = lib.mkMerge (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
serverName: serverCfg:
|
||||||
|
lib.mapAttrs' (scheduleName: scheduleCfg: let
|
||||||
|
# yes this will be building the scripts twice but thsi
|
||||||
|
# way the path is accessible by the SystemD service
|
||||||
|
rconBin = mkScript serverName serverCfg "rcon";
|
||||||
|
queryBin = mkScript serverName serverCfg "query";
|
||||||
|
backupBin = mkScript serverName serverCfg "backup";
|
||||||
|
sayBin = mkScript serverName serverCfg "say";
|
||||||
|
routineBin = mkScript serverName serverCfg "backup-routine";
|
||||||
|
in {
|
||||||
|
name = "minecraft-${serverName}-${scheduleName}";
|
||||||
|
value = {
|
||||||
|
description = "Minecraft ${serverName} scheduled job: ${scheduleName}";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = cfg.user;
|
||||||
|
Group = cfg.group;
|
||||||
|
Environment = [
|
||||||
|
"RCON_BIN=${rconBin}/bin/minecraft-${serverName}-rcon"
|
||||||
|
"QUERY_BIN=${queryBin}/bin/minecraft-${serverName}-query"
|
||||||
|
"BACKUP_BIN=${backupBin}/bin/minecraft-${serverName}-backup"
|
||||||
|
"SAY_BIN=${sayBin}/bin/minecraft-${serverName}-say"
|
||||||
|
"ROUTINE_BIN=${routineBin}/bin/minecraft-${serverName}-backup-routine"
|
||||||
|
"ZSTD_BIN=${pkgs.zstd}/bin/zstd"
|
||||||
|
# add more bin here
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Not properly enough tested
|
||||||
|
#
|
||||||
|
#scheduleCfg.customEnviorment
|
||||||
|
];
|
||||||
|
ExecStart = pkgs.writeShellScript "minecraft-${serverName}-${scheduleName}.sh" ''
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "hi — available helpers:"
|
||||||
|
echo "if you want any custom scripts or"
|
||||||
|
echo "packages for your script you need to ExecStart"
|
||||||
|
echo " $RCON_BIN"
|
||||||
|
echo " $QUERY_BIN"
|
||||||
|
echo " $BACKUP_BIN"
|
||||||
|
echo " $SAY_BIN"
|
||||||
|
echo " $ROUTINE_BIN"
|
||||||
|
|
||||||
|
# this is so it can use the scripts the same way you would run them:
|
||||||
|
minecraft-${serverName}-query() { $QUERY_BIN "$@"; }
|
||||||
|
minecraft-${serverName}-rcon() { $RCON_BIN "$@"; }
|
||||||
|
minecraft-${serverName}-backup() { $BACKUP_BIN "$@"; }
|
||||||
|
minecraft-${serverName}-say() { $SAY_BIN "$@"; }
|
||||||
|
minecraft-${serverName}-backup-routine() { $ROUTINE_BIN "$@"; }
|
||||||
|
minecraft-${serverName}-query
|
||||||
|
|
||||||
|
# her your code will go:
|
||||||
|
${scheduleCfg.code}
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# If you have this on:
|
||||||
|
# wantedBy = [ "multi-user.target" ];
|
||||||
|
# it will run the service on each rebuild.
|
||||||
|
# i dont want that you can enable it if you like
|
||||||
|
};
|
||||||
|
}) serverCfg.schedules
|
||||||
|
)
|
||||||
|
cfg.servers
|
||||||
|
);
|
||||||
|
|
||||||
|
# the timers to actually run the SystemD service
|
||||||
|
systemd.timers = lib.mkMerge (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
serverName: serverCfg:
|
||||||
|
lib.mapAttrs' (scheduleName: scheduleCfg: {
|
||||||
|
name = "minecraft-${serverName}-${scheduleName}";
|
||||||
|
value = {
|
||||||
|
description = "Timer for Minecraft ${serverName} schedule ${scheduleName}";
|
||||||
|
wantedBy = ["timers.target"];
|
||||||
|
timerConfig.OnCalendar = scheduleCfg.timer;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
serverCfg.schedules
|
||||||
|
)
|
||||||
|
cfg.servers
|
||||||
|
);
|
||||||
|
|
||||||
|
# this is building the scripts for the user
|
||||||
|
# Those are the prewritten scripts from the ./Script dir
|
||||||
|
environment.systemPackages = lib.flatten (
|
||||||
|
lib.mapAttrsToList (serverName: serverCfg: [
|
||||||
|
(mkScript serverName serverCfg "rcon")
|
||||||
|
(mkScript serverName serverCfg "query")
|
||||||
|
(mkScript serverName serverCfg "backup")
|
||||||
|
(mkScript serverName serverCfg "say")
|
||||||
|
(mkScript serverName serverCfg "backup-routine")
|
||||||
|
])
|
||||||
|
cfg.servers
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue