init minecraft server configs!

This commit is contained in:
Jet Pham 2026-03-13 23:36:43 -07:00
commit 64820d502a
No known key found for this signature in database
23 changed files with 24719 additions and 0 deletions

61
modules/backup.nix Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,8 @@
{ config, pkgs, ... }:
{
services.tailscale = {
enable = true;
authKeyFile = config.age.secrets.tailscale-auth-key.path;
};
}