From 9cb871275ac20716754345e842f6a50fb07a85a3 Mon Sep 17 00:00:00 2001 From: alisceon Date: Sun, 24 May 2026 17:15:28 +0200 Subject: [PATCH] vibed improved gc --- lib/commands.nix | 10 +++ nixos/modules/base.nix | 40 +++++++++- nixos/modules/profiles/workstation.nix | 38 +++++++++- util/dev_flake_gc.sh | 101 +++++++++++++++++++++++++ util/system_notify.sh | 23 ++++++ 5 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 util/dev_flake_gc.sh create mode 100644 util/system_notify.sh diff --git a/lib/commands.nix b/lib/commands.nix index 7e766f4..476b304 100644 --- a/lib/commands.nix +++ b/lib/commands.nix @@ -1,6 +1,15 @@ { pkgs }: let swaymsg = "${pkgs.sway}/bin/swaymsg"; + systemNotify = pkgs.writeShellApplication { + name = "system-notify"; + runtimeInputs = [ + pkgs.coreutils + pkgs.libnotify + pkgs.util-linux + ]; + text = builtins.readFile ../util/system_notify.sh; + }; in { uwsm = "${pkgs.uwsm}/bin/uwsm-app --"; @@ -9,6 +18,7 @@ in lock = "${pkgs.swaylock}/bin/swaylock --daemonize"; term = "${pkgs.foot}/bin/footclient"; notify = "${pkgs.libnotify}/bin/notify-send"; + "system-notify" = "${systemNotify}/bin/system-notify"; nag = "${pkgs.sway}/bin/swaynag --edge bottom"; dmenu = "${pkgs.rofi-unwrapped}/bin/rofi"; espanso = "${pkgs.espanso-wayland}/bin/espanso cmd"; diff --git a/nixos/modules/base.nix b/nixos/modules/base.nix index 4abe5fe..c4ec01f 100644 --- a/nixos/modules/base.nix +++ b/nixos/modules/base.nix @@ -2,6 +2,15 @@ let autoUpgradeUser = "alisceon"; flakeRef = "path:${repoLocalPath}"; + devFlakeGarbageCollect = pkgs.writeShellApplication { + name = "dev-flake-garbage-collect"; + runtimeInputs = [ + pkgs.coreutils + pkgs.findutils + pkgs.gnugrep + ]; + text = builtins.readFile ../../util/dev_flake_gc.sh; + }; in { boot = { @@ -34,9 +43,34 @@ in runGarbageCollection = true; }; - systemd.services.nixos-upgrade.preStart = '' - ${pkgs.util-linux}/bin/runuser -u ${autoUpgradeUser} -- ${lib.getExe config.nix.package} flake update --flake ${lib.escapeShellArg flakeRef} - ''; + systemd = { + services = { + nixos-upgrade.preStart = '' + ${pkgs.util-linux}/bin/runuser -u ${autoUpgradeUser} -- ${lib.getExe config.nix.package} flake update --flake ${lib.escapeShellArg flakeRef} + ''; + + dev-flake-garbage-collect = { + description = "Remove stale development flake caches and build symlinks"; + wants = [ "nix-gc.service" ]; + before = [ "nix-gc.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = lib.getExe devFlakeGarbageCollect; + Nice = 10; + IOSchedulingClass = "idle"; + }; + }; + }; + + timers.dev-flake-garbage-collect = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = "weekly"; + Persistent = true; + RandomizedDelaySec = "3h"; + }; + }; + }; nix = { settings = { diff --git a/nixos/modules/profiles/workstation.nix b/nixos/modules/profiles/workstation.nix index c8ed2d6..895e1d6 100644 --- a/nixos/modules/profiles/workstation.nix +++ b/nixos/modules/profiles/workstation.nix @@ -1,4 +1,12 @@ -{ pkgs, pkgs-unstable, ... }: +{ pkgs, pkgs-unstable, lib, ... }: +let + commands = import ../../../lib/commands.nix { inherit pkgs; }; + systemNotify = commands."system-notify"; + notify = urgency: title: body: + "${systemNotify} ${lib.escapeShellArgs [ urgency title body ]}"; + notifyFailure = title: service: + "${systemNotify} ${lib.escapeShellArgs [ "critical" title ]} \"${service} ended with: $SERVICE_RESULT\""; +in { boot = { plymouth = { @@ -53,6 +61,34 @@ 8888 ]; + systemd.services = { + nixos-upgrade = { + preStart = lib.mkBefore '' + ${notify "normal" "System update started" "Updating flake inputs and preparing the NixOS switch."} + ''; + postStop = '' + if [ "$SERVICE_RESULT" = "success" ]; then + ${notify "normal" "System update finished" "The automated NixOS update completed successfully."} + else + ${notifyFailure "System update failed" "nixos-upgrade.service"} + fi + ''; + }; + + nix-gc = { + preStart = '' + ${notify "normal" "Garbage collection started" "Cleaning old Nix generations and unreferenced store paths."} + ''; + postStop = '' + if [ "$SERVICE_RESULT" = "success" ]; then + ${notify "normal" "Garbage collection finished" "Nix store garbage collection completed successfully."} + else + ${notifyFailure "Garbage collection failed" "nix-gc.service"} + fi + ''; + }; + }; + environment = { systemPackages = [ pkgs-unstable.discord diff --git a/util/dev_flake_gc.sh b/util/dev_flake_gc.sh new file mode 100644 index 0000000..5942fe1 --- /dev/null +++ b/util/dev_flake_gc.sh @@ -0,0 +1,101 @@ +set -o pipefail + +retention_days="30" +scan_roots=( + / + /home + /root + /srv + /opt + /tmp + /var/tmp + /var/lib +) + +root_seen() { + local candidate="$1" + local seen + + for seen in "${seen_roots[@]}"; do + [ "$candidate" = "$seen" ] && return 0 + done + + return 1 +} + +has_recent_activity() { + local path="$1" + + find "$path" \ + -xdev \ + \( -name .git -o -name .hg -o -name .svn -o -name node_modules -o -name target \) -prune \ + -o -mindepth 1 -mtime "-$retention_days" -print -quit 2>/dev/null \ + | grep -q . +} + +is_nix_store_symlink() { + local link="$1" + local target + + target="$(readlink "$link" 2>/dev/null || true)" + case "$target" in + /nix/store/*) return 0 ;; + *) return 1 ;; + esac +} + +cleanup_direnv() { + local direnv_dir="$1" + local project_dir + + project_dir="$(dirname "$direnv_dir")" + + if [ ! -e "$project_dir/flake.nix" ] \ + && [ ! -e "$direnv_dir/flake-profile" ] \ + && [ ! -e "$direnv_dir/gcroots" ]; then + return + fi + + if has_recent_activity "$project_dir"; then + return + fi + + echo "Removing stale nix-direnv cache: $direnv_dir" + rm -rf --one-file-system "$direnv_dir" +} + +cleanup_result_link() { + local link="$1" + + if ! is_nix_store_symlink "$link"; then + return + fi + + echo "Removing stale Nix build result symlink: $link" + rm -f "$link" +} + +seen_roots=() + +for root in "${scan_roots[@]}"; do + [ -d "$root" ] || continue + root="$(readlink -f "$root")" + root_seen "$root" && continue + seen_roots+=("$root") + + find "$root" \ + -xdev \ + \( -path /nix -o -path /proc -o -path /sys -o -path /dev -o -path /run -o -path /boot \) -prune \ + -o -type d -name .direnv -mtime "+$retention_days" -print0 2>/dev/null \ + | while IFS= read -r -d "" direnv_dir; do + cleanup_direnv "$direnv_dir" + done + + find "$root" \ + -xdev \ + \( -path /nix -o -path /proc -o -path /sys -o -path /dev -o -path /run -o -path /boot \) -prune \ + -o -type l \( -name result -o -name "result-*" \) -mtime "+$retention_days" -print0 2>/dev/null \ + | while IFS= read -r -d "" link; do + cleanup_result_link "$link" + done +done diff --git a/util/system_notify.sh b/util/system_notify.sh new file mode 100644 index 0000000..2c307f4 --- /dev/null +++ b/util/system_notify.sh @@ -0,0 +1,23 @@ +set -o pipefail + +target_user="${SYSTEM_NOTIFY_USER:-alisceon}" +urgency="${1:-normal}" +title="${2:-System task}" +body="${3:-}" + +uid="$(id -u "$target_user" 2>/dev/null || true)" +[ -n "$uid" ] || exit 0 + +runtime_dir="/run/user/$uid" +bus="$runtime_dir/bus" +[ -S "$bus" ] || exit 0 + +runuser -u "$target_user" -- env \ + XDG_RUNTIME_DIR="$runtime_dir" \ + DBUS_SESSION_BUS_ADDRESS="unix:path=$bus" \ + notify-send \ + --app-name="NixOS maintenance" \ + --urgency="$urgency" \ + "$title" \ + "$body" \ + || exit 0