Skip to content

Combine nebula-snowglobe (NixOS) + main (darwin) into one dual-OS flake - #22

Merged
kriswill merged 148 commits into
mainfrom
nixos-darwin-combined
Jul 3, 2026
Merged

Combine nebula-snowglobe (NixOS) + main (darwin) into one dual-OS flake#22
kriswill merged 148 commits into
mainfrom
nixos-darwin-combined

Conversation

@kriswill

@kriswill kriswill commented Jul 3, 2026

Copy link
Copy Markdown
Owner

One flake for all four machines: k / mini / SOC-Kris-Williams (nix-darwin, aarch64-darwin) + nebula (NixOS, x86_64-linux) — joining the orphan nebula-snowglobe branch (140 commits) with main via --allow-unrelated-histories so both histories stay reachable.

Highlights

  • One nixpkgs on nixos-unstable (NixOS-test-gated; safe for darwin). Inputs were subsequently nix flake updated on-branch; the snowglobe-lib libvirtd-qemuqemu option rename and the vesktop/pnpm insecure flag that surfaced are fixed in the review commit.
  • Shared stow tree (home/): git config unified (aliases + lfs + mergetool + difftastic + delta + codeberg op credential helper), one OS-guarded .zshrc, ghostty split into shared config + per-OS generated os.conf (config-file = ?os.conf), nvim reconciled on main's newer tree. Per-OS scoping via mirrored skip lists in the two dotfiles-stow modules; the shared restow/self-heal script body lives in lib/stow-restow-script.nix.
  • NixOS twins for nh (nh os nrs/nrb/nrt), git, direnv + direnv-nom (was transitive-only on nebula), ghostty; nebula's packages/ dissolved into pkgs/ + overlays per convention (overlays/podman.nix platform-guarded).
  • sops-nix on both OSes: explicit input, darwin module with age identities derived from SSH host keys (ssh-to-age), k carries an encrypted smoke-test secret (verified decrypting live). gpg-agent + pass-xdg class-wide on both OSes (enableSSHSupport = false — 1Password keeps SSH/signing).
  • Docs/knowledge: AGENTS.md + README rewritten for dual-OS; nebula's 11 manuals under docs/; decision records nixos-darwin-unification, stow-os-skip-lists, sops-darwin-ssh-host-keys; okf validate clean.
  • xhigh multi-agent review applied (final commit): 11 confirmed findings fixed — two eval breakers (snowglobe rename, pnpm flag), a shell-injection vector and a silent-crash in cbissues, the PAM ${HOME} bug defeating the 1Password sudo socket on nebula, restored aarch64-linux package outputs, a reintroduced duplicate <leader>n keymap, plus cleanup (shared stow script extraction, updates.log untracked, stale comment refs, shell color convention).

Verification

  • nix flake check exit 0; all three darwin systems build; nix store diff-closures vs the pre-merge system shows only intended additions (sops pipeline, gpg-agent, pass stack, difftastic, os.conf) + channel drift.
  • nix eval .#nixosConfigurations.nebula.config.system.build.toplevel.drvPath — full cross-eval from a Mac passes on the updated inputs.
  • Live nrs on k: skip lists honored (no Linux configs in $HOME), os.conf linked, gpg-agent loaded, /run/secrets materialized.
  • statix/deadnix: only four pre-existing nebula-config style warnings (noted in the lint commit).

Post-merge per-machine steps

  • mini / SOC-Kris-Williams: nrs, then ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub → add recipients to .sops.yaml.
  • nebula: switch the checkout from nebula-snowglobe to main, then nrs (first switch renames the qemu option in place and picks up the direnv/git/nh modules).

