From 1d0cb5e08f857fa7c614a64d130443ff7b36a6fc Mon Sep 17 00:00:00 2001 From: "RingOfStorms (Joshua Bell)" Date: Wed, 17 Dec 2025 13:39:39 -0600 Subject: [PATCH] trying out some new tool commands for easier immpermanence with bcache and my setup --- hosts/i001/flake.nix | 1 + hosts/i001/impermanence-tools.nix | 67 ++++ hosts/i001/impermanence-tools.sh | 379 ++++++++++++++++++ hosts/lio/flake.lock | 68 ++-- hosts/lio/flake.nix | 6 + .../nixos-installers/install_bcachefs.md | 20 - 6 files changed, 487 insertions(+), 54 deletions(-) create mode 100644 hosts/i001/impermanence-tools.nix create mode 100644 hosts/i001/impermanence-tools.sh diff --git a/hosts/i001/flake.nix b/hosts/i001/flake.nix index d9149723..156add95 100644 --- a/hosts/i001/flake.nix +++ b/hosts/i001/flake.nix @@ -68,6 +68,7 @@ ./hardware-configuration.nix ./hardware-mounts.nix ./impermanence.nix + ./impermanence-tools.nix # ./preservation.nix ( { diff --git a/hosts/i001/impermanence-tools.nix b/hosts/i001/impermanence-tools.nix new file mode 100644 index 00000000..c3170eb5 --- /dev/null +++ b/hosts/i001/impermanence-tools.nix @@ -0,0 +1,67 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.impermanence.tools; + + bcacheImpermanenceBin = pkgs.writeShellScriptBin "bcache-impermanence" ( + builtins.readFile ./impermanence-tools.sh + ); + +in +{ + options.impermanence.tools = { + enable = lib.mkEnableOption "bcachefs impermanence tools (GC + CLI)"; + + snapshotRoot = lib.mkOption { + type = lib.types.str; + default = "/.snapshots/old_roots"; + description = "Root directory containing old root snapshots."; + }; + + gc = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Enable garbage collection of old root snapshots."; + }; + + keepPerMonth = lib.mkOption { + type = lib.types.int; + default = 1; + description = "Keep at least this many snapshots per calendar month (latest ones)."; + }; + + keepRecentWeeks = lib.mkOption { + type = lib.types.int; + default = 4; + description = "Keep at least one snapshot per ISO week within this many recent weeks."; + }; + + keepRecentCount = lib.mkOption { + type = lib.types.int; + default = 5; + description = "Always keep at least this many most recent snapshots overall."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ bcacheImpermanenceBin ]; + + systemd.services."bcache-impermanence-gc" = lib.mkIf cfg.gc.enable { + description = "Garbage collect bcachefs impermanence snapshots"; + wantedBy = [ "multi-user.target" ]; + after = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + Environment = "PATH=${lib.makeBinPath [ pkgs.coreutils pkgs.findutils pkgs.diffutils pkgs.bcachefs-tools ]}:/run/current-system/sw/bin"; + }; + script = '' + exec ${bcacheImpermanenceBin}/bin/bcache-impermanence gc \ + --snapshot-root ${cfg.snapshotRoot} \ + --keep-per-month ${toString cfg.gc.keepPerMonth} \ + --keep-recent-weeks ${toString cfg.gc.keepRecentWeeks} \ + --keep-recent-count ${toString cfg.gc.keepRecentCount} + ''; + }; + }; +} diff --git a/hosts/i001/impermanence-tools.sh b/hosts/i001/impermanence-tools.sh new file mode 100644 index 00000000..7b22b062 --- /dev/null +++ b/hosts/i001/impermanence-tools.sh @@ -0,0 +1,379 @@ +#!/usr/bin/env bash +set -eu + +SNAPSHOT_ROOT="/.snapshots/old_roots" +KEEP_PER_MONTH=1 +KEEP_RECENT_WEEKS=4 +KEEP_RECENT_COUNT=5 +DRY_RUN=0 + +usage() { + cat </dev/null 2>&1; then + echo "Missing required command: $cmd" >&2 + exit 1 + fi + done +} + +list_snapshots_desc() { + if [ ! -d "$SNAPSHOT_ROOT" ]; then + return 0 + fi + for entry in "$SNAPSHOT_ROOT"/*; do + [ -d "$entry" ] || continue + basename "$entry" + done | sort -r +} + +latest_snapshot_name() { + list_snapshots_desc | head -n1 +} + +cmd_ls() { + local n1=0 + while [ "$#" -gt 0 ]; do + case "$1" in + -n1) + n1=1 + ;; + --snapshot-root) + shift + [ "$#" -gt 0 ] || { echo "--snapshot-root requires a value" >&2; exit 1; } + SNAPSHOT_ROOT="$1" + ;; + --help|-h) + echo "Usage: bcache-impermanence ls [-n1] [--snapshot-root DIR]" >&2 + exit 0 + ;; + *) + echo "Unknown ls option: $1" >&2 + exit 1 + ;; + esac + shift + done + + local snaps + snaps=$(list_snapshots_desc) + + if [ -z "$snaps" ]; then + echo "No snapshots found in $SNAPSHOT_ROOT" >&2 + exit 1 + fi + + if [ "$n1" -eq 1 ]; then + printf '%s +' "$snaps" | head -n1 + else + printf '%s +' "$snaps" + fi +} + +build_keep_set() { + # Prints snapshot names to keep, one per line, based on policies. + local now + now=$(date +%s) + + local snaps + snaps=$(list_snapshots_desc) + + if [ -z "$snaps" ]; then + return 0 + fi + + local tmpdir + tmpdir=$(mktemp -d) + + # Always keep newest KEEP_RECENT_COUNT snapshots. + if [ "$KEEP_RECENT_COUNT" -gt 0 ]; then + printf '%s +' "$snaps" | head -n "$KEEP_RECENT_COUNT" >"$tmpdir/keep_recent" + fi + + # Per-month: keep up to KEEP_PER_MONTH newest per month. + if [ "$KEEP_PER_MONTH" -gt 0 ]; then + # Iterate newest -> oldest. + while read -r snap; do + [ -n "$snap" ] || continue + local month + month=${snap%_*} # YYYY-MM-DD + month=${month%-*} # YYYY-MM + local month_file="$tmpdir/month_$month" + local count=0 + if [ -f "$month_file" ]; then + count=$(wc -l <"$month_file") + fi + if [ "$count" -lt "$KEEP_PER_MONTH" ]; then + echo "$snap" >>"$month_file" + fi + done </dev/null || true) + [ -n "$ts" ] || continue + local age + age=$(( now - ts )) + if [ "$age" -gt "$max_age" ]; then + continue + fi + local week + week=$(date -d "${snap%_*} ${snap#*_}" +"%G-%V" 2>/dev/null || true) + [ -n "$week" ] || continue + local week_file="$tmpdir/week_$week" + if [ ! -f "$week_file" ]; then + echo "$snap" >"$week_file" + fi + done <&2; exit 1; } + SNAPSHOT_ROOT="$1" + ;; + --keep-per-month) + shift + [ "$#" -gt 0 ] || { echo "--keep-per-month requires a value" >&2; exit 1; } + KEEP_PER_MONTH="$1" + ;; + --keep-recent-weeks) + shift + [ "$#" -gt 0 ] || { echo "--keep-recent-weeks requires a value" >&2; exit 1; } + KEEP_RECENT_WEEKS="$1" + ;; + --keep-recent-count) + shift + [ "$#" -gt 0 ] || { echo "--keep-recent-count requires a value" >&2; exit 1; } + KEEP_RECENT_COUNT="$1" + ;; + --dry-run) + DRY_RUN=1 + ;; + --help|-h) + echo "Usage: bcache-impermanence gc [--snapshot-root DIR] [--keep-per-month N] [--keep-recent-weeks N] [--keep-recent-count N] [--dry-run]" >&2 + exit 0 + ;; + *) + echo "Unknown gc option: $1" >&2 + exit 1 + ;; + esac + shift + done + + if [ ! -d "$SNAPSHOT_ROOT" ]; then + echo "Snapshot root $SNAPSHOT_ROOT does not exist; nothing to do" >&2 + exit 0 + fi + + local snaps + snaps=$(list_snapshots_desc) + if [ -z "$snaps" ]; then + echo "No snapshots to process" >&2 + exit 0 + fi + + local keep + keep=$(build_keep_set) + + local tmpkeep + tmpkeep=$(mktemp -d) + while read -r k; do + [ -n "$k" ] || continue + : >"$tmpkeep/$k" + done <&2 + else + deleted=$((deleted + 1)) + fi + fi + done <&2; exit 1; } + snapshot_name="$1" + ;; + --snapshot-root) + shift + [ "$#" -gt 0 ] || { echo "--snapshot-root requires a value" >&2; exit 1; } + SNAPSHOT_ROOT="$1" + ;; + --help|-h) + echo "Usage: bcache-impermanence diff [-s SNAPSHOT] [--snapshot-root DIR] [PATH_PREFIX...]" >&2 + exit 0 + ;; + --*) + echo "Unknown diff option: $1" >&2 + exit 1 + ;; + *) + break + ;; + esac + shift + done + + if [ -z "$snapshot_name" ]; then + snapshot_name=$(latest_snapshot_name || true) + fi + + if [ -z "$snapshot_name" ]; then + echo "No snapshots found for diff" >&2 + exit 1 + fi + + local snapshot_dir + snapshot_dir="$SNAPSHOT_ROOT/$snapshot_name" + + if [ ! -d "$snapshot_dir" ]; then + echo "Snapshot directory $snapshot_dir does not exist" >&2 + exit 1 + fi + + if [ "$#" -eq 0 ]; then + set -- / + fi + + local rc=0 + while [ "$#" -gt 0 ]; do + local path + path="$1" + shift + + case "$path" in + /*) : ;; + *) + echo "Path prefix must be absolute: $path" >&2 + rc=2 + continue + ;; + esac + + local from + local to + from="$snapshot_dir$path" + to="$path" + + echo "--- Diff for $path (snapshot $snapshot_name) ---" + if ! diff -ru --new-file "$from" "$to"; then + local diff_rc=$? + if [ "$diff_rc" -gt 1 ]; then + echo "Error running diff for $path" >&2 + rc=$diff_rc + fi + fi + done + + exit "$rc" +} + +main() { + if [ "$#" -lt 1 ]; then + usage + exit 1 + fi + + ensure_deps + + local cmd + cmd="$1" + shift || true + + case "$cmd" in + gc) + cmd_gc "$@" + ;; + ls) + cmd_ls "$@" + ;; + diff) + cmd_diff "$@" + ;; + --help|-h|help) + usage + ;; + *) + echo "Unknown subcommand: $cmd" >&2 + usage + exit 1 + ;; + esac +} + +main "$@" diff --git a/hosts/lio/flake.lock b/hosts/lio/flake.lock index 1dcde6bb..04a01708 100644 --- a/hosts/lio/flake.lock +++ b/hosts/lio/flake.lock @@ -31,11 +31,11 @@ }, "locked": { "dir": "flakes/beszel", - "lastModified": 1765815414, - "narHash": "sha256-PVwVlVPMRPqVpd6G65u4VyGJWJL7JtcgX1r1NtfpWYE=", + "lastModified": 1765998800, + "narHash": "sha256-tFJsYcZQMuin4gtqjPpdg4V4QlCvpzLQ0H1ct5LM9Rg=", "ref": "refs/heads/master", - "rev": "d4088ffaa8039f024dd851146b5b7b34c6253257", - "revCount": 912, + "rev": "03487772acd9e0865207baf281f7a0478f6dbc16", + "revCount": 944, "type": "git", "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" }, @@ -149,11 +149,11 @@ }, "locked": { "dir": "flakes/flatpaks", - "lastModified": 1765815414, - "narHash": "sha256-PVwVlVPMRPqVpd6G65u4VyGJWJL7JtcgX1r1NtfpWYE=", + "lastModified": 1765998800, + "narHash": "sha256-tFJsYcZQMuin4gtqjPpdg4V4QlCvpzLQ0H1ct5LM9Rg=", "ref": "refs/heads/master", - "rev": "d4088ffaa8039f024dd851146b5b7b34c6253257", - "revCount": 912, + "rev": "03487772acd9e0865207baf281f7a0478f6dbc16", + "revCount": 944, "type": "git", "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" }, @@ -190,11 +190,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1765605144, - "narHash": "sha256-RM2xs+1HdHxesjOelxoA3eSvXShC8pmBvtyTke4Ango=", + "lastModified": 1765979862, + "narHash": "sha256-/r9/1KamvbHJx6I40H4HsSXnEcBAkj46ZwibhBx9kg0=", "owner": "rycee", "repo": "home-manager", - "rev": "90b62096f099b73043a747348c11dbfcfbdea949", + "rev": "d3135ab747fd9dac250ffb90b4a7e80634eacbe9", "type": "github" }, "original": { @@ -261,11 +261,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1765472234, - "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=", + "lastModified": 1765779637, + "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b", + "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", "type": "github" }, "original": { @@ -277,11 +277,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1764983851, - "narHash": "sha256-y7RPKl/jJ/KAP/VKLMghMgXTlvNIJMHKskl8/Uuar7o=", + "lastModified": 1765762245, + "narHash": "sha256-3iXM/zTqEskWtmZs3gqNiVtRTsEjYAedIaLL0mSBsrk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d9bc5c7dceb30d8d6fafa10aeb6aa8a48c218454", + "rev": "c8cfcd6ccd422e41cc631a0b73ed4d5a925c393d", "type": "github" }, "original": { @@ -293,11 +293,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1765762245, - "narHash": "sha256-3iXM/zTqEskWtmZs3gqNiVtRTsEjYAedIaLL0mSBsrk=", + "lastModified": 1765838191, + "narHash": "sha256-m5KWt1nOm76ILk/JSCxBM4MfK3rYY7Wq9/TZIIeGnT8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c8cfcd6ccd422e41cc631a0b73ed4d5a925c393d", + "rev": "c6f52ebd45e5925c188d1a20119978aa4ffd5ef6", "type": "github" }, "original": { @@ -309,11 +309,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1765644376, - "narHash": "sha256-yqHBL2wYGwjGL2GUF2w3tofWl8qO9tZEuI4wSqbCrtE=", + "lastModified": 1765934234, + "narHash": "sha256-pJjWUzNnjbIAMIc5gRFUuKCDQ9S1cuh3b2hKgA7Mc4A=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "23735a82a828372c4ef92c660864e82fbe2f5fbe", + "rev": "af84f9d270d404c17699522fab95bbf928a2d92f", "type": "github" }, "original": { @@ -1224,11 +1224,11 @@ }, "locked": { "dir": "flakes/opencode", - "lastModified": 1765815414, - "narHash": "sha256-PVwVlVPMRPqVpd6G65u4VyGJWJL7JtcgX1r1NtfpWYE=", + "lastModified": 1765998800, + "narHash": "sha256-tFJsYcZQMuin4gtqjPpdg4V4QlCvpzLQ0H1ct5LM9Rg=", "ref": "refs/heads/master", - "rev": "d4088ffaa8039f024dd851146b5b7b34c6253257", - "revCount": 912, + "rev": "03487772acd9e0865207baf281f7a0478f6dbc16", + "revCount": 944, "type": "git", "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" }, @@ -1243,11 +1243,11 @@ "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1765814475, - "narHash": "sha256-+HC5nvbpcrvTyWuJDLqC13KuDKMGwjmoYb2tm+hGDzQ=", + "lastModified": 1765998786, + "narHash": "sha256-OxSKJ9QR7demkOF6hrWaf4yS3fMGlU/FbF12VYsX5Mk=", "owner": "sst", "repo": "opencode", - "rev": "56dde2cc835f509f77cbd800d080d6dbb2b8edc6", + "rev": "1f527312554c3015286811fc33bb5348d0a27dae", "type": "github" }, "original": { @@ -1433,11 +1433,11 @@ }, "locked": { "dir": "flakes/secrets", - "lastModified": 1765815414, - "narHash": "sha256-PVwVlVPMRPqVpd6G65u4VyGJWJL7JtcgX1r1NtfpWYE=", + "lastModified": 1765998800, + "narHash": "sha256-tFJsYcZQMuin4gtqjPpdg4V4QlCvpzLQ0H1ct5LM9Rg=", "ref": "refs/heads/master", - "rev": "d4088ffaa8039f024dd851146b5b7b34c6253257", - "revCount": 912, + "rev": "03487772acd9e0865207baf281f7a0478f6dbc16", + "revCount": 944, "type": "git", "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" }, diff --git a/hosts/lio/flake.nix b/hosts/lio/flake.nix index 88ff495c..13229d03 100644 --- a/hosts/lio/flake.nix +++ b/hosts/lio/flake.nix @@ -148,6 +148,12 @@ common.homeManagerModules.starship common.homeManagerModules.zoxide common.homeManagerModules.zsh + ( + { ... }: + { + programs.tmux.package = pkgs.unstable.tmux; + } + ) ]; extraSpecialArgs = { diff --git a/utilities/nixos-installers/install_bcachefs.md b/utilities/nixos-installers/install_bcachefs.md index c85334eb..27f03c70 100644 --- a/utilities/nixos-installers/install_bcachefs.md +++ b/utilities/nixos-installers/install_bcachefs.md @@ -125,23 +125,3 @@ mount -t bcachefs --mkdir /dev/$DEVICE /usb_key echo "test" > /usb_key/key umount /usb_key && rmdir /usb_key ``` - - - -## TODO remove notes - -```sh -BOOT=sda1 -PRIMARY=sda2 -SWAP=sda3 -swapon /dev/$SWAP -keyctl link @u @s -DEV_B="/dev/disk/by-uuid/"$(lsblk -o name,uuid | grep $BOOT | awk '{print $2}') -DEV_P="/dev/disk/by-uuid/"$(lsblk -o name,uuid | grep $PRIMARY | awk '{print $2}') -mount -t bcachefs -o X-mount.subdir=@root $DEV_P /mnt -mount -t vfat $DEV_B /mnt/boot --mkdir -mount -t bcachefs -o X-mount.mkdir,X-mount.subdir=@nix,relatime $DEV_P /mnt/nix -mount -t bcachefs -o X-mount.mkdir,X-mount.subdir=@snapshots,relatime $DEV_P /mnt/.snapshots -mount -t bcachefs -o X-mount.mkdir,X-mount.subdir=@persist $DEV_P /mnt/persist -nixos-install --flake "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=hosts/i001#i001" -```