fix(impermanence): rollback as systemd initrd service, not postDeviceCommands
themes/engine/plymouth.nix enables systemd stage-1 initrd (boot.initrd.systemd.enable) distro-wide, and Plymouth is imported unconditionally from core/system/default.nix. systemd stage 1 hard-rejects boot.initrd.postDeviceCommands with a failed assertion, so every config with nomarchy.system.impermanence.enable = true — including installs that chose impermanence at the installer prompt — failed to evaluate. Convert the Erase-Your-Darlings rollback to a boot.initrd.systemd.services oneshot unit ordered after the LUKS mapping opens and before sysroot.mount, and add boot.initrd.systemd.initrdBin for btrfs-progs/coreutils/util-linux/ findutils (the systemd initrd doesn't ship them by default). Script body is unchanged. Verified eval via extendModules on nixosConfigurations.default; boot-time wipe semantics still need a real wipe-boot cycle (Pillar 8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -39,32 +39,53 @@ in
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# 1. The Rollback Script: Runs in initrd before filesystems are mounted
|
||||
boot.initrd.postDeviceCommands = lib.mkAfter ''
|
||||
mkdir -p /btrfs_tmp
|
||||
mount -o subvol=/ /dev/mapper/${cfg.mainLuksName} /btrfs_tmp
|
||||
|
||||
if [[ -e /btrfs_tmp/@ ]]; then
|
||||
mkdir -p /btrfs_tmp/old_roots
|
||||
timestamp=$(date --date="@$(stat -c %Y /btrfs_tmp/@)" "+%Y-%m-%-d_%H:%M:%S")
|
||||
mv /btrfs_tmp/@ "/btrfs_tmp/old_roots/$timestamp"
|
||||
fi
|
||||
# 1. The Rollback Service: wipes the @ root subvolume back to a blank
|
||||
# snapshot before the real root is mounted. Plymouth enables systemd
|
||||
# stage-1 initrd distro-wide (themes/engine/plymouth.nix), and systemd
|
||||
# stage 1 rejects boot.initrd.postDeviceCommands — so this runs as a
|
||||
# systemd initrd unit ordered after the LUKS mapping opens and before
|
||||
# sysroot is mounted. initrdBin pulls in the binaries the script calls
|
||||
# (the systemd initrd doesn't ship coreutils/findutils/btrfs by default).
|
||||
boot.initrd.systemd.initrdBin = [
|
||||
pkgs.btrfs-progs
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
pkgs.findutils
|
||||
];
|
||||
|
||||
delete_subvolume_recursively() {
|
||||
IFS=$'\n'
|
||||
for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
|
||||
delete_subvolume_recursively "/btrfs_tmp/$i"
|
||||
done
|
||||
btrfs subvolume delete "$1"
|
||||
}
|
||||
boot.initrd.systemd.services.nomarchy-rollback = {
|
||||
description = "Erase Your Darlings: roll the BTRFS root subvolume back to a blank snapshot";
|
||||
wantedBy = [ "initrd.target" ];
|
||||
after = [ "systemd-cryptsetup@${cfg.mainLuksName}.service" ];
|
||||
before = [ "sysroot.mount" ];
|
||||
unitConfig.DefaultDependencies = "no";
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
mkdir -p /btrfs_tmp
|
||||
mount -o subvol=/ /dev/mapper/${cfg.mainLuksName} /btrfs_tmp
|
||||
|
||||
for i in $(find /btrfs_tmp/old_roots/ -maxdepth 1 -mtime +30); do
|
||||
delete_subvolume_recursively "$i"
|
||||
done
|
||||
if [[ -e /btrfs_tmp/@ ]]; then
|
||||
mkdir -p /btrfs_tmp/old_roots
|
||||
timestamp=$(date --date="@$(stat -c %Y /btrfs_tmp/@)" "+%Y-%m-%-d_%H:%M:%S")
|
||||
mv /btrfs_tmp/@ "/btrfs_tmp/old_roots/$timestamp"
|
||||
fi
|
||||
|
||||
btrfs subvolume snapshot /btrfs_tmp/root-blank /btrfs_tmp/@
|
||||
umount /btrfs_tmp
|
||||
'';
|
||||
delete_subvolume_recursively() {
|
||||
IFS=$'\n'
|
||||
for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
|
||||
delete_subvolume_recursively "/btrfs_tmp/$i"
|
||||
done
|
||||
btrfs subvolume delete "$1"
|
||||
}
|
||||
|
||||
for i in $(find /btrfs_tmp/old_roots/ -maxdepth 1 -mtime +30); do
|
||||
delete_subvolume_recursively "$i"
|
||||
done
|
||||
|
||||
btrfs subvolume snapshot /btrfs_tmp/root-blank /btrfs_tmp/@
|
||||
umount /btrfs_tmp
|
||||
'';
|
||||
};
|
||||
|
||||
# 2. Persistence Configuration: What survives the wipe
|
||||
environment.persistence."/persist" = {
|
||||
|
||||
@@ -147,6 +147,8 @@ Pillar is **done** when every component has a closed `wave/qa-<component>` PR an
|
||||
|
||||
(Move items here when they land — keep them brief, link the commit/PR.)
|
||||
|
||||
- _2026-05-29_ — **Impermanence rollback no longer fails to build.** `core/system/impermanence.nix` implemented the Erase-Your-Darlings root wipe via `boot.initrd.postDeviceCommands`, but `themes/engine/plymouth.nix:63` sets `boot.initrd.systemd.enable = true` distro-wide (Plymouth is imported unconditionally from `core/system/default.nix`), and systemd stage-1 initrd hard-rejects `postDeviceCommands` with a failed assertion. Net effect: **every** install that flipped `nomarchy.system.impermanence.enable = true` — including any user who picked impermanence at the installer prompt — produced a config that wouldn't evaluate, let alone boot. Converted the rollback into a `boot.initrd.systemd.services.nomarchy-rollback` oneshot unit ordered `after = systemd-cryptsetup@${mainLuksName}.service` and `before = sysroot.mount`, with `boot.initrd.systemd.initrdBin` pulling in `btrfs-progs` / `coreutils` / `util-linux` / `findutils` (the systemd initrd doesn't ship them by default, unlike the old scripted initrd). Script body (timestamped `@` → `old_roots` move, 30-day recursive subvolume GC, `root-blank` → `@` snapshot) is unchanged. Verified the assertion is gone and `nixosConfigurations.default` + `impermanence.enable` evaluates fully via `extendModules`. **Runtime-verification caveat:** the boot-time wipe semantics (the `[[ ]]`/function/IFS shell idioms and the cleanup loop under the systemd initrd shell, plus correct ordering vs the LUKS mapping) can only be confirmed by a real wipe-boot cycle on an impermanence install — fold into the Pillar 8 punch-list. `docs/TROUBLESHOOTING.md` persistence-block line reference updated (46-72 → 91-120).
|
||||
|
||||
- _2026-05-22_ — **VSCode theme extensions pinned for 10 marketplace palettes.** Before this fix, only the 6 palettes whose theme extensions ship in `pkgs.vscode-extensions` (catppuccin, catppuccin-latte, nord, tokyo-night, rose-pine, gruvbox) had working VSCode theming — every other palette had `workbench.colorTheme` set to a theme VSCode couldn't find, so it silently fell back to the built-in default. Including the default `summer-night` palette (sainnhe.everforest), which meant the default install had broken VSCode theming. Probed all 13 unique extensions against the VSCode marketplace extensionquery API; 10 exist (`sainnhe.everforest`, `shadesOfBuntu.flexoki-light`, `qufiwefefwoyn.kanagawa`, `oldjobobo.{lumon,miasma,retro-82}-theme`, `TahaYVR.matteblack`, `jovejonovski.ocean-green`, `monokai.theme-monokai-pro-vscode`, `Bjarne.white-theme`) and 3 don't (`Bjarne.{ethereal,hackerman,vantablack}-nomarchy` — unpublished custom Nomarchy themes; logged as a new Later row). Fetched the latest version + sha256 for each of the 10 via `nix store prefetch-file` against `https://${publisher}.gallery.vsassets.io/_apis/public/gallery/publisher/${publisher}/extension/${name}/${version}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage`. Added a `marketplaceExtensions` list to `features/apps/vscode.nix` that wraps each entry in `pkgs.vscode-utils.extensionFromVscodeMarketplace { publisher; name; version; sha256; }` and concatenates with the existing nixpkgs-packaged list. Smoke-built `sainnhe.everforest` end-to-end to confirm the URL pattern + sha256 resolve. Documented the version-bump procedure in the module comment. `docs/OPTIONS.md` updated to drop the "still break" caveat for everything except the three Bjarne palettes.
|
||||
- _2026-05-22_ — **Chromium static `Default/Preferences` deleted.** `features/apps/chromium/default.nix` was deploying a 204-byte static `Default/Preferences` via Home Manager symlink into Chromium's mutable profile directory. Chromium expects to write that file at runtime, so the symlink-into-store deployment was structurally broken (Chromium either fails to save, or replaces the symlink on first write — losing whatever the static file claimed to set). The contents (`extensions.theme.{use_system,use_custom} = false`, `browser.theme.{color_scheme,user_color} = 2`) are duplicates of the managed-policy intent in `core/system/browser.nix` (`BrowserThemeColor` from palette `base00`, `BrowserColorScheme` from `isLightTheme`) — and worse, the static `color_scheme = 2` hardcoded "dark" while the policy is dynamic, so a light-palette user would have hit a conflict. Removed the whole `features/apps/chromium/` directory (8 lines + the 204-byte file) and dropped the import from `features/default.nix`. Chromium theming continues to flow through the system-level managed policy, which is the canonical chromium-on-NixOS path.
|
||||
- _2026-05-22_ — **Plymouth boot splash follows the active palette.** `themes/engine/plymouth/nomarchy.script` had `Window.SetBackgroundTopColor(0.101, 0.105, 0.149)` hardcoded — a Tokyo-Night-ish `#1a1b26` — and `nomarchy.plymouth` had a matching `ConsoleLogBackgroundColor=0x1a1b26`. Both were frozen regardless of `nomarchy.system.theme`. Replaced the literals with `@BG_R@`/`@BG_G@`/`@BG_B@`/`@BG_HEX@` placeholders; `themes/engine/plymouth.nix` now reads the active palette via `nomarchyLib.getPalette config.nomarchy.system.theme`, converts `palette.base00` into three 0.0–1.0 floats (Nix has no FP math, so `(byte * 1000) / 255` integer division formatted as `0.XXX`), and `sed`-substitutes during `installPhase`. Smoke-built the derivation against the default `summer-night` palette: emits `0.176, 0.207, 0.231` (matches `0x2d353b`) and `ConsoleLogBackgroundColor=0x2d353b`. Closes the "Plymouth theme variants per palette" Next-column item.
|
||||
|
||||
@@ -97,7 +97,7 @@ If churn continues, you have a config under `~/.config/<app>/` that home-manager
|
||||
error: The path '/persist' does not exist
|
||||
```
|
||||
|
||||
**Cause:** Impermanence requires (a) a `/persist` mountpoint that survives the boot wipe, and (b) every directory you want to keep must be in the persistence list. Nomarchy persists the basics in `core/system/impermanence.nix:46-72` (NetworkManager, Bluetooth, fprint, SSH host keys, the user's `.ssh` / `.gnupg` / Documents / Downloads / Pictures / Videos / Projects). Anything else you care about — Steam library, Flatpak data, custom dotfiles — must be added.
|
||||
**Cause:** Impermanence requires (a) a `/persist` mountpoint that survives the boot wipe, and (b) every directory you want to keep must be in the persistence list. Nomarchy persists the basics in `core/system/impermanence.nix:91-120` (NetworkManager, Bluetooth, fprint, SSH host keys, the user's `.ssh` / `.gnupg` / Documents / Downloads / Pictures / Videos / Projects). Anything else you care about — Steam library, Flatpak data, custom dotfiles — must be added.
|
||||
|
||||
**Fix:** Make sure `/persist` is mounted (check `mount | grep persist`). Then add the missing path in your `system.nix`:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user