kriswill added 30 commits June 6, 2026 17:54
Initial import of the nebula host flake from ~/src/dotfiles onto a bare
(orphan) branch: snowglobe-lib-based system config, the GNU Stow dotfiles
tree under home/, and the gh (GitHub CLI) system package.
Set background to a neutral #0e0e0e (was cool-navy #101014) and lower
background-opacity to 0.92. Pin the libadwaita window surface to #0e0e0e
for both active and :backdrop states in corners.css, and add
window-theme = ghostty, so the transparent focused window no longer
picks up libadwaita's default blue accent tint.
Add a stow-managed git config that signs commits and tags with the
SSH key held in 1Password (op-ssh-sign), plus the matching
allowed_signers entry so signatures verify locally.
niri has no settings daemon / running xdg-desktop-portal to broadcast a
color-scheme, so GTK apps (LibreOffice's gtk3 VCL plugin included) default
to light and override any in-app dark theme. Set GTK_THEME=Adwaita:dark as
a session variable, plus the gtk-3/4 prefer-dark settings.ini hints.
Backup/Template/Gallery/AutoText/AutoCorrect/dictionary defaulted to
~/.config/libreoffice/4/user/<...>. Seed registrymodifications.xcu so
backups go to ~/.local/state and the rest to ~/.local/share, keeping the
profile/settings in ~/.config. Exact xcu syntax verified by round-tripping
through a live soffice launch.
Claude Code MCP servers launch via npx (e.g. the chrome-devtools-mcp
plugin runs 'npx chrome-devtools-mcp@…'), which fails on stock NixOS
since no node runtime is on PATH. Add nodejs (node/npm/npx) and bun.
- Wrap stow + libreoffice-paths activation snippets in subshells so their
  exit/set -u don't abort or leak into the rest of NixOS activation (was
  silently breaking boot of new generations).
- Enable boot.loader.grub.useOSProber so the Windows install on a separate
  disk shows up in the menu (reason nebula stays on GRUB, not systemd-boot).
- Add docs/bootloader-issues-jun-06.md documenting the investigation.
Restore the neovim lua config into the stow tree (home/nvim/) and add
nixosModules/default/neovim.nix providing the LSP servers, formatters,
linters, and tools the config needs on PATH.

- programs.neovim {enable, viAlias, vimAlias} (snowglobe-lib replaces the
  nixpkgs module, so no defaultEditor/extraPackages); EDITOR set separately.
- Tools ported from the main branch; dockerfile-language-server-nodejs
  renamed to dockerfile-language-server.
- vtsls rebuilt against nodejs_24 so node 22 stays out of the closure.
- gcc added so nvim-treesitter can compile parsers.
kriswill added 29 commits June 20, 2026 13:09
The initial encrypted snapshot (prev commit) was captured while Helium was
running, so Cookies/Login Data were mid-write. Re-captured with Helium quit
for a consistent point-in-time; Preferences/Local State also picked up state
Helium flushes on close.
- compare-skip: when the age identity can't be resolved (e.g. 1Password
  locked), leave the existing .age untouched instead of re-encrypting — a
  fresh age nonce over identical plaintext churned git on every unattended
  capture. Warn and tell the user to unlock + re-run to update.
- HELIUM_AGE_OP_REF: use no-colon ${VAR-…} so an explicit empty value really
  disables the op route, as the comment claims (${VAR:-…} fell back to the
  default on empty).
- diff: decrypt to a temp file and check the exit status before diffing — in a
  <(…) process substitution age's failure was lost, so a decrypt error showed
  as the whole live file differing. Now prints a clear per-file marker.
Flip nixpkgs ownership: this flake now pins
github:NixOS/nixpkgs/nixos-unstable directly and snowglobe-lib follows
it (inputs.nixpkgs.follows = "nixpkgs"), so there's a single nixpkgs in
the store and we control the rev. Pulled unstable to 567a49d
(2026-06-16)
for the latest security fixes and the newer in-tree NTFS/ntsync driver
off the Windows disk. Default kernel is now 7.1.

corefreq disabled (programs.corefreq.enable = false): its out-of-tree
module fails to build on 7.1 (cppc_perf_fb_ctrs.reference_perf →
reference). Re-enable when upstream catches up.

