NixOS Generations Trimmer: Difference between revisions

imported>Nix
Page to help with generations cleanup
 
imported>Mike1
m New confirm option added
 
(9 intermediate revisions by 2 users not shown)
Line 13: Line 13:
* If you are a normal user, you can run on either the <code>home-manager</code> or <code>channels</code> profiles too.
* If you are a normal user, you can run on either the <code>home-manager</code> or <code>channels</code> profiles too.


=== Example script output ===
== Script (trim-generations.sh) ==


==== Without parameters (defaults) ====
The script is available here: [https://gist.github.com/MaxwellDupre/3077cd229490cf93ecab08ef2a79c852 Github Gist - NixOS: trim-generations.sh]
 
== Example script output ==
 
=== Without parameters (defaults) ===


<syntaxHighlight lang=bash>
<syntaxHighlight lang=bash>
❯ ./trim-generations.sh     
❯ ./trim-generations.sh     
Keeping default: 10 generations OR 7 days, whichever is more
Operating on profile: /nix/var/nix/profiles/per-user/user/profile


oldest generation:            27
The current defaults are:
oldest generation created:    2021-09-24
Keep-Gens=30 Keep-Days=30
minutes before now:            3918
 
hours before now:              65
Keep these defaults? (y/n):y
days before now:              2
Using defaults..
Keeping default: 30 generations OR 30 days, whichever is more
Operating on profile:    /nix/var/nix/profiles/per-user/dougal/profile
 
oldest generation:            75
oldest generation created:    2023-08-05
minutes before now:            46740
hours before now:              779
days before now:              32
 
current generation:            217
current generation created:    2023-09-06
minutes before now:            660
hours before now:              11
days before now:              0
 
        Something to do...


current generation:           33
Found the following generation(s) to delete:
current generation created:   2021-09-25
generation 75    2023-08-05, 32 day(s) old
generation 76    2023-08-05, 32 day(s) old
generation 77    2023-08-05, 32 day(s) old
generation 78    2023-08-05, 32 day(s) old
generation 79    2023-08-05, 32 day(s) old
generation 80    2023-08-05, 32 day(s) old
generation 81    2023-08-05, 32 day(s) old
generation 82    2023-08-05, 32 day(s) old
generation 83    2023-08-05, 32 day(s) old
generation 84    2023-08-05, 32 day(s) old
generation 85    2023-08-05, 32 day(s) old
generation 86   2023-08-05, 32 day(s) old


All generations are no more than 7 days older than current generation.
Do you want to delete these? [Y/n]:  
Oldest gen difference from current gen: 1


Nothing to do!
</syntaxHighlight>
</syntaxHighlight>


==== With parameters ====
=== With parameters ===


<syntaxHighlight lang=bash>
<syntaxHighlight lang=bash>
Line 66: Line 93:
</syntaxHighlight>
</syntaxHighlight>


==== With parameters (not enough though) ====
=== With parameters (not enough though) ===


Example if you accidentally don't specify all parameters / arguments.
Example if you accidentally don't specify all parameters / arguments.
Line 76: Line 103:


Usage:
Usage:
trim-generations.sh (defaults are: Keep-Gens=10 Keep-Days=7 Profile=user)
        ./trim-generations.sh <keep-gernerations> <keep-days> <profile>


If you enter any parameters, you must enter all three.


(defaults are: Keep-Gens=30 Keep-Days=30 Profile=user)
If you enter any parameters, you must enter all three, or none to use defaults.
Example:
Example:
trim-generations.sh 15 10 home-manager
        trim-generations.sh 15 10 home-manager
... this will work on the home-manager profile and keep all generations from the last 10 days, and keep at least 15 generations no matter how old.
  this will work on the home-manager profile and keep all generations from the
last 10 days, and keep at least 15 generations no matter how old.


Profile choices available: user, home-manager, channels, system (root only)
Profiles available are: user, home-manager, channels, system (root)
 
-h or --help prints this help text.
</syntaxHighlight>
</syntaxHighlight>


==== Run as root (default) ====
=== Run as root (default) ===


<syntaxHighlight lang=bash>
<syntaxHighlight lang=bash>
❯ sudo ./trim-generations.sh                 
❯ sudo ./trim-generations.sh                 
Keeping default: 10 generations OR 7 days, whichever is more
The current defaults are:
Operating on profile: /nix/var/nix/profiles/default
Keep-Gens=30 Keep-Days=30


oldest generation:             8
Keep these defaults? (y/n):y
oldest generation created:    2021-09-04
Using defaults..
minutes before now:           32725
Keeping default: 30 generations OR 30 days, whichever is more
hours before now:              545
Operating on profile:   /nix/var/nix/profiles/system
days before now:               22


current generation:           8
oldest generation:             65
current generation created:   2021-09-04
oldest generation created:     2023-08-05
minutes before now:            46727
hours before now:              778
days before now:              32


All generations are no more than 7 days older than current generation.  
current generation:            102
Oldest gen days difference from current gen: 0
current generation created:    2023-09-04
minutes before now:            3527
hours before now:              58
days before now:              2
 
All generations are no more than 30 days older than current generation.  
Oldest gen days difference from current gen: 30


Nothing to do!
        Nothing to do!
</syntaxHighlight>
</syntaxHighlight>


==== Run as root (on system profile) ====
=== Run as root (on system profile) ===


<syntaxHighlight lang=bash>
<syntaxHighlight lang=bash>
Line 129: Line 169:
</syntaxHighlight>
</syntaxHighlight>


==== Run as root (wrong profile) ====
=== Run as root (wrong profile) ===


Example if you accidentally give wrong profile (no home-manager on root):
Example if you accidentally give wrong profile (no home-manager on root):
Line 139: Line 179:


Usage:
Usage:
trim-generations.sh (defaults are: Keep-Gens=3 Keep-Days=0 Profile=user)
        ./trim-generations.sh <keep-gernerations> <keep-days> <profile>
 


If you enter any parameters, you must enter all three.
(defaults are: Keep-Gens=30 Keep-Days=30 Profile=user)


If you enter any parameters, you must enter all three, or none to use defaults.
Example:
Example:
trim-generations.sh 15 10 home-manager
        trim-generations.sh 15 10 home-manager
... this will work on the home-manager profile and keep all generations from the last 10 days, and keep at least 15 generations no matter how old.
  this will work on the home-manager profile and keep all generations from the
last 10 days, and keep at least 15 generations no matter how old.


Profile choices available: user, home-manager, channels, system (root only)
Profiles available are: user, home-manager, channels, system (root)
 
-h or --help prints this help text.
</syntaxHighlight>
</syntaxHighlight>




 
[[Category:Tutorial]]
 
 
== Script (trim-generations.sh) ==
 
{{file|trim-generations.sh|bash|<nowiki>
#!/usr/bin/env bash
set -euo pipefail
 
## Defaults
keepGens=10; keepDays=7
 
## Usage
usage () {
    printf "Usage:\n\t trim-generations.sh (defaults are: Keep-Gens=$keepGens Keep-Days=$keepDays Profile=user)\n\n"
    printf "If you enter any parameters, you must enter all three.\n\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 last 10 days, and keep at least 15 generations no matter how old.\n"
    printf "\nProfile choices available: \t user, home-manager, channels, system (root only)\n"
}
 
## Handle parameters (and change if root)
if [[ $EUID -ne 0 ]]; then
    profile=$(readlink /home/$USER/.nix-profile)
else
    profile="/nix/var/nix/profiles/default"
fi
if (( $# < 1 )); then
    printf "Keeping default: 10 generations OR 7 days, whichever is more\n"
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
    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
            profile="/nix/var/nix/profiles/per-user/$USER/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 = $keepGens \t Keep Days = $keepDays\n\n"
fi
 
printf "Operating on profile: \t $profile\n\n"
 
## Runs at the end, to decide whether to delete profiles that match chosen parameters.
choose () {
    local default="$1"
    local prompt="$2"
    local answer
 
    read -p "$prompt" answer
    [ -z "$answer" ] && answer="$default"
 
    case "$answer" in
        [yY1] ) #printf "answered yes!\n"
            nix-env --delete-generations ${!gens[@]}
            exit 0
            ;;
        [nN0] ) printf "answered no! exiting\n"
            exit 6;
            ;;
        *    ) printf "%b" "Unexpected answer '$answer'!" >&2
            exit 7;
            ;;
    esac
} # end of function choose
 
 
## Query nix-env for generations list
IFS=$'\n' 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\n" "current generation created:" $currentDate
        currentTime=$(date -d "$currentDate" +%s)
    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 $keepDays days older than current generation. \nOldest gen days difference from current gen: $elapsedDays \n\n\tNothing to do!\n"
    exit 4;
elif [[ generationsDiff -lt keepGens ]]; then
    printf "Oldest generation ($oldestGen) is only $generationsDiff generations behind current ($currentGen). \t Nothing to do!\n"
    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 $K \t ${gens[$K]}\n"
    done
    printf "\n"
    choose "y" "Do you want to delete these? [Y/n]: "
fi
</nowiki>}}