commit 1dc6b8d971a9915c4737d840bc1e7cfa8d5aa857 Author: didier amyot Date: Sun Nov 30 14:53:02 2025 -0500 Added a script that list least important nixos generations diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5d1f41d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.direnv +.ipynb_checkpoints +notebooks/ +.envrc diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a2fa0f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Nixos Utils + +Collection of tools to help with nixos diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..f984089 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1764242076, + "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..27bbd92 --- /dev/null +++ b/flake.nix @@ -0,0 +1,18 @@ +{ + description = "A very basic flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + python-env = pkgs.python3.withPackages + (ps: with ps; [ jupyter requests pandas matplotlib click ]); + in { + devShells.default = pkgs.mkShell { buildInputs = [ python-env ]; }; + }); +} diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..3f8c31b --- /dev/null +++ b/src/main.py @@ -0,0 +1,65 @@ +import click + +import subprocess +from pathlib import Path +from io import BytesIO +import pandas as pd +import json + +def get_generations(): + cmd = ['nixos-rebuild', 'list-generations'] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + process.wait() + return BytesIO(stdout) + + +def get_generations_as_df(): + generations = get_generations() + df = pd.read_csv(generations, sep=r'\s\s+', engine='python') + df['Build-date'] = pd.to_datetime(df['Build-date'], format='%Y-%m-%d %H:%M:%S') + df['Time-To-Next-Build'] = -df['Build-date'].diff(periods=1) + df['nix-store'] = df['Generation'].map(lambda x: str((Path('/nix/var/nix/profiles/') / f'system-{x}-link').readlink())) + return df + + +def _get_generations_by_time_to_next_build(df): + return df.sort_values('Time-To-Next-Build').to_dict(orient='records') + +def _get_old_duplicate_generation_ids(df): + return df[df.duplicated('nix-store', keep='last')] + + +def get_generations_by_importance(): + df = get_generations_as_df() + old_duplicates = _get_old_duplicate_generation_ids(df) + yield from old_duplicates.to_dict(orient='records') + df = df.drop(old_duplicates.index) + + yield from _get_generations_by_time_to_next_build(df) + + +@click.group() +def cli(): + pass + +@cli.command() +@click.option('--max', default=-1) +def list_generations(max): + """ + List all nix generation by the lest important first. The least + important generations are old duplicated generations. Once all + of these are passed, an unimportant generation is a generation + that has been rapidly over wridden""" + generations = list(get_generations_by_importance()) + max = len(generations) if max == 0 else max + for generation in generations[:max]: + generation['Build-date'] = str(generation['Build-date']) + if generation.get('Time-To-Next-Build'): + generation['Time-To-Next-Build'] = generation['Time-To-Next-Build'].total_seconds() + print(json.dumps(generation)) + + +if __name__ == '__main__': + cli() +8179