Notable package changes (nvd /run/booted-system → /run/current-system):

  kernel / GPU
    linux              7.0.11 -> 7.1
    nvidia-open        595.80-7.0.11 -> 595.80-7.1

  security-relevant
    chromium           149.0.7827.53 -> 149.0.7827.114
    electron           40.10.2 -> 40.10.3
    apache-httpd       2.4.67 -> 2.4.68
    dnsmasq            2.92rel2 -> 2.93
    curl               8.20.0 (rebuild)
    gnupg              2.4.9 (rebuild)
    nss                3.124 -> 3.125
    1password-cli      2.34.0 -> 2.34.1

  virt
    libvirt            12.2.0 -> 12.4.0
    qemu-host-cpu-only 11.0.0 -> 11.0.1

  filesystems
    zfs-user           2.4.2 -> 2.4.3
    xfsprogs           6.19.0 -> 7.0.1

  desktop / KDE Frameworks
    kf6 (kio, kconfig, kirigami, …) 6.26.0 -> 6.27.0
    breeze-icons       6.26.0 -> 6.27.0
    uwsm               0.26.4 -> 0.26.5
    kitty              0.47.1 -> 0.47.4
    mangohud           0.8.3 -> 0.8.4

  tooling
    neovim             0.12.2 -> 0.12.3
    gh                 2.93.0 -> 2.94.0
    fastfetch          2.63.1 -> 2.64.2
    statix             2026-05-14 -> 0.5.8 (2026-06-13)

  removed
    corefreq 2.1.0 (+ unit-corefreq.service)

Closure: 2965 -> 2927 paths (-318.1 MiB).
The PNG logo fell back to ASCII inside tmux because fastfetch hard-blocks
image logos when TERM is exactly "screen", and tmux had no default-terminal
set (so TERM=screen).

- tmux: set default-terminal "tmux-256color" so TERM != "screen"
- fastfetch: logo.type kitty-icat (only protocol that renders in ghostty+tmux;
  native kitty sends a malformed payload, kitty-direct/sixel fall back)
- docs: add docs/fastfetch.md and docs/tmux.md (with the ghostty+grim test
  harness), register both in the CLAUDE.md manuals table
DP-3 (landscape OLED) had its current wallpaper pointing at the portrait
1440x3440 image — it falls back to the global [wallpaper.default] (portrait)
whenever the OLED drops its DisplayPort signal and Noctalia re-detects it.

- Set DP-3 back to horizontal-wide/nebula-1.jpg and captured the corrected
  settings.toml snapshot (config/noctalia/settings.toml)
- Add Super+Shift+W: re-applies each monitor's intended wallpaper from the
  snapshot via tomato + 'noctalia msg wallpaper-set' — a panic button for the
  recurring fallback. Update target with 'noctalia-config capture'.
