NixOS Generations Trimmer: Difference between revisions

From NixOS Wiki
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>}}

Latest revision as of 10:01, 6 September 2023

Overview

The normal options available in NixOS to cleanup old generations might be too limiting. The script below is interactive and smart.

By default (run with no arguments), the script keeps the last 7 days of generations and will prompt you to remove older generations if they are more than 10 generations behind the current one. So it always keeps at least 10 generations and always preserves generations from the last week.

You can specify the number of days to always keep generations, and you can specify the number of generations to always keep.

By default, it works on the default profile for the user running it.

Other profile options:

  • If you are root, you can run on the system profile
  • If you are a normal user, you can run on either the home-manager or channels profiles too.

Script (trim-generations.sh)

The script is available here: Github Gist - NixOS: trim-generations.sh

Example script output

Without parameters (defaults)

 ./trim-generations.sh    

The current defaults are:
 Keep-Gens=30 Keep-Days=30 

Keep these defaults? (y/n):y
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...

Found the following generation(s) to delete:
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

Do you want to delete these? [Y/n]:

With parameters

 ./trim-generations.sh 3 0 home-manager
OK! 	 Keep Gens = 3 	 Keep Days = 0

Operating on profile: 	 /nix/var/nix/profiles/per-user/user/home-manager

oldest generation:             58
oldest generation created:     2021-09-24
minutes before now:            3922
hours before now:              65
days before now:               2

current generation:            65
current generation created:    2021-09-25

	Something to do...

Found the following generation(s) to delete:
generation 58 	 2021-09-24, 2 day(s) old
generation 59 	 2021-09-25, 1 day(s) old
generation 60 	 2021-09-25, 1 day(s) old
generation 61 	 2021-09-25, 1 day(s) old
generation 62 	 2021-09-25, 1 day(s) old

Do you want to delete these? [Y/n]:

With parameters (not enough though)

Example if you accidentally don't specify all parameters / arguments.

 sudo ./trim-generations.sh 2 0

Error: Not enough arguments.

Usage:
         ./trim-generations.sh <keep-gernerations> <keep-days> <profile> 


(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:
         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.

Profiles available are: user, home-manager, channels, system (root)

-h or --help prints this help text.

Run as root (default)

 sudo ./trim-generations.sh                 
The current defaults are:
 Keep-Gens=30 Keep-Days=30 

Keep these defaults? (y/n):y
Using defaults..
Keeping default: 30 generations OR 30 days, whichever is more
Operating on profile:    /nix/var/nix/profiles/system

oldest generation:             65
oldest generation created:     2023-08-05
minutes before now:            46727
hours before now:              778
days before now:               32

current generation:            102
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!

Run as root (on system profile)

 sudo ./trim-generations.sh 2 0 system
OK! 	 Keep Gens = 2 	 Keep Days = 0

Operating on profile: 	 /nix/var/nix/profiles/system

oldest generation:             11
oldest generation created:     2021-09-22
minutes before now:            6807
hours before now:              113
days before now:               4

current generation:            12
current generation created:    2021-09-26

Oldest generation (11) is only 1 generations behind current (12). 	 Nothing to do!

Run as root (wrong profile)

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

 sudo ./trim-generations.sh 3 0 home-manager

Error: Do not understand your third argument. Should be one of: (user / system)

Usage:
         ./trim-generations.sh <keep-gernerations> <keep-days> <profile> 


(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:
         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.

Profiles available are: user, home-manager, channels, system (root)

-h or --help prints this help text.