diff --git a/dry_run.zsh b/dry_run.zsh index f350dab..1a184db 100755 --- a/dry_run.zsh +++ b/dry_run.zsh @@ -17,6 +17,7 @@ configs=( "nixos-build-machine" "nixos-logs" "nixos-mail" + "nixos-minecraft" ) if [[ -n "$1" ]]; then diff --git a/flake.lock b/flake.lock index f732214..06145b8 100644 --- a/flake.lock +++ b/flake.lock @@ -1,6 +1,58 @@ { "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "nix-minecraft": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs", + "systems": "systems" + }, + "locked": { + "lastModified": 1773802295, + "narHash": "sha256-luPLLgS8VR2fHo3xT04KbJm0RU2wep6SDh3smwF8e5E=", + "owner": "Infinidoge", + "repo": "nix-minecraft", + "rev": "41870283e080c46a6d33b6c3b3923e90348254c3", + "type": "github" + }, + "original": { + "owner": "Infinidoge", + "repo": "nix-minecraft", + "type": "github" + } + }, "nixpkgs": { + "locked": { + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1770770419, "narHash": "sha256-iKZMkr6Cm9JzWlRYW/VPoL0A9jVKtZYiU4zSrVeetIs=", @@ -18,7 +70,23 @@ }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nix-minecraft": "nix-minecraft", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index be591ff..3d10608 100644 --- a/flake.nix +++ b/flake.nix @@ -3,11 +3,13 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nix-minecraft.url = "github:Infinidoge/nix-minecraft"; }; - outputs = { + outputs = inputs @ { self, nixpkgs, + nix-minecraft, ... }: let system = "x86_64-linux"; @@ -62,6 +64,15 @@ inherit system; modules = [./nix-system-configs/modules/system/mail-server.nix]; }; + + "nixos-minecraft" = nixpkgs.lib.nixosSystem { + inherit system; + modules = [ + ./nix-system-configs/modules/system/minecraft.nix + nix-minecraft.nixosModules.minecraft-servers + {nixpkgs.overlays = [inputs.nix-minecraft.overlay];} + ]; + }; }; }; } diff --git a/nix-system-configs/modules/flake_files/minecraft/flake.nix b/nix-system-configs/modules/flake_files/minecraft/flake.nix new file mode 100644 index 0000000..ad93e47 --- /dev/null +++ b/nix-system-configs/modules/flake_files/minecraft/flake.nix @@ -0,0 +1,27 @@ +{ + description = "Homelab running Minecraft"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/release-25.11"; + nix-minecraft.url = "github:Infinidoge/nix-minecraft"; + }; + + outputs = inputs @ { + self, + nixpkgs, + nix-minecraft, + ... + }: { + nixosConfigurations."edi" = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ./configuration.nix + ./minecraft.nix + nix-minecraft.nixosModules.minecraft-servers + { + nixpkgs.overlays = [inputs.nix-minecraft.overlay]; + } + ]; + }; + }; +} diff --git a/nix-system-configs/modules/scripts/pull.zsh b/nix-system-configs/modules/scripts/pull.zsh index 0e2775b..0b236c3 100755 --- a/nix-system-configs/modules/scripts/pull.zsh +++ b/nix-system-configs/modules/scripts/pull.zsh @@ -181,10 +181,12 @@ else exit 1 fi -# Step 4: Rebuild system -print_info "Rebuilding NixOS system..." +# Step 4: Rebuild system using local flake +# Compute repository root (three levels up from this script: .../nix-system-configs/modules/scripts -> repo root) +FLAKE_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +print_info "Rebuilding NixOS system using flake at ${BOLD}${FLAKE_ROOT}${RESET}..." echo "" -if sudo nixos-rebuild switch --upgrade-all; then +if sudo NIX_CONFIG='experimental-features = nix-command flakes' nixos-rebuild switch --upgrade-all --flake "${FLAKE_ROOT}#${selected_system}"; then print_success "System rebuild completed successfully!" else print_error "System rebuild failed" diff --git a/nix-system-configs/modules/system/minecraft.nix b/nix-system-configs/modules/system/minecraft.nix new file mode 100644 index 0000000..e9a60e7 --- /dev/null +++ b/nix-system-configs/modules/system/minecraft.nix @@ -0,0 +1,160 @@ +{ + config, + pkgs, + lib, + ... +}: let + choose = paths: builtins.head (builtins.filter (p: builtins.pathExists p) paths); + # Domain and Cloudflare DDNS records configured here. Update this list to add/remove records. + domain = "prg-radio.org"; +in { + options.local = { + hostname = lib.mkOption { + type = lib.types.str; + default = "nixos-default"; + description = "System hostname"; + }; + username = lib.mkOption { + type = lib.types.str; + default = "user"; + description = "Primary user username"; + }; + userDescription = lib.mkOption { + type = lib.types.str; + default = "NixOS User"; + description = "Primary user description"; + }; + address = lib.mkOption { + type = lib.types.str; + default = "10.1.1.100"; + description = "Static IP address"; + }; + }; + + imports = [ + (choose [./modules/desktop-manager/sway_greetd_homemanager.nix ../desktop-manager/sway_greetd_homemanager.nix]) + (choose [./modules/local/hostname_username.nix ../local/hostname_username.nix]) + (choose [./modules/local/networking_local.nix ../local/networking_local.nix]) + (choose [./modules/bootloader/seabios-assigned-iso-at-birth.nix ../bootloader/seabios-assigned-iso-at-birth.nix]) + (choose [./modules/lix-default.nix ../lix-default.nix]) + # Optionally: (choose [ ./modules/toolsets/remote_building.nix ../toolsets/remote_building.nix ]) + ]; + + config = { + local.hostname = "nixos-minecraft"; + local.username = "minecraftprg"; + local.userDescription = "NixOS PRG Minecraft Service"; + local.address = "10.1.1.244"; + + networking.firewall.allowedTCPPorts = [25565 24454]; + networking.firewall.allowedUDPPorts = [25565 24454]; + + # 24454 - https://modrepo.de/minecraft/voicechat/wiki/server_setup_self_hosted !!! + # This is the VoiceChat mod in Minecraft + + # --------------------------------------------------------------------------- + # Minecraft server configuration (nix-minecraft) + # --------------------------------------------------------------------------- + services.minecraft-servers = { + enable = true; + eula = true; + openFirewall = true; + + servers.fabric-modded = { + enable = true; + autoStart = true; + restart = "always"; + + package = pkgs.fabricServers.fabric-1_21_11.override { + loaderVersion = "0.18.4"; + }; + + jvmOpts = lib.concatStringsSep " " [ + "-Xms6048M" + # Use 120GB of RAM + "-Xmx12288M" + ]; + + serverProperties = { + server-port = 25565; + motd = "PRG Fabric Modded Server"; + difficulty = "hard"; + gamemode = "survival"; + max-players = 64; + online-mode = true; + white-list = false; + spawn-protection = 4; + view-distance = 10; + simulation-distance = 8; + enable-command-block = false; + enable-rcon = false; + }; + + # Grant operator privileges to the user with the given UUID. + # Update `name` to the player's Minecraft username if you know it. + operators = { + username1 = { + uuid = "5e52c4ba-9b1a-469b-8f68-b9d684eae31d"; + level = 4; # 4 = highest permission (operator) + bypassesPlayerLimit = true; + }; + }; + + symlinks = { + mods = pkgs.linkFarmFromDrvs "mods" ( + builtins.attrValues { + Fabric-API = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/i5tSkVBH/fabric-api-0.141.3%2B1.21.11.jar"; + sha512 = "c20c017e23d6d2774690d0dd774cec84c16bfac5461da2d9345a1cd95eee495b1954333c421e3d1c66186284d24a433f6b0cced8021f62e0bfa617d2384d0471"; + }; + Lithium = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/gvQqBUqZ/versions/Ow7wA0kG/lithium-fabric-0.21.4%2Bmc1.21.11.jar"; + sha512 = "f14a5c3d2fad786347ca25083f902139694f618b7c103947f2fd067a7c5ee88a63e1ef8926f7d693ea79ed7d00f57317bae77ef9c2d630bf5ed01ac97a752b94"; + }; + + LetMeDespawn = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/vE2FN5qn/versions/7gmpSYHk/LetMeDespawn-1.21.11-x-fabric-1.6.2.jar"; + sha512 = "e0a03faef8bc3e94de34be71b0e0c3a3129a65baf2290483d64637547bc6008b8658231be268342b98e4f0405c57e64b1a84c42185fbd043bec39c185a71cb48"; + }; + + Clumps = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/Wnxd13zP/versions/OgBE8Rz4/Clumps-fabric-1.21.11-29.0.0.1.jar"; + sha512 = "3cff3cd2d600a6d84030b38ce6244143d13774d5287627bb7312adae5edc7ae2d9151a2c9c39a00681c354d549b0a62ac48c0077ba586cc10c00d32f39e87f18"; + }; + + FerriteCore = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/uXXizFIs/versions/Ii0gP3D8/ferritecore-8.2.0-fabric.jar"; + sha512 = "3210926a82eb32efd9bcebabe2f6c053daf5c4337eebc6d5bacba96d283510afbde646e7e195751de795ec70a2ea44fef77cb54bf22c8e57bb832d6217418869"; + }; + + Terralith = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/8oi3bsk5/versions/94lWUKHj/Terralith_1.21.11_v2.6.0_Fabric.jar"; + sha512 = "3513374822c9181e2ec6e95ea04e0533d837934b6fe02165f3bc1b7752eff14972d094fce840355928ffa27844bf456ca6bb2cc968c5ad147601a64de1005c09"; + }; + + Chunky = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/fALzjamp/versions/1CpEkmcD/Chunky-Fabric-1.4.55.jar"; + sha512 = "3be0e049e3dea6256b395ccb1f7dccc9c6b23cb7b1f6a717a7cd1ca55f9dbda489679df32868c72664ebb28ca05f2c366590d1e1a11f0dc5f69f947903bad833"; + }; + + AppleSkin = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/EsAfCjCV/versions/59ti1rvg/appleskin-fabric-mc1.21.11-3.0.8.jar"; + sha512 = "d32206cb8d6fac7f0b579f7269203135777283e1639ccb68f8605e9f5469b5b54305fd36ba82c64b48b89ae4f1a38501bfb5827284520c3ec622d95edcfa34de"; + }; + + SimpleVoiceChat = pkgs.fetchurl { + url = "https://cdn.modrinth.com/data/9eGKb6K1/versions/pFTZ8sqQ/voicechat-fabric-1.21.11-2.6.12.jar"; + sha512 = "afc78e3d8ca463fb783b00ec3d4cb938ff9249f088d077f8cd75f6c846419e0fe4916061f57a0b188a5c28565c934623c52113f476e69e82aecabaffc8e8fdcc"; + }; + } + ); + }; + }; + }; + + # The nix-minecraft module creates the `minecraft` user/group automatically. + users.users.minecraftprg.extraGroups = ["minecraft"]; + + system.stateVersion = "25.11"; + }; +} diff --git a/nix-system-configs/modules/system/traefik.nix b/nix-system-configs/modules/system/traefik.nix index 7b50376..af4fae0 100644 --- a/nix-system-configs/modules/system/traefik.nix +++ b/nix-system-configs/modules/system/traefik.nix @@ -7,7 +7,7 @@ choose = paths: builtins.head (builtins.filter (p: builtins.pathExists p) paths); # Domain and Cloudflare DDNS records configured here. Update this list to add/remove records. domain = "prg-radio.org"; - records = ["git" "grafana" "anubis" "wavelog" "partdb" "mail" "mailadmin" "@" "test"]; + records = ["git" "grafana" "anubis" "wavelog" "partdb" "mail" "mailadmin" "@" "test" "minecraft"]; recordsStr = lib.concatStringsSep " " records; zoneId = "9fde8d0fa53502f2d1b7e0b1d3765d49"; envFile = "/home/traefikprg/cloudflare/cloudflare.env"; @@ -292,6 +292,16 @@ in { # implicit TLS IMAP (port 993) - passthrough to backend address = "[::]:993"; }; + + # Minecraft TCP entrypoint (Minecraft Java Edition default port) + minecraft = { + address = "[::]:25565"; + }; + + # Minecraft VoiceChat UDP entrypoint (VoiceChat mod default port) + minecraft-voice = { + address = "[::]:24454/udp"; + }; }; log = { level = "INFO"; @@ -448,6 +458,13 @@ in { mail-webadmin.loadBalancer = { servers = [{url = "http://10.1.1.15:8081";}]; }; + + # Minecraft service (plain TCP on 25565) + minecraft.loadBalancer = { + servers = [ + {url = "http://10.1.1.244:25565";} + ]; + }; }; # TCP routing for TeamSpeak @@ -509,6 +526,14 @@ in { entryPoints = ["imaps"]; tls = {passthrough = true;}; }; + + # TCP router for Minecraft (plain TCP on 25565) + minecraft = { + # Use catch-all SNI rule because Minecraft clients don't use TLS/SNI + rule = "HostSNI(`*`)"; + service = "minecraft"; + entryPoints = ["minecraft"]; + }; }; tcp.services = { @@ -543,6 +568,13 @@ in { proxyProtocol = {version = 2;}; # Add this line servers = [{address = "10.1.1.15:993";}]; }; + + # TCP service for Minecraft + minecraft.loadBalancer = { + servers = [ + {address = "10.1.1.244:25565";} + ]; + }; }; # UDP routing for TeamSpeak voice @@ -551,6 +583,12 @@ in { entryPoints = ["teamspeak-voice"]; service = "teamspeak-voice"; }; + + # UDP router for Minecraft VoiceChat mod + minecraft-voice = { + entryPoints = ["minecraft-voice"]; + service = "minecraft-voice"; + }; }; udp.services = { @@ -559,6 +597,12 @@ in { {address = "10.1.1.248:9987";} ]; }; + + minecraft-voice.loadBalancer = { + servers = [ + {address = "10.1.1.244:24454";} + ]; + }; }; }; }; @@ -817,8 +861,8 @@ in { systemd.services.prg-cloudflare-ddns-updater = { description = "PRG Cloudflare DDNS updater"; - after = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; + after = ["network-online.target"]; + wantedBy = ["multi-user.target"]; serviceConfig = { Type = "oneshot"; ExecStart = "${pkgs.bash}/bin/bash /etc/cloudflare-ddns/update.sh"; @@ -830,10 +874,10 @@ in { systemd.timers.prg-cloudflare-ddns-updater = { description = "Run PRG Cloudflare DDNS updater hourly"; - wantedBy = [ "timers.target" ]; + wantedBy = ["timers.target"]; timerConfig = { - OnBootSec = "1m"; # run shortly after boot - OnUnitActiveSec = "1h"; # then every hour + OnBootSec = "1m"; # run shortly after boot + OnUnitActiveSec = "1h"; # then every hour AccuracySec = "1m"; }; };