The previous bind re-applied exact filenames from the snapshot, so it broke
the moment the image files were swapped (stale paths -> wallpaper-set on a
missing file = no-op). Now it reads each monitor's configured directory
([wallpaper.monitor.<conn>].directory) and sets the first image found there —
filename-agnostic, deterministic, survives swapping the wallpapers.
noctalia.lua overrode inactive_border to its surface colour (rgb 131313),
lighter than ghostty (#0e0e0e), leaving a faint line between tiled ghostty
windows in gapless layouts. Add noctalia-overrides.lua (required last so it
wins) setting inactive border to rgb(050505) -- tuned so the opaque border,
after the HDR/gamma lift that ghostty content does not get, renders ~rgb13,
matching ghostty interior.
`nix flake update` + `nrs`. nvd diff (system gen 72 → 73) shows only one
realized package change in the closure; nixpkgs itself was unchanged
(26.11.20260616.567a49d both sides), so the other input bumps were no-ops
for the built system.

Realized change (nvd):
  hyprland  0.55.0+date=2026-06-19_d486a5f → 0.55.0+date=2026-06-21_5a7078d
  closure 2952→2952 paths (40 added/40 removed, delta +0, disk +801.4KiB)

Inputs bumped in flake.lock:
  hyprland                  d486a5f → 5a7078d
  snowglobe-lib            38e3a18 → a676eee  (revCount 1657 → 1663)
  sops-nix                 9ed6585 → 420f8d2
  noctalia-shell           a7af758 → ca1d211
  nixpkgs-stable           bd0ff2d → a037402
  Jovian-NixOS             e509169 → ee34630
  nix-index-database       1a2ea89 → 854d04e
  nix-post-build-hook-queue a670d87 → 3b04dc1
…S3 wake

Immediate wake-from-S3 on nebula traced to MSI BIOS "Wake Up Event By: BIOS"
overriding all Linux wakeup controls; setting it to OS fixes it. Documents the
fix, wake-source diagnostics, and the xHCI/PME red herrings.

Claude-Session: https://claude.ai/code/session_01HXHkGCV6M7EDz8xrsE4Wou
Define Hyprland directly in modules/hosts/nebula/hyprland.nix instead of via
snowglobe-lib.desktop.hyprland.enable. That module force-enabled hyprlock
(which auto-enables hypridle via nixpkgs' hyprlock module) plus
kitty/dolphin/hyprlauncher — none used here (ghostty, fuzzel/Noctalia
launcher, Noctalia lock). hyprlock comes from nixpkgs but the hyprland-packages
overlay swaps hyprutils globally for the flake's newer one, so flake updates
that outpaced nixpkgs' hyprlock broke the build (Seat.cpp CSharedPointer →
bool). The new module re-asserts the shared snowglobe desktop layer
(system.hasDesktop + desktop.{enable,installWaylandDeps}) niri used to provide,
plus programs.fuzzel.

Remove niri entirely (host is Hyprland-only now): the snowglobe niri desktop
module, programs.kanshi + home/kanshi (drove niri displays; fought hl.monitor
under Hyprland), home/niri, swaybg (painted niri's wallpaper; Noctalia does
Hyprland's), and the now-dead kanshi-stop autostart hook. Refresh stale niri
attributions in docs and comments.
The shared git config hardcoded op-ssh-sign at the NixOS system-profile path
(/run/current-system/sw/bin), which is absent on macOS (1Password ships it in
the .app bundle), so every signed commit failed on the Mac. Split the program
into signing-{linux,macos}.inc and select by home-dir prefix (/home vs /Users)
— a reliable OS discriminator since all repos live under $HOME, and gitdir
resolves symlinks so nebula's /etc/nixos rebuild path still matches Linux.
Forward-compatible with the planned single Linux+macOS branch.
nebula-snowglobe lacked the home/gh package that main carries, so switching
this checkout to nebula left ~/.config/gh/config.yml a dangling symlink (stow
had linked it while on main) — gh then errored on startup and its git
credential helper returned nothing, breaking pushes. Add the same config.yml
(comment pointed at modules/nixos/dotfiles-stow.nix) so both branches stow an
identical gh package and neither dangles. Auth stays in the untracked hosts.yml.
Joins the orphan nebula-snowglobe branch (NixOS host nebula, 140 commits)
with main via --allow-unrelated-histories. Both branches were deliberately
pre-shaped for this: identical dendritic skeleton (flake-parts + import-tree),
disjoint class dirs (modules/darwin/ vs modules/nixos/), and the includeIf
gitdir OS-discriminator already shared. Resolutions:

- flake.nix: input union. Single nixpkgs on nixos-unstable (NixOS-test-gated;
  safe for darwin), initially pinned to nebula's prior rev to keep nebula
  byte-stable. import-tree unified on vic/import-tree. Explicit sops-nix input
  (snowglobe-lib now follows it) for upcoming darwin secrets support.
  snowglobe-lib/hyprland/noctalia/tomato pinned at nebula's locked revs
  (unstable HEAD renamed snowglobe-lib.libvirtd-qemu; bump separately).
- modules/flake-parts.nix: systems = aarch64-darwin + x86_64-linux.
- modules/packages.nix: main's shape; nebula's packages/ dissolved into pkgs/
  per AGENTS.md (aggregator + shell.nix + devshell.nix removed, dev shell
  gains nil/nom/sops/age/ssh-to-age/stow). kitten/podman/apple-container
  guarded darwin; wowup/flatpak-user guarded linux. Linux re-export block
  obsoleted by the systems union.
- modules/overlays.nix: union; overlays/podman.nix platform-guarded (Linux
  keeps nixpkgs' podman); per-package overlays for the migrated pkgs;
  tomato inline (closes over inputs); wowPath default moved into
  pkgs/wowup.nix so overlay + packages output stay in sync.
- home/: git config = union (aliases/lfs/mergetool + difftastic/delta/
  codeberg helper); .zshrc = main's superset with OS guards (direnv/fzf
  guarded); tmux gains default-terminal tmux-256color; fastfetch takes
  nebula's glyphs + kernel module, keeps logo.png; ghostty split into shared
  config + config-file = ?os.conf (per-OS file generated by each ghostty
  module — follow-up commit); nvim keeps main's tree (nebula had drifted
  behind) + nebula's snacks notifications keymap; the duplicate <M-B> debug
  bind was dropped as accidental.
- .gitignore union; main's CLAUDE.md/README/.envrc retained (docs rewrite
  follows).

