From a8568872bf7c54c2cbbeea7c1fce8709f8d7ea5a Mon Sep 17 00:00:00 2001 From: Tomas Rivera <137088692+Totorile1@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:40:52 +0100 Subject: [PATCH] added custom-trimmer for removing old nix generations --- hosts/laptop/configuration.nix | 2 +- hosts/laptop/home.nix | 1 + modules/scripts/trimmer.nix | 266 +++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 modules/scripts/trimmer.nix diff --git a/hosts/laptop/configuration.nix b/hosts/laptop/configuration.nix index 378888c..a33914f 100755 --- a/hosts/laptop/configuration.nix +++ b/hosts/laptop/configuration.nix @@ -142,7 +142,7 @@ qt.enable = true; nix.gc = { automatic = true; dates = "weekly"; - options = "--delete-older-than 30d"; # every week delete generations older than a month + options = "--delete-older-than 7d"; # every week delete generations older than a month }; # Install firefox. diff --git a/hosts/laptop/home.nix b/hosts/laptop/home.nix index 26503cf..787d31d 100755 --- a/hosts/laptop/home.nix +++ b/hosts/laptop/home.nix @@ -75,6 +75,7 @@ (pkgs.callPackage ../../modules/scripts/btm.nix {}) (pkgs.callPackage ../../modules/scripts/manix.nix {}) (pkgs.callPackage ../../modules/scripts/man.nix {}) + (pkgs.callPackage ../../modules/scripts/trimmer.nix {}) #pkgs pkgs.gruvbox-gtk-theme # # Adds the 'hello' command to your environment. It prints a friendly diff --git a/modules/scripts/trimmer.nix b/modules/scripts/trimmer.nix new file mode 100644 index 0000000..8baa855 --- /dev/null +++ b/modules/scripts/trimmer.nix @@ -0,0 +1,266 @@ +# https://gist.github.com/MaxwellDupre/3077cd229490cf93ecab08ef2a79c852 +{ + writeShellApplication, + nix, + ... +}: +writeShellApplication { + name = "custom-trimmer"; + runtimeInputs = [ + nix + ]; + text = '' +set -euo pipefail + +## Defaults +keepGensDef=30; keepDaysDef=7 +keepGens=$keepGensDef; keepDays=$keepDaysDef + +## Usage +usage () { + printf "Usage:\n\t ./trim-generations.sh \n\n +(defaults are: Keep-Gens=%d Keep-Days=%d Profile=user)\n\n" "$keepGensDef" "$keepDaysDef" + printf "If you enter any parameters, you must enter all three, or none to use defaults.\n" + printf "Example:\n\t trim-generations.sh 15 10 home-manager\n" + printf " this will work on the home-manager profile and keep all generations from the\n" + printf "last 10 days, and keep at least 15 generations no matter how old.\n" + printf "\nProfiles available are:\tuser, home-manager, channels, system (root)\n" + printf "\n-h or --help prints this help text." +} + +if [ $# -eq 1 ]; then # if help requested + if [ "$1" = "-h" ]; then + usage + exit 1; + fi + if [ "$1" = "--help" ]; then + usage + exit 2; + fi + printf "Dont recognise your option exiting..\n\n" + usage + exit 3; + + elif [ $# -eq 0 ]; then # print the defaults + printf "The current defaults are:\n Keep-Gens=%d Keep-Days=%d \n\n" "$keepGensDef" "$keepDaysDef" + read -r -p "Keep these defaults? (y/n):" answer + + case "$answer" in + [yY1] ) + printf "Using defaults..\n" + ;; + [nN0] ) printf "ok, doing nothing, exiting..\n" + exit 6; + ;; + * ) printf "%b" "Doing nothing, exiting.." + exit 7; + ;; + esac +fi + +## Handle parameters (and change if root) +if [[ $EUID -ne 0 ]]; then # if not root + profile=$(readlink /home/"$USER"/.nix-profile) +else + if [ -d /nix/var/nix/profiles/system ]; then # maybe this or the other + profile="/nix/var/nix/profiles/system" + elif [ -d /nix/var/nix/profiles/default ]; then + profile="/nix/var/nix/profiles/default" + else + echo "Cant find profile for root. Exiting" + exit 8 + fi +fi +if (( $# < 1 )); then + printf "Keeping default: %d generations OR %d days, whichever is more\n" "$keepGensDef" "$keepDaysDef" +elif [[ $# -le 2 ]]; then + printf "\nError: Not enough arguments.\n\n" >&2 + usage + exit 1 +elif (( $# > 4)); then + printf "\nError: Too many arguments.\n\n" >&2 + usage + exit 2 +else + if [ "$1" -lt 1 ]; then + printf "using Gen numbers less than 1 not recommended. Setting to min=1\n" + read -r -p "is that ok? (y/n): " answer + printf "%s" "$answer" + case "$answer" in + [yY1] ) + printf "ok, continuing..\n" + ;; + [nN0] ) + printf "ok, doing nothing, exiting..\n" + exit 6; + ;; + * ) + printf "%b" "Doing nothing, exiting.." + exit 7; + ;; + esac + fi + if [ "$2" -lt 0 ]; then + printf "using negative days number not recommended. Setting to min=0\n" + read -r -p "is that ok? (y/n): " answer + + case "$answer" in + [yY1] ) + printf "ok, continuing..\n" + ;; + [nN0] ) + printf "ok, doing nothing, exiting..\n" + exit 6; + ;; + * ) + printf "%b" "Doing nothing, exiting.." + exit 7; + ;; + esac + fi + keepGens=$1; keepDays=$2; + (( keepGens < 1 )) && keepGens=1 + (( keepDays < 0 )) && keepDays=0 + if [[ $EUID -ne 0 ]]; then + if [[ $3 == "user" ]] || [[ $3 == "default" ]]; then + profile=$(readlink /home/"$USER"/.nix-profile) + elif [[ $3 == "home-manager" ]]; then + # home-manager defaults to $XDG_STATE_HOME; otherwise, use + # `home-manager generations` and `nix-store --query --roots + # /nix/store/...` to figure out what reference is keeping the old + # generations alive. + profile="''${XDG_STATE_HOME:-$HOME/.local/state}/nix/profiles/home-manager" + elif [[ $3 == "channels" ]]; then + profile="/nix/var/nix/profiles/per-user/$USER/channels" + else + printf "\nError: Do not understand your third argument. Should be one of: (user / home-manager/ channels)\n\n" + usage + exit 3 + fi + else + if [[ $3 == "system" ]]; then + profile="/nix/var/nix/profiles/system" + elif [[ $3 == "user" ]] || [[ $3 == "default" ]]; then + profile="/nix/var/nix/profiles/default" + else + printf "\nError: Do not understand your third argument. Should be one of: (user / system)\n\n" + usage + exit 3 + fi + fi + printf "OK! \t Keep Gens = %s \t Keep Days = %s \n\n" "$keepGens" "$keepDays" + + +fi + +printf "Operating on profile: %s \t \n\n" "$profile" + +## Runs at the end, to decide whether to delete profiles that match chosen parameters. +choose () { + local default="$1" + local prompt="$2" + local answer + + read -r -p "$prompt" answer + [ -z "$answer" ] && answer="$default" + + case "$answer" in + [yY1] ) #printf "answered yes!\n" + nix-env --delete-generations -p "$profile" "''${!gens[@]}" + exit 0 + ;; + [nN0] ) printf "Ok doing nothing exiting..\n" + exit 6; + ;; + * ) printf "%b" "Unexpected answer '$answer'!" >&2 + exit 7; + ;; + esac +} # end of function choose + +# printf "profile = %s \n\n" "$profile" +## Query nix-env for generations list +#IFS=$'\n' nixGens=( $(nix-env --list-generations -p "$profile" | sed 's:^\s*::; s:\s*$::' | tr '\t' ' ' | tr -s ' ') ) +# gave errors replaced by: +mapfile -t nixGens < <( + nix-env --list-generations -p "$profile" | + sed 's:^\s*::; s:\s*$::' | + tr '\t' ' ' | + tr -s ' ' +) +timeNow=$(date +%s) + +## Get info on oldest generation +IFS=' ' read -r -a oldestGenArr <<< "''${nixGens[0]}" +oldestGen=''${oldestGenArr[0]} +oldestDate=''${oldestGenArr[1]} +printf "%-30s %s\n" "oldest generation:" "$oldestGen" +#oldestDate=''${nixGens[0]:3:19} +printf "%-30s %s\n" "oldest generation created:" "$oldestDate" +oldestTime=$(date -d "$oldestDate" +%s) +oldestElapsedSecs=$((timeNow-oldestTime)) +oldestElapsedMins=$((oldestElapsedSecs/60)) +oldestElapsedHours=$((oldestElapsedMins/60)) +oldestElapsedDays=$((oldestElapsedHours/24)) +printf "%-30s %s\n" "minutes before now:" "$oldestElapsedMins" +printf "%-30s %s\n" "hours before now:" "$oldestElapsedHours" +printf "%-30s %s\n\n" "days before now:" "$oldestElapsedDays" + +## Get info on current generation +for i in "''${nixGens[@]}"; do + IFS=' ' read -r -a iGenArr <<< "$i" + genNumber=''${iGenArr[0]} + genDate=''${iGenArr[1]} + if [[ "$i" =~ current ]]; then + currentGen=$genNumber + printf "%-30s %s\n" "current generation:" "$currentGen" + currentDate=$genDate + printf "%-30s %s\n" "current generation created:" "$currentDate" + currentTime=$(date -d "$currentDate" +%s) + currentElapsedSecs=$((timeNow-currentTime)) + currentElapsedMins=$((currentElapsedSecs/60)) + currentElapsedHours=$((currentElapsedMins/60)) + currentElapsedDays=$((currentElapsedHours/24)) + printf "%-30s %s\n" "minutes before now:" "$currentElapsedMins" + printf "%-30s %s\n" "hours before now:" "$currentElapsedHours" + printf "%-30s %s\n\n" "days before now:" "$currentElapsedDays" + fi +done + +## Compare oldest and current generations +timeBetweenOldestAndCurrent=$((currentTime-oldestTime)) +elapsedDays=$((timeBetweenOldestAndCurrent/60/60/24)) +generationsDiff=$((currentGen-oldestGen)) + +## Figure out what we should do, based on generations and options +if [[ elapsedDays -le keepDays ]]; then + printf "All generations are no more than %s days older than current generation. \nOldest gen days difference from current gen: %s \n\n\tNothing to do!\n" "$keepDays" "$elapsedDays" + exit 4; +elif [[ generationsDiff -lt keepGens ]]; then + printf "Oldest generation (%d) is only %d generations behind current (%d). \n\n\t Nothing to do!\n" "$oldestGen" "$generationsDiff" "$currentGen" + exit 5; +else + printf "\tSomething to do...\n" + declare -a gens + for i in "''${nixGens[@]}"; do + IFS=' ' read -r -a iGenArr <<< "$i" + genNumber=''${iGenArr[0]} + genDiff=$((currentGen-genNumber)) + genDate=''${iGenArr[1]} + genTime=$(date -d "$genDate" +%s) + elapsedSecs=$((timeNow-genTime)) + genDaysOld=$((elapsedSecs/60/60/24)) + if [[ genDaysOld -gt keepDays ]] && [[ genDiff -ge keepGens ]]; then + gens["$genNumber"]="$genDate, $genDaysOld day(s) old" + fi + done + printf "\nFound the following generation(s) to delete:\n" + for K in "''${!gens[@]}"; do + printf "generation %d \t ''${gens[$K]}\n" "$K" + done + printf "\n" + choose "y" "Do you want to delete these? [Y/n]: " +fi + + ''; +}