adding h002 back in

This commit is contained in:
RingOfStorms (Joshua Bell) 2025-12-18 17:38:16 -06:00
parent ea9340a612
commit acfec76a79
11 changed files with 1014 additions and 131 deletions

View file

@ -34,6 +34,4 @@
networkmanager networkmanager
nvtopPackages.full nvtopPackages.full
]; ];
system.stateVersion = "23.11";
} }

View file

@ -1,13 +1,15 @@
{ {
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
# nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; home-manager.url = "github:rycee/home-manager/release-25.11";
# Use relative to get current version for testing common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/common";
# common.url = "path:../../common"; # de_plasma.url = "path:../../../../flakes/de_plasma";
common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles"; de_plasma.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/de_plasma";
ros_neovim.url = "git+https://git.joshuabell.xyz/ringofstorms/nvim"; ros_neovim.url = "git+https://git.joshuabell.xyz/ringofstorms/nvim";
impermanence.url = "github:nix-community/impermanence";
}; };
outputs = outputs =
@ -16,92 +18,122 @@
common, common,
ros_neovim, ros_neovim,
... ...
}: }@inputs:
let let
configuration_name = "h002"; configurationName = "h002";
lib = nixpkgs.lib; primaryUser = "luser";
configLocation = "/home/${primaryUser}/.config/nixos-config/hosts/${configurationName}";
stateAndHomeVersion = "25.11";
# overlayIp = "100.64.0.14";
lib = inputs.nixpkgs.lib;
in in
{ {
nixosConfigurations = { nixosConfigurations = {
"${configuration_name}" = ( "${configurationName}" = (
lib.nixosSystem { lib.nixosSystem {
specialArgs = {
inherit inputs;
};
modules = [ modules = [
common.nixosModules.default inputs.impermanence.nixosModules.impermanence
ros_neovim.nixosModules.default inputs.home-manager.nixosModules.default
./configuration.nix
./hardware-configuration.nix
(
{ config, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
lua
qdirstat
];
ringofstorms_common = { # TODO
systemName = configuration_name; # secrets.nixosModules.default
boot.grub.enable = true; inputs.ros_neovim.nixosModules.default
secrets.enable = true; ({
desktopEnvironment.gnome.enable = true; ringofstorms-nvim.includeAllRuntimeDependencies = true;
general = { })
reporting.enable = true;
inputs.common.nixosModules.essentials
inputs.common.nixosModules.git
inputs.common.nixosModules.tmux
inputs.common.nixosModules.boot_grub
({
boot.loader.grub.device = "/dev/sdb";
})
inputs.common.nixosModules.hardening
inputs.common.nixosModules.nix_options
inputs.common.nixosModules.no_sleep
inputs.common.nixosModules.timezone_auto
inputs.common.nixosModules.tty_caps_esc
inputs.common.nixosModules.zsh
# TODO
# common.nixosModules.tailnet
# beszel.nixosModules.agent
# (
# { ... }:
# {
# beszelAgent = {
# listen = "${overlayIp}:45876";
# token = "f8a54c41-486b-487a-a78d-a087385c317b";
# };
# }
# )
./hardware-configuration.nix
./hardware-mounts.nix
./impermanence.nix
./impermanence-tools.nix
(
{
config,
pkgs,
lib,
...
}:
rec {
system.stateVersion = stateAndHomeVersion;
# Home Manager
home-manager = {
useUserPackages = true;
useGlobalPkgs = true;
backupFileExtension = "bak";
# add all normal users to home manager so it applies to them
users = lib.mapAttrs (name: user: {
home.stateVersion = stateAndHomeVersion;
programs.home-manager.enable = true;
}) (lib.filterAttrs (name: user: user.isNormalUser or false) users.users);
sharedModules = [
inputs.common.homeManagerModules.tmux
inputs.common.homeManagerModules.atuin
inputs.common.homeManagerModules.direnv
inputs.common.homeManagerModules.git
inputs.common.homeManagerModules.postgres_cli_options
inputs.common.homeManagerModules.starship
inputs.common.homeManagerModules.zoxide
inputs.common.homeManagerModules.zsh
];
extraSpecialArgs = {
inherit inputs;
}; };
programs = { };
qFlipper.enable = true;
rustDev.enable = true; # System configuration
tailnet.enable = true; networking.networkmanager.enable = true;
ssh.enable = true; networking.hostName = configurationName;
docker.enable = true; programs.nh.flake = configLocation;
uhkAgent.enable = true; nixpkgs.config.allowUnfree = true;
}; # users.mutableUsers = false;
users = { users.users = {
admins = [ "luser" ]; # First admin is also the primary user owning nix config "${primaryUser}" = {
users = { isNormalUser = true;
root = { # hashedPassword = ""; # Use if mutable users is false above
openssh.authorizedKeys.keys = [ initialHashedPassword = "$y$j9T$v1QhXiZMRY1pFkPmkLkdp0$451GvQt.XFU2qCAi4EQNd1BEqjM/CH6awU8gjcULps6"; # "test" password
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJie9OPheWn/EZWfXJSZ3S0DnISqI3ToCmOqhX/Tkwby nix2h002" extraGroups = [
]; "wheel"
}; "networkmanager"
luser = { ];
openssh.authorizedKeys.keys = [ openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJie9OPheWn/EZWfXJSZ3S0DnISqI3ToCmOqhX/Tkwby nix2h002" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2KFSRkViT+asBTjCgA7LNP3SHnfNCW+jHbV08VUuIi nix2nix"
]; ];
extraGroups = [
"networkmanager"
"video"
"input"
];
shell = pkgs.zsh;
packages = with pkgs; [
bitwarden
vaultwarden
google-chrome
firefox-esr
openscad
vlc
];
};
};
};
homeManager = {
users = {
luser = {
imports = with common.homeManagerModules; [
kitty
tmux
atuin
direnv
git
nix_deprecations
postgres
ssh
starship
zoxide
zsh
];
};
};
}; };
root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2KFSRkViT+asBTjCgA7LNP3SHnfNCW+jHbV08VUuIi nix2nix"
];
}; };
} }
) )

View file

@ -18,8 +18,8 @@
"ahci" "ahci"
"xhci_pci" "xhci_pci"
"firewire_ohci" "firewire_ohci"
"usb_storage"
"usbhid" "usbhid"
"usb_storage"
"sd_mod" "sd_mod"
"sr_mod" "sr_mod"
]; ];
@ -27,23 +27,6 @@
boot.kernelModules = [ "kvm-intel" ]; boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/NIXBOOT";
fsType = "vfat";
};
swapDevices = [
{
device = "/.swapfile";
size = 18 * 1024; # 18GB
}
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's # (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction # still possible to use this option, but it's recommended to use it in conjunction

View file

@ -0,0 +1,142 @@
{
utils,
lib,
...
}:
let
BOOT = "/dev/disk/by-uuid/CC65-4ADF";
PRIMARY = "/dev/disk/by-uuid/35c8b82e-de7d-45bc-9cb2-2a422a99ee9c";
SWAP = "/dev/disk/by-uuid/85801775-1aad-4cc8-846a-560f9f4b11f4";
primaryDeviceUnit = "${utils.escapeSystemdPath PRIMARY}.device";
in
lib.mkMerge [
# Main filesystems
{
# BOOT
fileSystems."/boot" = {
device = BOOT;
fsType = "vfat";
options = [
"fmask=0077"
"dmask=0077"
];
};
# PRIMARY
fileSystems."/" = {
device = PRIMARY;
fsType = "bcachefs";
options = [
"X-mount.subdir=@root"
];
};
fileSystems."/nix" = {
device = PRIMARY;
fsType = "bcachefs";
options = [
"X-mount.mkdir"
"X-mount.subdir=@nix"
"relatime"
];
};
fileSystems."/.snapshots" = {
device = PRIMARY;
fsType = "bcachefs";
options = [
"X-mount.mkdir"
"X-mount.subdir=@snapshots"
"relatime"
];
};
# (optional) for preservation/impermanence
fileSystems."/persist" = {
device = PRIMARY;
fsType = "bcachefs";
options = [
"X-mount.mkdir"
"X-mount.subdir=@persist"
];
neededForBoot = true; # NOTE for impermanence only
};
}
# SWAP (optional)
{ swapDevices = [ { device = SWAP; } ]; }
{
# Impermanence fix
boot.initrd.systemd.services.create-needed-for-boot-dirs = {
after = [
"bcachefs-reset-root.service"
];
requires = [
"bcachefs-reset-root.service"
];
serviceConfig.KeyringMode = "shared";
};
}
# Reset root for erase your darlings/impermanence/preservation
(lib.mkIf true {
boot.initrd.systemd.services.bcachefs-reset-root = {
description = "Reset bcachefs root subvolume before pivot";
after = [
"initrd-root-device.target"
"cryptsetup.target"
];
requires = [
primaryDeviceUnit
];
before = [
"sysroot.mount"
];
wantedBy = [
"initrd-root-fs.target"
"sysroot.mount"
"initrd.target"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
KeyringMode = "shared";
};
script = ''
cleanup() {
if [[ ! -e /primary_tmp/@root ]]; then
echo "Cleanup: Creating new @root"
bcachefs subvolume create /primary_tmp/@root
fi
echo "Cleanup: Unmounting /primary_tmp"
umount /primary_tmp || true
}
trap cleanup EXIT
mkdir -p /primary_tmp
echo "Mounting ${PRIMARY}..."
if ! mount "${PRIMARY}" /primary_tmp; then
echo "Mount failed. Cannot reset root."
exit 1
fi
if [[ -e /primary_tmp/@root ]]; then
mkdir -p /primary_tmp/@snapshots/old_roots
# Use safe timestamp format (dashes instead of colons)
timestamp=$(date "+%Y-%m-%d_%H-%M-%S")
snap="/primary_tmp/@snapshots/old_roots/$timestamp"
echo "Snapshotting @root to $snap"
bcachefs subvolume snapshot /primary_tmp/@root "$snap"
echo "Deleting current @root"
bcachefs subvolume delete /primary_tmp/@root
fi
# Trap handles creating new root and unmount
'';
};
})
]

View file

@ -0,0 +1,76 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.impermanence.tools;
bcacheImpermanenceBin = pkgs.writeShellScriptBin "bcache-impermanence" (
builtins.readFile ./impermanence-tools.sh
);
in
{
options.impermanence.tools = {
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 = {
environment.systemPackages = [
bcacheImpermanenceBin
pkgs.coreutils
pkgs.findutils
pkgs.diffutils
pkgs.bcachefs-tools
pkgs.fzf
];
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";
};
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}
'';
};
};
}

View file

@ -0,0 +1,625 @@
#!/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
DIFF_MAX_DEPTH=0
usage() {
cat <<EOF
bcache-impermanence - tools for managing impermanence snapshots
Usage:
bcache-impermanence gc [--snapshot-root DIR] [--keep-per-month N] [--keep-recent-weeks N] [--keep-recent-count N] [--dry-run]
bcache-impermanence ls [-nN] [--snapshot-root DIR]
bcache-impermanence diff [-s SNAPSHOT] [--snapshot-root DIR] [--max-depth N] [PATH_PREFIX...]
Subcommands:
gc Run garbage collection on old root snapshots.
ls List snapshots (newest first). With -nN prints N latest.
diff Browse and diff files/dirs between current system and a snapshot.
Options:
--snapshot-root DIR Override snapshot root directory (default: /.snapshots/old_roots).
--keep-per-month N For gc: keep at least N snapshots per calendar month.
--keep-recent-weeks N For gc: keep at least one snapshot per ISO week within the last N weeks.
--keep-recent-count N For gc: always keep at least N most recent snapshots overall.
--dry-run For gc: show what would be deleted.
--max-depth N For diff: limit scan depth under each prefix.
EOF
}
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 count=0
while [ "$#" -gt 0 ]; do
case "$1" in
-n*)
# Accept -nN where N is integer; default to 1 if empty.
local n="${1#-n}"
if [ -z "$n" ]; then
n=1
fi
count="$n"
;;
--snapshot-root)
shift
[ "$#" -gt 0 ] || { echo "--snapshot-root requires a value" >&2; exit 1; }
SNAPSHOT_ROOT="$1"
;;
--help|-h)
echo "Usage: bcache-impermanence ls [-nN] [--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 [ "$count" -gt 0 ] 2>/dev/null; then
printf '%s\n' "$snaps" | head -n "$count"
else
printf '%s\n' "$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\n' "$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
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 <<EOF_SNAPS
$snaps
EOF_SNAPS
fi
# Recent weeks: keep latest snapshot per week within last KEEP_RECENT_WEEKS weeks.
if [ "$KEEP_RECENT_WEEKS" -gt 0 ]; then
local max_age
max_age=$(( KEEP_RECENT_WEEKS * 7 * 24 * 3600 ))
while read -r snap; do
[ -n "$snap" ] || continue
local ts
ts=$(date -d "${snap%_*} ${snap#*_}" +%s 2>/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 <<EOF_SNAPS2
$snaps
EOF_SNAPS2
fi
# Aggregate and dedupe.
for f in "$tmpdir"/*; do
[ -f "$f" ] || continue
cat "$f"
done | sort -u
rm -rf "$tmpdir"
}
cmd_gc() {
while [ "$#" -gt 0 ]; do
case "$1" in
--snapshot-root)
shift
[ "$#" -gt 0 ] || { echo "--snapshot-root requires a value" >&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 <<EOF_KEEP
$keep
EOF_KEEP
local deleted=0
while read -r snap; do
[ -n "$snap" ] || continue
if [ -f "$tmpkeep/$snap" ]; then
continue
fi
local full
full="$SNAPSHOT_ROOT/$snap"
if [ "$DRY_RUN" -eq 1 ]; then
echo "[dry-run] Would delete $full"
else
echo "Deleting snapshot $full"
if ! bcachefs subvolume delete "$full"; then
echo "Failed to delete $full" >&2
else
deleted=$((deleted + 1))
fi
fi
done <<EOF_SNAPS
$snaps
EOF_SNAPS
rm -rf "$tmpkeep"
echo "GC complete; deleted $deleted snapshots"
}
browse_diff_tree() {
local snapshot_name snapshot_dir diff_list initial_prefix
snapshot_name="$1"
snapshot_dir="$2"
diff_list="$3"
initial_prefix="${4-}"
local current_prefix=""
if [ -n "$initial_prefix" ]; then
current_prefix="$initial_prefix"
fi
while :; do
local children
children=$(mktemp)
local seen
seen=$(mktemp)
# Optional parent entry
if [ -n "$current_prefix" ]; then
echo "dir .." >"$children"
fi
# Build immediate children under current_prefix.
while read -r st rel; do
[ -n "$rel" ] || continue
local sub
if [ -n "$current_prefix" ]; then
case "$rel" in
"$current_prefix")
# Exact match at this level; treat as leaf, not a separate child.
continue
;;
"$current_prefix"/*)
sub="${rel#"$current_prefix"/}"
;;
*)
continue
;;
esac
else
sub="$rel"
fi
[ -n "$sub" ] || continue
local child
child="${sub%%/*}"
local child_rel
if [ -n "$current_prefix" ]; then
child_rel="$current_prefix/$child"
else
child_rel="$child"
fi
if grep -qx "$child_rel" "$seen" 2>/dev/null; then
continue
fi
echo "$st $child_rel" >>"$children"
echo "$child_rel" >>"$seen"
done <"$diff_list"
rm -f "$seen"
if [ ! -s "$children" ]; then
echo "No further differences under ${current_prefix:-/}" >&2
rm -f "$children"
break
fi
if ! command -v fzf >/dev/null 2>&1; then
echo "fzf is required for diff browsing" >&2
rm -f "$children"
break
fi
local preview_cmd
preview_cmd='sel_rel=$(printf "%s\n" {} | cut -d" " -f2-)
if [ "$sel_rel" = ".." ]; then
echo "[UP] .."
exit 0
fi
snap_dir="'"$snapshot_dir"'"
a_path="$snap_dir/$sel_rel"
b_path="/$sel_rel"
if [ ! -e "$a_path" ] && [ -e "$b_path" ]; then
echo "[ADDED] /$sel_rel"; echo
if [ -d "$b_path" ]; then
(cd / && find "$sel_rel" -maxdepth 3 -print 2>/dev/null || true)
else
diff -u /dev/null "$b_path" 2>/dev/null || cat "$b_path" 2>/dev/null || true
fi
elif [ -e "$a_path" ] && [ ! -e "$b_path" ]; then
echo "[REMOVED] /$sel_rel"; echo
if [ -d "$a_path" ]; then
(cd "$snap_dir" && find "$sel_rel" -maxdepth 3 -print 2>/dev/null || true)
else
diff -u "$a_path" /dev/null 2>/dev/null || cat "$a_path" 2>/dev/null || true
fi
else
if [ -d "$a_path" ] && [ -d "$b_path" ]; then
echo "[CHANGED DIR] /$sel_rel"; echo
diff -ru "$a_path" "$b_path" 2>/dev/null || true
else
echo "[CHANGED] /$sel_rel"; echo
diff -u "$a_path" "$b_path" 2>/dev/null || true
fi
fi
'
local selection
selection=$(FZF_DEFAULT_OPTS="${FZF_DEFAULT_OPTS:-} --ansi --preview-window=right:70%:wrap" \
fzf --prompt="[bcache-impermanence diff ${current_prefix:-/}] " \
--preview "$preview_cmd" <"$children") || {
rm -f "$children"
break
}
rm -f "$children"
local sel_rel
sel_rel=$(printf "%s\n" "$selection" | cut -d" " -f2-)
if [ "$sel_rel" = ".." ]; then
if [ -z "$current_prefix" ]; then
break
fi
if printf "%s" "$current_prefix" | grep -q '/'; then
current_prefix="${current_prefix%/*}"
else
current_prefix=""
fi
continue
fi
# If this selection has descendants, drill down; otherwise treat as leaf and exit.
if grep -q " $sel_rel/" "$diff_list"; then
current_prefix="$sel_rel"
continue
else
# Leaf: user already saw diff in preview; exit.
break
fi
done
}
cmd_diff() {
local snapshot_name=""
while [ "$#" -gt 0 ]; do
case "$1" in
-s)
shift
[ "$#" -gt 0 ] || { echo "-s requires a snapshot name" >&2; exit 1; }
snapshot_name="$1"
;;
--snapshot-root)
shift
[ "$#" -gt 0 ] || { echo "--snapshot-root requires a value" >&2; exit 1; }
SNAPSHOT_ROOT="$1"
;;
--max-depth)
shift
[ "$#" -gt 0 ] || { echo "--max-depth requires a value" >&2; exit 1; }
DIFF_MAX_DEPTH="$1"
;;
--help|-h)
echo "Usage: bcache-impermanence diff [-s SNAPSHOT] [--snapshot-root DIR] [--max-depth N] [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
if ! command -v fzf >/dev/null 2>&1; then
echo "fzf is required for diff browsing" >&2
exit 1
fi
# Build list of bind mounts backed by /persist so we can filter them out.
local persist_mounts
persist_mounts=$(awk '$2 ~ "^/persist(/|$)" { print $2 }' /proc/self/mounts || true)
is_persist_backed() {
local p
p="$1"
if [ -z "$p" ]; then
return 1
fi
if [ -z "$persist_mounts" ]; then
return 1
fi
local m
for m in $persist_mounts; do
case "$p" in
"$m"|"$m"/*) return 0 ;;
esac
done
return 1
}
local prefixes
prefixes=("$@")
local tmp
tmp=$(mktemp)
for prefix in "${prefixes[@]}"; do
case "$prefix" in
/*) : ;;
*)
echo "Path prefix must be absolute: $prefix" >&2
continue
;;
esac
# Skip prefixes that are themselves backed by /persist.
if is_persist_backed "$prefix"; then
continue
fi
local rel
rel="${prefix#/}"
[ -z "$rel" ] && rel="."
if [ "$DIFF_MAX_DEPTH" -gt 0 ] 2>/dev/null; then
(
cd "$snapshot_dir" && find "$rel" -mindepth 1 -maxdepth "$DIFF_MAX_DEPTH" -print 2>/dev/null || true
) | sed "s/^/A /" >>"$tmp"
(
cd / && find "$rel" -mindepth 1 -maxdepth "$DIFF_MAX_DEPTH" -print 2>/dev/null || true
) | sed "s/^/B /" >>"$tmp"
else
(
cd "$snapshot_dir" && find "$rel" -mindepth 1 -print 2>/dev/null || true
) | sed "s/^/A /" >>"$tmp"
(
cd / && find "$rel" -mindepth 1 -print 2>/dev/null || true
) | sed "s/^/B /" >>"$tmp"
fi
done
if [ ! -s "$tmp" ]; then
echo "No files found under specified prefixes" >&2
rm -f "$tmp"
exit 1
fi
local paths
paths=$(cut -d' ' -f2- "$tmp" | sort -u)
local diff_list
diff_list=$(mktemp)
while read -r rel; do
[ -n "$rel" ] || continue
local a_path b_path
a_path="$snapshot_dir/$rel"
b_path="/$rel"
# Skip paths that reside under a /persist-backed mount in the live system.
if is_persist_backed "$b_path"; then
continue
fi
local status
if [ ! -e "$a_path" ] && [ -e "$b_path" ]; then
status="added"
elif [ -e "$a_path" ] && [ ! -e "$b_path" ]; then
status="removed"
else
if [ -d "$a_path" ] && [ -d "$b_path" ]; then
if ! diff -rq "$a_path" "$b_path" >/dev/null 2>&1; then
status="changed-dir"
else
continue
fi
else
if ! diff -q "$a_path" "$b_path" >/dev/null 2>/dev/null; then
status="changed"
else
continue
fi
fi
fi
echo "$status $rel" >>"$diff_list"
done <<<"$paths"
rm -f "$tmp"
if [ ! -s "$diff_list" ]; then
echo "No differences found between snapshot $snapshot_name and current system" >&2
rm -f "$diff_list"
exit 0
fi
local initial_prefix=""
if [ "$#" -eq 1 ]; then
initial_prefix="${1#/}"
fi
browse_diff_tree "$snapshot_name" "$snapshot_dir" "$diff_list" "$initial_prefix"
rm -f "$diff_list"
}
main() {
if [ "$#" -lt 1 ]; then
usage
exit 1
fi
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 "$@"

View file

@ -0,0 +1,44 @@
{ ... }:
{
environment.persistence."/persist" = {
enable = true;
hideMounts = true;
directories = [
"/var/log"
"/var/lib/nixos"
"/var/lib/systemd/coredump"
"/var/lib/systemd/timers"
"/etc/nixos"
"/etc/ssh"
"/etc/NetworkManager/system-connections"
"/var/lib/bluetooth"
"/var/lib/NetworkManager"
"/var/lib/iwd"
"/var/lib/fail2ban"
];
files = [
"/etc/machine-id"
];
users.luser = {
directories = [
".ssh"
".gnupg"
".config/nixos-config"
".config/atuin"
".local/share/atuin"
".local/share/zoxide"
# neovim ros_neovim
".local/state/nvim_ringofstorms_helium"
];
files = [
];
};
};
}

View file

@ -30,7 +30,6 @@
nixosConfigurations = { nixosConfigurations = {
"${configurationName}" = ( "${configurationName}" = (
lib.nixosSystem { lib.nixosSystem {
inherit system;
specialArgs = { specialArgs = {
inherit inputs; inherit inputs;
}; };

View file

@ -111,7 +111,7 @@ lib.mkMerge [
} }
) )
{ {
# Impermanence fix # Impermanence fix for working with custom unlock and reset with root bcache
boot.initrd.systemd.services.create-needed-for-boot-dirs = { boot.initrd.systemd.services.create-needed-for-boot-dirs = {
after = [ after = [
"unlock-bcachefs-custom.service" "unlock-bcachefs-custom.service"
@ -169,29 +169,13 @@ lib.mkMerge [
primaryDeviceUnit primaryDeviceUnit
]; ];
# unitConfig = {
# # Ensure this service doesn't time out if USB detection takes a while
# DefaultDependencies = "no";
# };
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = true; RemainAfterExit = true;
KeyringMode = "shared"; # TODO so it shares with reset root below, not needed otherwise # TODO so it shares with reset root below, not needed otherwise
KeyringMode = "shared";
}; };
# script = ''
# echo "Using USB key for bcachefs unlock: ${USB_KEY}"
#
# # only try mount if the node exists
# if [ ! -e "${USB_KEY}" ]; then
# echo "USB key device ${USB_KEY} not present in initrd"
# exit 1
# fi
#
# ${pkgs.bcachefs-tools}/bin/bcachefs unlock -f /usb_key/key "${PRIMARY}"
# echo "bcachefs unlock successful for ${PRIMARY}"
# '';
script = '' script = ''
echo "Searching for USB Unlock Key..." echo "Searching for USB Unlock Key..."
KEY_FOUND=0 KEY_FOUND=0
@ -236,7 +220,6 @@ lib.mkMerge [
''; '';
}; };
# TODO rotate root
} }
# Reset root for erase your darlings/impermanence/preservation # Reset root for erase your darlings/impermanence/preservation
(lib.mkIf true { (lib.mkIf true {
@ -266,11 +249,6 @@ lib.mkMerge [
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = true; RemainAfterExit = true;
KeyringMode = "shared"; KeyringMode = "shared";
# Environment = "PATH=${
# lib.makeBinPath [
# # pkgs.coreutils
# ]
# }:/bin:/sbin";
}; };
script = '' script = ''
@ -286,7 +264,6 @@ lib.mkMerge [
mkdir -p /primary_tmp mkdir -p /primary_tmp
# If unlocked, mounts instantly. If locked, prompts for password on TTY.
echo "Mounting ${PRIMARY}..." echo "Mounting ${PRIMARY}..."
if ! mount "${PRIMARY}" /primary_tmp; then if ! mount "${PRIMARY}" /primary_tmp; then
echo "Mount failed. Cannot reset root." echo "Mount failed. Cannot reset root."

View file

@ -47,6 +47,10 @@
fastfetch fastfetch
fzf fzf
dmidecode # motherboard info
lshw # hardware info
sysbench # testing tools
# bcachefs # bcachefs
# Required as a workaround for bug # Required as a workaround for bug
# https://github.com/NixOS/nixpkgs/issues/32279 # https://github.com/NixOS/nixpkgs/issues/32279

View file

@ -25,11 +25,14 @@ parted /dev/$DEVICE -- mkpart PRIMARY 2GB 100%
```sh ```sh
BOOT=sda1 BOOT=sda1
mkfs.fat -F 32 -n BOOT /dev/$BOOT
PRIMARY=sda2 PRIMARY=sda2
SWAP=sda3
mkfs.fat -F 32 -n BOOT /dev/$BOOT
bcachefs format --label=nixos --encrypted /dev/$PRIMARY bcachefs format --label=nixos --encrypted /dev/$PRIMARY
bcachefs unlock /dev/$PRIMARY bcachefs unlock /dev/$PRIMARY
SWAP=sda3
mkswap /dev/$SWAP mkswap /dev/$SWAP
swapon /dev/$SWAP swapon /dev/$SWAP
``` ```