nix flake show --all-systems: all 3 darwin hosts + nebula evaluate.
Stow scoping: home/ deploys wholesale on both OSes, so each dotfiles-stow
module now carries an explicit skip list for the other OS's packages (darwin
skips the Wayland/freedesktop set; nixos skips the Homebrew/Library/1Password-
socket set). Unlisted packages deploy everywhere — the right default for
cross-platform CLI configs. Lists mirror each other in comments.

NixOS twins for the darwin-only machinery (each existing darwin module
untouched unless noted):

- nh.nix: nrs/nrb/nrt as real executables via `nh os` (replaces the zsh
  wheel-gated shell functions; flake dir resolved with readlink -f since
  nix --flake won't follow the ~/src/dotfiles symlink on nebula).
- git.nix: git/gh/git-lfs/delta/diffnav/difftastic (moved out of nebula's
  configuration.nix; lfs + difftool now referenced by the shared git config).
  darwin git.nix gains difftastic for the same reason.
- direnv.nix + direnv-nom.nix: explicit direnv provisioning (was transitive
  on nebula) with the nix-direnv stdlib + nom wrapper linked via tmpfiles;
  wrapper text extracted to lib/direnv-nom-wrapper.nix and shared by both
  classes. direnv dropped from the nixos stow skip list.
- ghostty.nix: programs.ghostty.enable (moved from configuration.nix) + the
  Linux half of the shared config as a generated os.conf (tmpfiles); darwin
  ghostty.nix generates the macOS half (window chrome, font 18, cmd+` quick
  terminal). The stowed config ends with `config-file = ?os.conf` so the
  per-OS file wins and a missing one is non-fatal.
- neovim.nix (nixos): + svelte-language-server (the merged nvim config
  enables svelte on both OSes now). zsh.nix (nixos): + eza (alias chain).

nix flake show --all-systems: all 4 host configurations evaluate.
- modules/darwin/sops.nix (universal): imports sops-nix's darwinModules.sops
  with the age identity derived from the host SSH key (ssh-to-age) — no new
  key material to manage. Inert until a host defines secrets.
- .sops.yaml: documents the per-host recipient workflow; adds k's recipient
  (derived from its ssh_host_ed25519_key) and a creation rule for
  modules/hosts/k/secrets.yaml. mini/SOC-Kris-Williams recipients TODO —
  derive on each machine.
- modules/hosts/k: encrypted smoke-test secret proving darwin decryption
  end-to-end (sops.defaultSopsFile + sops.secrets.sops-smoke-test).
- modules/{darwin,nixos}/gpg.nix: gpg-agent enabled class-wide on both OSes
  (moved out of nebula's configuration.nix), enableSSHSupport explicitly
  false — SSH auth and git signing stay with the 1Password agent.
- modules/darwin/pass.nix: pass-xdg on macOS (twin of modules/nixos/pass.nix),
  backed by the new gpg-agent.
…merge

- AGENTS.md: dual-OS overview (4 hosts, two module classes + twins
  convention), nixos build/eval commands, stow skip lists, config/ snapshot
  section, secrets workflow, updated file tree, docs/ manuals section.
- README: dual-OS intro + NixOS (nebula) setup section (checkout layout,
  symlink caveat, nrs/nrb/nrt on both OSes).
- knowledge/: okf scaffold stubs for the 22 new nixos modules/host/packages;
  decision records nixos-darwin-unification, stow-os-skip-lists,
  sops-darwin-ssh-host-keys; log.md milestone entry; okf index regenerated.
  okf validate: 127 files, 0 errors, 0 warnings.
…, inherit typescript)

Remaining statix warnings are pre-existing nebula host-config style
(repeated-keys in configuration.nix and the generated
hardware-configuration.nix) — left for a dedicated cleanup.
Correctness:
- nebula: snowglobe-lib.libvirtd-qemu.enable -> snowglobe-lib.qemu.enable —
  the flake update (5497cb0) moved snowglobe-lib to f0135ce, which renamed
  the option (verified 1:1: same libvirtd + qemu_kvm + virt-manager payload).
  Restores nebula evaluation, nixos-rebuild, and the cross-eval gate.
- nebula: permit insecure pnpm-10.29.2 (build-time dep of vesktop via
  programs.discord; flagged by nixpkgs b5aa0fb) with a dated removal note —
  second half of unbreaking nebula eval on the updated inputs.
- cbissues: strict owner/repo slug validation — $repo was spliced unescaped
  into a single-quoted command inside the fzf enter binding ($SHELL -c), so a
  crafted remote URL or argument containing a quote could inject commands.
  Reject, don't escape. Same validation in cbissue for its API URLs.
- cbissues: guard `--state` with a missing value — the loop's trailing shift
  hit $#==0 and killed the script silently under set -e (cbissue already had
  the guard for -l).
- sudo-1password: SSH_AUTH_SOCK must use the unbraced literal $HOME — NixOS
  writes sessionVariables into /etc/pam/environment via a plain
  replaceStrings ["$HOME"] substitution; the braced ${HOME} was written
  verbatim and resolved by pam_env as a nonexistent pam_env variable,
  silently breaking the agent socket path for PAM sessions.
- packages: restore the aarch64-linux re-export of ccglass +
  codebase-memory-mcp (deliberate coverage from 371fa1b that the merge
  dropped; the root systems list doesn't include aarch64-linux).
- helium-config: capture's read-failure message no longer claims "not valid
  JSON" for raw (cat) transforms.
- nvim: drop the <leader>n picker bind the merge reintroduced from the stale
  nebula copy — misc.lua owns <leader>n (same shadowing 2595d6c fixed once).

Cleanup:
- stow: extract the shared restow/self-heal script body to
  lib/stow-restow-script.nix (parameterized by home/skip/runAsUser), same
  pattern as lib/direnv-nom-wrapper.nix — a future healing fix can no longer
  land on only one OS.
- updates.log: untracked (gitignored) — machine-appended rebuild log nothing
  consumes; history retains the old entries.
- tomato/noctalia comments: point at keybindings.lua's toggleGaps instead of
  the deleted scripts/toggle-gaps.sh.
- cbissue/cbissues/helium-config: AGENTS.md shell color convention
  (RED/GREEN/YELLOW/BLUE/NC) with colored error output.

Verified: nebula toplevel cross-eval passes on the updated inputs; all three
darwin systems rebuild; cbissue/cbissues/helium-config packages build
(shellcheck); nix flake check exit 0; statix/deadnix show only the four
pre-existing nebula-config style warnings.
nixpkgs' vesktop deliberately pins CVE-flagged pnpm-10.29.2 (10.29.3+
crashes electron-builder < 26.8.2 at launch), so the nebula whitelist is
structural for now. The decision record captures the analysis and the
preferred whitelist-free exit — repack upstream's prebuilt AppImage like
wowup and set programs.discord.package — plus the re-test cadence on
nixpkgs bumps. okf validate: 128 files, 0 errors.
@kriswill
kriswill merged commit 0b8a629 into main Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant