diff --git a/hosts/h002/configuration.nix b/hosts/h002/configuration.nix index 473b5226..cd6c9375 100644 --- a/hosts/h002/configuration.nix +++ b/hosts/h002/configuration.nix @@ -34,6 +34,4 @@ networkmanager nvtopPackages.full ]; - - system.stateVersion = "23.11"; } diff --git a/hosts/h002/flake.nix b/hosts/h002/flake.nix index 4a24350d..8527a80c 100644 --- a/hosts/h002/flake.nix +++ b/hosts/h002/flake.nix @@ -1,13 +1,15 @@ { inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; - # nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; + home-manager.url = "github:rycee/home-manager/release-25.11"; - # Use relative to get current version for testing - # common.url = "path:../../common"; - common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles"; + common.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/common"; + # de_plasma.url = "path:../../../../flakes/de_plasma"; + de_plasma.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/de_plasma"; ros_neovim.url = "git+https://git.joshuabell.xyz/ringofstorms/nvim"; + + impermanence.url = "github:nix-community/impermanence"; }; outputs = @@ -16,92 +18,122 @@ common, ros_neovim, ... - }: + }@inputs: let - configuration_name = "h002"; - lib = nixpkgs.lib; + configurationName = "h002"; + primaryUser = "luser"; + configLocation = "/home/${primaryUser}/.config/nixos-config/hosts/${configurationName}"; + stateAndHomeVersion = "25.11"; + # overlayIp = "100.64.0.14"; + lib = inputs.nixpkgs.lib; in { nixosConfigurations = { - "${configuration_name}" = ( + "${configurationName}" = ( lib.nixosSystem { + specialArgs = { + inherit inputs; + }; modules = [ - common.nixosModules.default - ros_neovim.nixosModules.default - ./configuration.nix - ./hardware-configuration.nix - ( - { config, pkgs, ... }: - { - environment.systemPackages = with pkgs; [ - lua - qdirstat - ]; + inputs.impermanence.nixosModules.impermanence + inputs.home-manager.nixosModules.default - ringofstorms_common = { - systemName = configuration_name; - boot.grub.enable = true; - secrets.enable = true; - desktopEnvironment.gnome.enable = true; - general = { - reporting.enable = true; + # TODO + # secrets.nixosModules.default + inputs.ros_neovim.nixosModules.default + ({ + ringofstorms-nvim.includeAllRuntimeDependencies = 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; - tailnet.enable = true; - ssh.enable = true; - docker.enable = true; - uhkAgent.enable = true; - }; - users = { - admins = [ "luser" ]; # First admin is also the primary user owning nix config - users = { - root = { - openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJie9OPheWn/EZWfXJSZ3S0DnISqI3ToCmOqhX/Tkwby nix2h002" - ]; - }; - luser = { - openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJie9OPheWn/EZWfXJSZ3S0DnISqI3ToCmOqhX/Tkwby nix2h002" - ]; - 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 - ]; - }; - }; + }; + + # System configuration + networking.networkmanager.enable = true; + networking.hostName = configurationName; + programs.nh.flake = configLocation; + nixpkgs.config.allowUnfree = true; + # users.mutableUsers = false; + users.users = { + "${primaryUser}" = { + isNormalUser = true; + # hashedPassword = ""; # Use if mutable users is false above + initialHashedPassword = "$y$j9T$v1QhXiZMRY1pFkPmkLkdp0$451GvQt.XFU2qCAi4EQNd1BEqjM/CH6awU8gjcULps6"; # "test" password + extraGroups = [ + "wheel" + "networkmanager" + ]; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2KFSRkViT+asBTjCgA7LNP3SHnfNCW+jHbV08VUuIi nix2nix" + ]; }; + root.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH2KFSRkViT+asBTjCgA7LNP3SHnfNCW+jHbV08VUuIi nix2nix" + ]; }; } ) diff --git a/hosts/h002/hardware-configuration.nix b/hosts/h002/hardware-configuration.nix index ceaa644e..8c9205c6 100644 --- a/hosts/h002/hardware-configuration.nix +++ b/hosts/h002/hardware-configuration.nix @@ -18,8 +18,8 @@ "ahci" "xhci_pci" "firewire_ohci" - "usb_storage" "usbhid" + "usb_storage" "sd_mod" "sr_mod" ]; @@ -27,23 +27,6 @@ boot.kernelModules = [ "kvm-intel" ]; 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 # (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 diff --git a/hosts/h002/hardware-mounts.nix b/hosts/h002/hardware-mounts.nix new file mode 100644 index 00000000..95c2848d --- /dev/null +++ b/hosts/h002/hardware-mounts.nix @@ -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 + ''; + }; + }) +] diff --git a/hosts/h002/impermanence-tools.nix b/hosts/h002/impermanence-tools.nix new file mode 100644 index 00000000..042d0634 --- /dev/null +++ b/hosts/h002/impermanence-tools.nix @@ -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} + ''; + }; + }; +} diff --git a/hosts/h002/impermanence-tools.sh b/hosts/h002/impermanence-tools.sh new file mode 100644 index 00000000..bd6d293a --- /dev/null +++ b/hosts/h002/impermanence-tools.sh @@ -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 <&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 </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 <"$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 "$@" diff --git a/hosts/h002/impermanence.nix b/hosts/h002/impermanence.nix new file mode 100644 index 00000000..d9727cf7 --- /dev/null +++ b/hosts/h002/impermanence.nix @@ -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 = [ + + ]; + }; + }; +} diff --git a/hosts/i001/flake.nix b/hosts/i001/flake.nix index 5fe7533e..9ee83904 100644 --- a/hosts/i001/flake.nix +++ b/hosts/i001/flake.nix @@ -30,7 +30,6 @@ nixosConfigurations = { "${configurationName}" = ( lib.nixosSystem { - inherit system; specialArgs = { inherit inputs; }; diff --git a/hosts/i001/hardware-mounts.nix b/hosts/i001/hardware-mounts.nix index c761f27a..f39405d6 100644 --- a/hosts/i001/hardware-mounts.nix +++ b/hosts/i001/hardware-mounts.nix @@ -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 = { after = [ "unlock-bcachefs-custom.service" @@ -169,29 +169,13 @@ lib.mkMerge [ primaryDeviceUnit ]; - # unitConfig = { - # # Ensure this service doesn't time out if USB detection takes a while - # DefaultDependencies = "no"; - # }; serviceConfig = { Type = "oneshot"; 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 = '' echo "Searching for USB Unlock Key..." KEY_FOUND=0 @@ -236,7 +220,6 @@ lib.mkMerge [ ''; }; - # TODO rotate root } # Reset root for erase your darlings/impermanence/preservation (lib.mkIf true { @@ -266,11 +249,6 @@ lib.mkMerge [ Type = "oneshot"; RemainAfterExit = true; KeyringMode = "shared"; - # Environment = "PATH=${ - # lib.makeBinPath [ - # # pkgs.coreutils - # ] - # }:/bin:/sbin"; }; script = '' @@ -286,7 +264,6 @@ lib.mkMerge [ mkdir -p /primary_tmp - # If unlocked, mounts instantly. If locked, prompts for password on TTY. echo "Mounting ${PRIMARY}..." if ! mount "${PRIMARY}" /primary_tmp; then echo "Mount failed. Cannot reset root." diff --git a/utilities/nixos-installers/flake.nix b/utilities/nixos-installers/flake.nix index 168bfaac..22a8a076 100644 --- a/utilities/nixos-installers/flake.nix +++ b/utilities/nixos-installers/flake.nix @@ -47,6 +47,10 @@ fastfetch fzf + dmidecode # motherboard info + lshw # hardware info + sysbench # testing tools + # bcachefs # Required as a workaround for bug # https://github.com/NixOS/nixpkgs/issues/32279 diff --git a/utilities/nixos-installers/install_bcachefs.md b/utilities/nixos-installers/install_bcachefs.md index f1d3de70..1a864ce3 100644 --- a/utilities/nixos-installers/install_bcachefs.md +++ b/utilities/nixos-installers/install_bcachefs.md @@ -25,11 +25,14 @@ parted /dev/$DEVICE -- mkpart PRIMARY 2GB 100% ```sh BOOT=sda1 -mkfs.fat -F 32 -n BOOT /dev/$BOOT PRIMARY=sda2 +SWAP=sda3 + +mkfs.fat -F 32 -n BOOT /dev/$BOOT + bcachefs format --label=nixos --encrypted /dev/$PRIMARY bcachefs unlock /dev/$PRIMARY -SWAP=sda3 + mkswap /dev/$SWAP swapon /dev/$SWAP ```