init minecraft server configs!
This commit is contained in:
commit
64820d502a
23 changed files with 24719 additions and 0 deletions
61
modules/backup.nix
Normal file
61
modules/backup.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
let
|
||||
mcDataDir = "/srv/minecraft/data";
|
||||
b2Bucket = "compsigh-minecraft-backups"; # TODO: change to your bucket name
|
||||
in
|
||||
{
|
||||
systemd.services.minecraft-backup = {
|
||||
description = "Backup Minecraft world to Backblaze B2";
|
||||
after = [ "docker-minecraft.service" ];
|
||||
path = [ pkgs.docker ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = pkgs.writeShellScript "minecraft-backup" ''
|
||||
TEMP_BACKUP="/tmp/minecraft-world-latest.tar.gz"
|
||||
|
||||
# Check if the container is running
|
||||
if ! docker ps --format '{{.Names}}' | grep -q '^minecraft$'; then
|
||||
echo "Minecraft container not running, skipping backup"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Flush all chunks to disk and pause saving
|
||||
docker exec minecraft rcon-cli save-all flush
|
||||
sleep 2
|
||||
docker exec minecraft rcon-cli save-off
|
||||
|
||||
# Compress world data
|
||||
${pkgs.gnutar}/bin/tar czf "$TEMP_BACKUP" \
|
||||
-C ${mcDataDir} world world_nether world_the_end
|
||||
|
||||
# Re-enable saving immediately
|
||||
docker exec minecraft rcon-cli save-on
|
||||
|
||||
# Upload to B2 (overwrites the single backup file)
|
||||
B2_ACCOUNT=$(cat ${config.age.secrets.b2-account-id.path})
|
||||
B2_KEY=$(cat ${config.age.secrets.b2-application-key.path})
|
||||
|
||||
${pkgs.rclone}/bin/rclone copyto \
|
||||
"$TEMP_BACKUP" \
|
||||
":b2:${b2Bucket}/world-latest.tar.gz" \
|
||||
--b2-account "$B2_ACCOUNT" \
|
||||
--b2-key "$B2_KEY" \
|
||||
--no-check-dest
|
||||
|
||||
# Clean up local temp file
|
||||
rm -f "$TEMP_BACKUP"
|
||||
|
||||
echo "Backup uploaded to Backblaze B2 successfully"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.minecraft-backup = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "*:00,30";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
27
modules/caddy.nix
Normal file
27
modules/caddy.nix
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
let
|
||||
domain = "minecraft.compsigh.club";
|
||||
gitRepo = "https://github.com/compsigh/minecraft";
|
||||
in
|
||||
{
|
||||
services.caddy = {
|
||||
enable = true;
|
||||
|
||||
virtualHosts = {
|
||||
# minecraft.compsigh.club → redirect to git repo
|
||||
"${domain}" = {
|
||||
extraConfig = ''
|
||||
redir ${gitRepo} permanent
|
||||
'';
|
||||
};
|
||||
|
||||
# status.minecraft.compsigh.club → Grafana
|
||||
"status.${domain}" = {
|
||||
extraConfig = ''
|
||||
reverse_proxy localhost:3000
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
36
modules/discord.nix
Normal file
36
modules/discord.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
let
|
||||
mcDataDir = "/srv/minecraft/data";
|
||||
in
|
||||
{
|
||||
systemd.services.minecraft-discord-config = {
|
||||
description = "Inject Discord bot token into Discord4Fabric config";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "docker-minecraft.service" ];
|
||||
after = [ "agenix.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = pkgs.writeShellScript "setup-discord4fabric" ''
|
||||
CONFIG_DIR="${mcDataDir}/config/discord4fabric"
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
TOKEN=$(cat ${config.age.secrets.discord-bot-token.path})
|
||||
|
||||
cat > "$CONFIG_DIR/config.json" <<EOF
|
||||
{
|
||||
"botToken": "$TOKEN",
|
||||
"guildId": "849685154543960085",
|
||||
"channelId": "1482269524571979899",
|
||||
"chatMessageFormat": "**%player%** > %message%",
|
||||
"joinFormat": "%player% joined the server",
|
||||
"leaveFormat": "%player% left the server",
|
||||
"deathFormat": "%message%",
|
||||
"advancementFormat": "%player% has made the advancement **%advancement%**",
|
||||
"discordToMinecraftFormat": "[Discord] <%user%> %message%"
|
||||
}
|
||||
EOF
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
87
modules/hardening.nix
Normal file
87
modules/hardening.nix
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
# ── Firewall ──
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 80 443 25565 ]; # Caddy HTTP/HTTPS + Minecraft
|
||||
allowedUDPPorts = [ 24454 ]; # Simple Voice Chat
|
||||
trustedInterfaces = [ "tailscale0" ]; # Full access over Tailscale (SSH, etc.)
|
||||
logRefusedConnections = true;
|
||||
};
|
||||
|
||||
# ── SSH — key-only, hardened ──
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PasswordAuthentication = false;
|
||||
PermitRootLogin = "prohibit-password";
|
||||
KbdInteractiveAuthentication = false;
|
||||
X11Forwarding = false;
|
||||
MaxAuthTries = 3;
|
||||
LoginGraceTime = 30;
|
||||
AllowAgentForwarding = false;
|
||||
AllowTcpForwarding = false;
|
||||
};
|
||||
};
|
||||
|
||||
# ── Fail2ban ──
|
||||
services.fail2ban = {
|
||||
enable = true;
|
||||
bantime = "1h";
|
||||
maxretry = 10;
|
||||
bantime-increment = {
|
||||
enable = true;
|
||||
maxtime = "24h";
|
||||
};
|
||||
};
|
||||
|
||||
# ── Kernel hardening ──
|
||||
boot.kernel.sysctl = {
|
||||
# SYN flood protection
|
||||
"net.ipv4.tcp_syncookies" = 1;
|
||||
"net.ipv4.tcp_max_syn_backlog" = 2048;
|
||||
"net.ipv4.tcp_synack_retries" = 2;
|
||||
|
||||
# IP spoofing protection
|
||||
"net.ipv4.conf.all.rp_filter" = 1;
|
||||
"net.ipv4.conf.default.rp_filter" = 1;
|
||||
|
||||
# Disable ICMP redirects
|
||||
"net.ipv4.conf.all.accept_redirects" = 0;
|
||||
"net.ipv4.conf.default.accept_redirects" = 0;
|
||||
"net.ipv6.conf.all.accept_redirects" = 0;
|
||||
"net.ipv4.conf.all.send_redirects" = 0;
|
||||
"net.ipv4.conf.default.send_redirects" = 0;
|
||||
|
||||
# Ignore broadcast pings
|
||||
"net.ipv4.icmp_echo_ignore_broadcasts" = 1;
|
||||
|
||||
# Disable source routing
|
||||
"net.ipv4.conf.all.accept_source_route" = 0;
|
||||
"net.ipv4.conf.default.accept_source_route" = 0;
|
||||
|
||||
# Restrict unprivileged BPF
|
||||
"kernel.unprivileged_bpf_disabled" = 1;
|
||||
|
||||
# Restrict dmesg
|
||||
"kernel.dmesg_restrict" = 1;
|
||||
|
||||
# Restrict kernel pointers
|
||||
"kernel.kptr_restrict" = 2;
|
||||
};
|
||||
|
||||
# ── Kernel image protection ──
|
||||
security.protectKernelImage = true;
|
||||
|
||||
# ── Disable unused services ──
|
||||
services.avahi.enable = false;
|
||||
services.printing.enable = false;
|
||||
|
||||
# ── Automatic security updates ──
|
||||
system.autoUpgrade = {
|
||||
enable = true;
|
||||
allowReboot = false;
|
||||
dates = "04:00";
|
||||
};
|
||||
}
|
||||
142
modules/minecraft.nix
Normal file
142
modules/minecraft.nix
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
mcDataDir = "/srv/minecraft/data";
|
||||
|
||||
modrinthMods = builtins.concatStringsSep "," [
|
||||
# Performance
|
||||
"lithium"
|
||||
"krypton"
|
||||
"ferrite-core"
|
||||
"c2me-fabric"
|
||||
"noisium"
|
||||
"linearly-optimized"
|
||||
"vmp-fabric"
|
||||
"ksyxis"
|
||||
"scalablelux"
|
||||
"lmd"
|
||||
"structure-layout-optimizer"
|
||||
"threadtweak"
|
||||
"crashexploitfixer"
|
||||
|
||||
# Anti-cheat
|
||||
"anti-xray"
|
||||
"grimac"
|
||||
"no-chat-reports"
|
||||
|
||||
# Worldgen — terrain (Stardust Labs suite)
|
||||
"terralith"
|
||||
"tectonic"
|
||||
"amplified-nether"
|
||||
"nullscape"
|
||||
|
||||
# Worldgen — structures (YUNG's suite)
|
||||
"yungs-api"
|
||||
"yungs-better-dungeons"
|
||||
"yungs-better-strongholds"
|
||||
"yungs-better-ocean-monuments"
|
||||
"yungs-better-mineshafts"
|
||||
"yungs-better-desert-temples"
|
||||
"yungs-better-jungle-temples"
|
||||
"yungs-better-witch-huts"
|
||||
"yungs-better-nether-fortresses"
|
||||
"yungs-better-end-island"
|
||||
"yungs-extras"
|
||||
"yungs-bridges"
|
||||
|
||||
# Client mod compatibility (optional on client side)
|
||||
"appleskin"
|
||||
"simple-voice-chat"
|
||||
|
||||
# QoL
|
||||
"oneplayersleep"
|
||||
"netherportalfix"
|
||||
|
||||
# Moderation
|
||||
"luckperms"
|
||||
"banhammer"
|
||||
"ledger"
|
||||
"styled-chat"
|
||||
|
||||
# Discord bridge
|
||||
"discord4fabric"
|
||||
|
||||
# Utilities
|
||||
"chunky"
|
||||
];
|
||||
|
||||
jvmFlags = builtins.concatStringsSep " " [
|
||||
"-XX:+UseG1GC"
|
||||
"-XX:+ParallelRefProcEnabled"
|
||||
"-XX:MaxGCPauseMillis=200"
|
||||
"-XX:+UnlockExperimentalVMOptions"
|
||||
"-XX:+DisableExplicitGC"
|
||||
"-XX:G1NewSizePercent=30"
|
||||
"-XX:G1MaxNewSizePercent=40"
|
||||
"-XX:G1HeapRegionSize=8M"
|
||||
"-XX:G1ReservePercent=20"
|
||||
"-XX:G1MixedGCCountTarget=4"
|
||||
"-XX:InitiatingHeapOccupancyPercent=15"
|
||||
"-XX:G1MixedGCLiveThresholdPercent=90"
|
||||
"-XX:SurvivorRatio=32"
|
||||
"-XX:+PerfDisableSharedMem"
|
||||
"-XX:MaxTenuringThreshold=1"
|
||||
];
|
||||
in
|
||||
{
|
||||
virtualisation.docker.enable = true;
|
||||
|
||||
virtualisation.oci-containers.backend = "docker";
|
||||
virtualisation.oci-containers.containers.minecraft = {
|
||||
image = "itzg/minecraft-server:latest";
|
||||
ports = [
|
||||
"25565:25565" # Minecraft
|
||||
"24454:24454/udp" # Simple Voice Chat
|
||||
];
|
||||
volumes = [
|
||||
"${mcDataDir}:/data"
|
||||
];
|
||||
environment = {
|
||||
EULA = "TRUE";
|
||||
TYPE = "FABRIC";
|
||||
VERSION = "1.21.4";
|
||||
MEMORY = "2G";
|
||||
MAX_PLAYERS = "10";
|
||||
DIFFICULTY = "hard";
|
||||
VIEW_DISTANCE = "10";
|
||||
SIMULATION_DISTANCE = "10";
|
||||
ENABLE_WHITELIST = "TRUE";
|
||||
ENFORCE_WHITELIST = "TRUE";
|
||||
WHITELIST = "player1,player2"; # TODO: add your players
|
||||
MOTD = "compsigh Minecraft"; # TODO: customize
|
||||
MODRINTH_PROJECTS = modrinthMods;
|
||||
JVM_XX_OPTS = jvmFlags;
|
||||
};
|
||||
extraOptions = [
|
||||
"--memory=3g"
|
||||
"--cpus=2"
|
||||
"--restart=unless-stopped"
|
||||
"--pids-limit=256"
|
||||
"--security-opt=no-new-privileges"
|
||||
];
|
||||
};
|
||||
|
||||
# Ensure data directory exists
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${mcDataDir} 0755 root root -"
|
||||
];
|
||||
|
||||
# Copy Chunky config (concurrency: 1 for background generation)
|
||||
systemd.services.minecraft-mod-configs = {
|
||||
description = "Copy mod configs into Minecraft data volume";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "docker-minecraft.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = pkgs.writeShellScript "setup-mod-configs" ''
|
||||
mkdir -p ${mcDataDir}/plugins/Chunky
|
||||
cp ${../configs/chunky.yml} ${mcDataDir}/plugins/Chunky/config.yml
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
86
modules/monitoring.nix
Normal file
86
modules/monitoring.nix
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
services.prometheus = {
|
||||
enable = true;
|
||||
port = 9090;
|
||||
retentionTime = "30d";
|
||||
|
||||
exporters = {
|
||||
node = {
|
||||
enable = true;
|
||||
port = 9100;
|
||||
enabledCollectors = [
|
||||
"cpu"
|
||||
"diskstats"
|
||||
"filesystem"
|
||||
"loadavg"
|
||||
"meminfo"
|
||||
"netdev"
|
||||
"netstat"
|
||||
"stat"
|
||||
"time"
|
||||
"vmstat"
|
||||
"systemd"
|
||||
"processes"
|
||||
"interrupts"
|
||||
"conntrack"
|
||||
"tcpstat"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
scrapeConfigs = [
|
||||
{
|
||||
job_name = "node";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:9100" ];
|
||||
labels = { instance = "minecraft-server"; };
|
||||
}];
|
||||
scrape_interval = "15s";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
http_addr = "127.0.0.1";
|
||||
http_port = 3000;
|
||||
domain = "status.minecraft.compsigh.club";
|
||||
root_url = "https://status.minecraft.compsigh.club";
|
||||
};
|
||||
security = {
|
||||
admin_user = "admin";
|
||||
admin_password = "$__file{${config.age.secrets.grafana-admin-password.path}}";
|
||||
secret_key = "$__file{${config.age.secrets.grafana-secret-key.path}}";
|
||||
};
|
||||
"auth.anonymous".enabled = false;
|
||||
users.allow_sign_up = false;
|
||||
};
|
||||
|
||||
provision = {
|
||||
enable = true;
|
||||
|
||||
datasources.settings.datasources = [
|
||||
{
|
||||
name = "Prometheus";
|
||||
type = "prometheus";
|
||||
url = "http://localhost:9090";
|
||||
isDefault = true;
|
||||
editable = false;
|
||||
}
|
||||
];
|
||||
|
||||
dashboards.settings.providers = [
|
||||
{
|
||||
name = "Node Exporter Full";
|
||||
options.path = "${pkgs.writeTextDir "dashboards/node-exporter-full.json"
|
||||
(builtins.readFile ../configs/node-exporter-full.json)}/dashboards";
|
||||
options.foldersFromFilesStructure = false;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
8
modules/tailscale.nix
Normal file
8
modules/tailscale.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
services.tailscale = {
|
||||
enable = true;
|
||||
authKeyFile = config.age.secrets.tailscale-auth-key.path;
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue