diff --git a/README.md b/README.md index f8496c7..ee0d382 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,5 @@ - `thinkpad` - Thinkpad T480, i5 8350U, 16GB RAM, 256GB NVME - `homelab` - i7 8700T, 32GB RAM, 512GB NVME, 1TB 2.5" SATA -## Todo -- Automatic backups to external drives. -- Better documentation and code structure. -- Use NixOS modules system. - ## Credits -- [orangc's flake](https://git.orangc.net/c/dots) -- [vimjoyer's tutorials](https://www.youtube.com/@vimjoyer) -- [wallpaper source](https://github.com/er2de2/catppuccin_walls/blob/master/wallpapers_png/autumn_2.0.png) \ No newline at end of file +- [orangc's flake](https://git.orangc.net/c/dots) \ No newline at end of file diff --git a/modules/hardware/misc/battery-power.nix b/modules/hardware/misc/battery-power.nix index 8c529e1..3a2ca6b 100644 --- a/modules/hardware/misc/battery-power.nix +++ b/modules/hardware/misc/battery-power.nix @@ -17,10 +17,8 @@ BAT_PCT=`${pkgs.acpi}/bin/acpi -b | ${pkgs.gnugrep}/bin/grep -P -o '[0-9]+(?=%)'` BAT_STA=`${pkgs.acpi}/bin/acpi -b | ${pkgs.gnugrep}/bin/grep -P -o '\w+(?=,)'` echo "`date` battery status:$BAT_STA percentage:$BAT_PCT" - export DISPLAY=:0 - export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus - test $BAT_PCT -le 30 && test $BAT_PCT -gt 15 && test $BAT_STA = "Discharging" && ${pkgs.libnotify}/bin/notify-send "Low Battery" "Battery remaining: $BAT_PCT%." - test $BAT_PCT -le 15 && test $BAT_STA = "Discharging" && ${pkgs.libnotify}/bin/notify-send -u critical "Low Battery" "Shutdown at 10%." + test $BAT_PCT -le 30 && test $BAT_PCT -gt 15 && test $BAT_STA = "Discharging" && DISPLAY=:0.0 ${pkgs.libnotify}/bin/notify-send "Low Battery" "Battery remaining: $BAT_PCT%." + test $BAT_PCT -le 15 && test $BAT_STA = "Discharging" && DISPLAY=:0.0 ${pkgs.libnotify}/bin/notify-send -u critical "Low Battery" "Shutdown at 10%." ''} > /tmp/cron.batt.log 2>&1" ]; }; diff --git a/modules/home/core/cli.nix b/modules/home/core/cli.nix index 4f2dbf6..86b3c08 100644 --- a/modules/home/core/cli.nix +++ b/modules/home/core/cli.nix @@ -49,20 +49,10 @@ initLua = '' vim.opt.clipboard = "unnamedplus" vim.opt.termguicolors = true - vim.g.clipboard = { - name = "OSC 52", - copy = { - ["+"] = require("vim.ui.clipboard.osc52").copy("+"), - ["*"] = require("vim.ui.clipboard.osc52").copy("*"), - }, - paste = { - ["+"] = require("vim.ui.clipboard.osc52").paste("+"), - ["*"] = require("vim.ui.clipboard.osc52").paste("*"), - }, - } require("nvim-tree").setup() vim.api.nvim_create_autocmd("VimEnter", { callback = function() + -- vim.cmd("NvimTreeOpen") vim.cmd("set nu") vim.cmd.wincmd 'p' end, diff --git a/modules/home/core/shell.nix b/modules/home/core/shell.nix index 7c69e47..2a23a42 100644 --- a/modules/home/core/shell.nix +++ b/modules/home/core/shell.nix @@ -37,9 +37,9 @@ "cd" = "z"; "sys" = "sudo systemctl --runtime"; - "sys-log" = "journalctl -o cat -f -b -u"; + "sys-log" = "journalctl -f -b -u"; "user" = "systemctl --user --runtime"; - "user-log" = "journalctl -o cat -f -b --user-unit"; + "user-log" = "journalctl -f -b --user-unit"; "ts" = "sudo tailscale"; "tsip" = "tailscale ip -4"; diff --git a/modules/system/homelab/dash.nix b/modules/system/homelab/dash.nix index e7d62bf..d973699 100644 --- a/modules/system/homelab/dash.nix +++ b/modules/system/homelab/dash.nix @@ -89,101 +89,6 @@ in { }; pages = [ - { - name = "Dashboard"; - show-mobile-header = true; - width = "slim"; - columns = [ - { - size = "small"; - widgets = [ - { - type = "monitor"; - title = "Critical Systems"; - cache = "15s"; - style = "compact"; - show-failing-only = true; - sites = map (e: { - same-tab = true; - allow-insecure = true; - title = builtins.elemAt e 0; - url = builtins.elemAt e 1; - }) monitor; - } - { - type = "dns-stats"; - title = "DNS Stats"; - service = "adguard"; - url = "http://localhost:8088/"; - hour-format = "12h"; - } - { - type = "bookmarks"; - groups = [ - { - links = [{ - same-tab = true; - title = "NixFlake"; - icon = "si:nixos"; - url = "https://flake.satr14.my.id"; - }]; - } - { - links = map (e: { - same-tab = true; - title = builtins.elemAt e 0; - icon = "si:${builtins.elemAt e 1}"; - url = builtins.elemAt e 2; - alt-status-codes = [ 401 ]; - }) bookmarks; - } - ]; - } - { - type = "to-do"; - id = "tasks"; - } - ]; - } - { - size = "full"; - widgets = [ - { - type = "server-stats"; - servers = [{ - type = "local"; - mountpoints = { - "/boot".hide = true; - "/nix/store".hide = true; - "/var/lib/vaultwarden".hide = true; - "/var/lib/private/cryptpad".hide = true; - "/var/lib/acme/proxy.satr14.my.id".hide = true; - }; - }]; - } - { - type = "monitor"; - cache = "1m"; - title = "Services"; - sites = map (e: { - same-tab = true; - allow-insecure = true; - title = builtins.elemAt e 0; - icon = "si:${builtins.elemAt e 1}"; - url = builtins.elemAt e 2; - check-url = builtins.elemAt e 3; - }) homelab.dash; - } - { - type = "docker-containers"; - title = "Containers"; - format-container-names = true; - hide-by-default = true; - } - ]; - } - ]; - } { name = "Home"; show-mobile-header = true; @@ -282,6 +187,101 @@ in { } ]; } + { + name = "Dashboard"; + show-mobile-header = true; + width = "slim"; + columns = [ + { + size = "small"; + widgets = [ + { + type = "monitor"; + title = "Critical Systems"; + cache = "15s"; + style = "compact"; + show-failing-only = true; + sites = map (e: { + same-tab = true; + allow-insecure = true; + title = builtins.elemAt e 0; + url = builtins.elemAt e 1; + }) monitor; + } + { + type = "dns-stats"; + title = "DNS Stats"; + service = "adguard"; + url = "http://localhost:8088/"; + hour-format = "12h"; + } + { + type = "bookmarks"; + groups = [ + { + links = [{ + same-tab = true; + title = "NixFlake"; + icon = "si:nixos"; + url = "https://flake.satr14.my.id"; + }]; + } + { + links = map (e: { + same-tab = true; + title = builtins.elemAt e 0; + icon = "si:${builtins.elemAt e 1}"; + url = builtins.elemAt e 2; + alt-status-codes = [ 401 ]; + }) bookmarks; + } + ]; + } + { + type = "to-do"; + id = "tasks"; + } + ]; + } + { + size = "full"; + widgets = [ + { + type = "server-stats"; + servers = [{ + type = "local"; + mountpoints = { + "/boot".hide = true; + "/nix/store".hide = true; + "/var/lib/vaultwarden".hide = true; + "/var/lib/private/cryptpad".hide = true; + "/var/lib/acme/proxy.satr14.my.id".hide = true; + }; + }]; + } + { + type = "monitor"; + cache = "1m"; + title = "Services"; + sites = map (e: { + same-tab = true; + allow-insecure = true; + title = builtins.elemAt e 0; + icon = "si:${builtins.elemAt e 1}"; + url = builtins.elemAt e 2; + check-url = builtins.elemAt e 3; + }) homelab.dash; + } + { + type = "docker-containers"; + title = "Containers"; + format-container-names = true; + hide-by-default = true; + } + ]; + } + ]; + } ]; }; }; diff --git a/modules/system/homelab/git.nix b/modules/system/homelab/git.nix index 2544f90..db30c7a 100644 --- a/modules/system/homelab/git.nix +++ b/modules/system/homelab/git.nix @@ -45,7 +45,7 @@ url = "http://localhost:5080"; #"https://git.proxy.${homelab.domain}"; tokenFile = "/mnt/data/apps/forgejo/token-runner"; labels = [ "self-hosted:host" ]; - hostPackages = with pkgs; [ bash coreutils git nix openssh bun ]; + hostPackages = with pkgs; [ bash coreutils git nix openssh nodejs ]; }; }; systemd.services = { diff --git a/modules/system/homelab/mc-backup.nix b/modules/system/homelab/mc-backup.nix new file mode 100644 index 0000000..1a2cb96 --- /dev/null +++ b/modules/system/homelab/mc-backup.nix @@ -0,0 +1,103 @@ +{ pkgs, ... }: let + serverName = "mc0-explorers-creativity"; + serviceName = "minecraft-server-${serverName}"; + backupDir = "/mnt/data/backups/mc"; + keepBackups = 7; # number of backups to retain + rconHost = "localhost"; + rconPort = "25575"; + rconPassword = "howdy"; + ntfyUrl = "http://127.0.0.1:8067"; + ntfyTopic = "mc-backup"; + + backupScript = pkgs.writeShellScriptBin "mc-backup" '' + set -euo pipefail + + rcon() { + ${pkgs.rcon-cli}/bin/rcon-cli --address "${rconHost}:${rconPort}" --password "${rconPassword}" "$@" || true + } + + BACKUP_OK=false + + on_exit() { + # Always restart the server first + echo "[mc-backup] Restarting server..." + systemctl start ${serviceName}.service + + # Notify via ntfy only on failure + if [ "$BACKUP_OK" != "true" ]; then + echo "[mc-backup] Sending failure notification..." + ${pkgs.curl}/bin/curl -s -o /dev/null \ + -H "Title: Minecraft Backup Failed" \ + -H "Priority: high" \ + -H "Tags: warning" \ + -d "Nightly backup failed at $(date '+%Y-%m-%d %H:%M:%S'). Check logs with: journalctl -u mc-backup -n 50" \ + "${ntfyUrl}/${ntfyTopic}" + fi + } + + # Always restart the server on exit, even if the script fails mid-backup + trap on_exit EXIT + + # --- Countdown warnings via RCON --- + echo "[mc-backup] Sending 5-minute warning..." + rcon "say §c[Backup] §fServer will restart in §e5 minutes §ffor a scheduled backup." + + sleep 240 + + echo "[mc-backup] Sending 1-minute warning..." + rcon "say §c[Backup] §fServer restarting in §e1 minute§f." + + sleep 50 + + echo "[mc-backup] Sending 10-second warning..." + rcon "say §c[Backup] §fServer restarting in §e10 seconds§f." + + sleep 10 + + rcon "say §c[Backup] §fShutting down now. Back shortly!" + + # --- Save world & stop --- + echo "[mc-backup] Saving world..." + rcon "save-all" + sleep 5 + + echo "[mc-backup] Stopping ${serviceName}..." + systemctl stop ${serviceName}.service + + # --- Backup --- + echo "[mc-backup] Backing up to ${backupDir}..." + mkdir -p "${backupDir}" + TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S) + XZ_OPT="-9e" tar -cJf "${backupDir}/mc-backup-$TIMESTAMP.tar.xz" \ + -C /srv/minecraft ${serverName} + + # Prune old backups, keeping the last ${toString keepBackups} + ls -t "${backupDir}"/mc-backup-*.tar.xz | tail -n +${toString (keepBackups + 1)} | xargs -r rm -- + + echo "[mc-backup] Backup complete: mc-backup-$TIMESTAMP.tar.xz" + BACKUP_OK=true + # Server restart is handled by the EXIT trap above + ''; +in { + environment.systemPackages = [ backupScript ]; + + systemd.services.mc-backup = { + description = "Nightly Minecraft server backup"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${backupScript}/bin/mc-backup"; + User = "root"; + StandardOutput = "journal"; + StandardError = "journal"; + }; + }; + + systemd.timers.mc-backup = { + description = "Nightly Minecraft backup timer"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = "daily"; # fires at 00:00:00 every day + Persistent = true; # catch up if the machine was off at midnight + }; + }; +} diff --git a/modules/system/homelab/mc.nix b/modules/system/homelab/mc.nix index 90e2e76..276ab69 100644 --- a/modules/system/homelab/mc.nix +++ b/modules/system/homelab/mc.nix @@ -1,65 +1,75 @@ { inputs, lib, pkgs, ... }: let - production = true; - ram-allocation-mb = 12288; - rcon-pass = "howdy"; + ram-allocation = "10240M"; + # auth-server = "https://mc.satr14.my.id"; # TODO: self hosted drasl server modpack = let - commit = "8523f89493ace13087eb68cd9fe3b5eb4f669440"; - path = if production then "commit/${commit}" else "branch/main"; + commit = "667aadf36aac9b0689289f4988a76b924bbb9cbc"; in pkgs.fetchPackwizModpack { - packHash = "sha256-xB9Oc/aneogSQ9r7L42vyVM6xwq+QkoTaXYNuUzeo6M="; - url = "https://git.satr14.my.id/satr14/server-modpack/raw/${path}/pack.toml"; + packHash = "sha256-sNWuqTIpqnwxhoof5PkJXrvVE5x/wnhc3LoqomjYBNs="; + url = "https://git.satr14.my.id/satr14/server-modpack/raw/commit/${commit}/pack.toml"; }; - in { imports = [ inputs.mc.nixosModules.minecraft-servers ]; nixpkgs.overlays = [ inputs.mc.overlay ]; - powerManagement.cpuFreqGovernor = "powersave"; # performance governor causes overheating and thermal throttling, works fine with powesave - boot.kernel.sysctl = { - "vm.nr_hugepages" = (ram-allocation-mb / 2) + 512; # (heap_mb / 2MB per page) + 512 pages (1GB) for ZGC off-heap overhead - "vm.swappiness" = 10; - }; - services.minecraft-servers = { enable = true; eula = true; - managementSystem.systemd-socket.enable = true; + managementSystem.systemd-socket.enable = true; # Referenced but unset environment variable evaluates to an empty string: MAINPID # ^^^ https://github.com/Infinidoge/nix-minecraft/issues/119 - - # TODO: figure out how to set gamerules on start - # gamerules to disable: locator_bar, mob_explosion_drop_decay, (and possibly) reduced_debug_info, global_sound_events - # gamerules to enable (temporarily): noend:disable_end - servers.da-s3 = { + servers.mc0-explorers-creativity = { enable = true; autoStart = true; restart = "always"; - enableReload = production; - # extraReload = '' - # function rcon() { - # ${pkgs.rcon-cli}/bin/rcon-cli -p ${rcon-pass} $@ - # } - - # rcon "gamerule locator_bar false" - # rcon "gamerule mob_explosion_drop_decay false" - # rcon "gamerule reduced_debug_info false" - # rcon "gamerule global_sound_events false" - # ''; + enableReload = false; # NOTE: development phase, disable in production - operators = lib.mkIf (!production) { - "satr14" = { - uuid = "54441a30-fe73-46e7-adca-c476bd4fc6d2"; - bypassesPlayerLimit = true; - level = 4; - }; + package = pkgs.fabricServers.fabric-1_21_11.override { + jre_headless = pkgs.javaPackages.compiler.temurin-bin.jre-25; + loaderVersion = "0.19.2"; }; + + jvmOpts = let + flags = [ + "-Xms${ram-allocation}" + "-Xmx${ram-allocation}" + "--add-modules=jdk.incubator.vector" + + # Custom auth server + # "-Dminecraft.api.env=custom" + # "-Dminecraft.api.auth.host=${auth-server}/auth" + # "-Dminecraft.api.account.host=${auth-server}/account" + # "-Dminecraft.api.profiles.host=${auth-server}/account" + # "-Dminecraft.api.session.host=${auth-server}/session" + # "-Dminecraft.api.services.host=${auth-server}/services" + + # Aikar's GC flags (tuned for 10GB) + "-XX:+UseG1GC" + "-XX:+ParallelRefProcEnabled" + "-XX:MaxGCPauseMillis=200" + "-XX:+UnlockExperimentalVMOptions" + "-XX:+DisableExplicitGC" + "-XX:+AlwaysPreTouch" + "-XX:G1HeapWastePercent=5" + "-XX:G1MixedGCCountTarget=4" + "-XX:InitiatingHeapOccupancyPercent=15" + "-XX:G1MixedGCLiveThresholdPercent=90" + "-XX:G1RSetUpdatingPauseTimePercent=5" + "-XX:SurvivorRatio=32" + "-XX:+PerfDisableSharedMem" + "-XX:MaxTenuringThreshold=1" + "-Dusing.aikars.flags=https://mcflags.emc.gs" + "-Daikars.new.flags=true" + "-XX:G1NewSizePercent=30" + "-XX:G1MaxNewSizePercent=40" + "-XX:G1HeapRegionSize=8M" + "-XX:G1ReservePercent=20" + ]; + in lib.concatStringsSep " " flags; serverProperties = { - # server-ip = "localhost"; server-port = 25565; - server-name = "Minecraft Server"; - motd = "§lSeason 3§r - §dExplorers Creativity 🔥"; - log-ips = false; # TODO: figure out how to get ips from cloudflared tunnel + server-name = "Digit Association"; + motd = "§lSeason 3 TESTING§r - §dExplorers Creativity 🔥"; difficulty = "normal"; gamemode = "survival"; @@ -74,41 +84,26 @@ in { allow-flight = false; player-idle-timeout = 0; - view-distance = 12; - simulation-distance = 4; + # resource-pack = "https://cdn.satr14.my.id/public/fullslide-1.21.11.zip"; + # resource-pack-sha1 = "e0958dcef5755286f390c22280700c471ec34a65"; + # resource-pack-enforce = false; + + simulation-distance = 16; + view-distance = 4; enable-rcon = true; sync-chunk-writes = false; - "rcon.password" = rcon-pass; + "rcon.password" = "howdy"; "rcon.port" = 25575; }; - symlinks = lib.mapAttrs' - (name: _: lib.nameValuePair "mods/${name}" "${modpack}/mods/${name}") - (builtins.readDir "${modpack}/mods"); - - package = pkgs.fabricServers.fabric-1_21_11.override { - jre_headless = pkgs.javaPackages.compiler.temurin-bin.jdk-25; - loaderVersion = "0.19.2"; + symlinks = { + # "resources/datapack/required" = "${modpack}/datapacks"; + "mods" = "${modpack}/mods"; + + # "server-icon.png" = "${modpack}/server-icon.png"; + # "config" = ""; }; - - jvmOpts = let flags = [ - "-Xms${toString ram-allocation-mb}M" - "-Xmx${toString ram-allocation-mb}M" - - "-XX:+UseZGC" # Use ZGC (requires Java v25+, 8+ CPU cores, 10GB+ RAM) - "-XX:+UseCompactObjectHeaders" # Use compact object headers (requires Java v16+, saves a couple of bits per object) - - "--add-modules=jdk.incubator.vector" # Exposes SIMD instructions (requires full JDK, useful with performance mods) - "-XX:+UseLargePages" # Large pages support (requires hugepages configured on the system) - "-XX:+AlwaysPreTouch" # Pre-allocates memory on startup, OS claims it immediately for JVM instead of negotiating it - "-XX:+DisableExplicitGC" # Disables mods from manually invoking the GC - "-XX:+PerfDisableSharedMem" # Disables constant /tmp writes for JVM metrics - "-XX:ZAllocationSpikeTolerance=5" # Helps when server is active with many players - "-XX:SoftMaxHeapSize=${toString (ram-allocation-mb - 2048)}M" # Leave 2GB headroom - "-XX:ZCollectionInterval=1" # Force a GC cycle at minimum every second - "-XX:ConcGCThreads=8" # Threads ZGC uses for concurrent work - ]; in lib.concatStringsSep " " flags; }; }; } \ No newline at end of file diff --git a/modules/system/misc/utilities.nix b/modules/system/misc/utilities.nix index a637e8d..de5d35e 100644 --- a/modules/system/misc/utilities.nix +++ b/modules/system/misc/utilities.nix @@ -8,7 +8,6 @@ ntfs3g exfatprogs smartmontools - rclone ncdu ventoy-full-qt diff --git a/modules/system/server.nix b/modules/system/server.nix index a2cef9f..0916649 100644 --- a/modules/system/server.nix +++ b/modules/system/server.nix @@ -26,6 +26,7 @@ in { ./homelab/ai.nix ./homelab/db.nix ./homelab/mc.nix + ./homelab/mc-backup.nix ./core/swapfile.nix ./core/oom.nix @@ -35,17 +36,14 @@ in { users.users.root.openssh.authorizedKeys.keys = homelab.ssh-keys; - services = { - netbird.enable = true; - tailscale = { - enable = true; - authKeyFile = "/mnt/data/apps/tailscale/authkey"; - useRoutingFeatures = "server"; - extraUpFlags = ts-flags; - extraSetFlags = ts-flags; - }; + services.tailscale = { + enable = true; + authKeyFile = "/mnt/data/apps/tailscale/authkey"; + useRoutingFeatures = "server"; + extraUpFlags = ts-flags; + extraSetFlags = ts-flags; }; - + virtualisation = { oci-containers.backend = "docker"; docker = { diff --git a/modules/system/user.nix b/modules/system/user.nix index 5f50a54..44910e9 100644 --- a/modules/system/user.nix +++ b/modules/system/user.nix @@ -7,7 +7,6 @@ shell = pkgs.zsh; extraGroups = [ "networkmanager" - "minecraft" "wheel" "dialout" "libvirtd"