NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

nix.md (17268B)


      1 # nix — rebuilding this machine from ~/NixDaemon
      2 
      3 NixOS **and** home-manager are one flake: `~/NixDaemon` (`NH_FLAKE` points at it).
      4 home-manager runs as a NixOS module, so **one rebuild does both**; there is no separate `home-manager switch` here.
      5 Terminal card: **`nix-cheat`** — sections `layout rebuild remote nh home search update rollback clean inspect add desktop dotfiles secrets repo shell`.
      6 
      7 ## layout — what lives where
      8 
      9 Dendritic: `flake.nix` is `flake-parts.lib.mkFlake … (import-tree ./modules)`; every `*.nix` under `modules/` is a
     10 flake-parts module that declares what it owns, and modules name each other through `self` (never by path).
     11 
     12 ```text
     13 ~/NixDaemon/flake.nix                 inputs only · outputs = import-tree ./modules
     14 modules/parts.nix                     systems, the home-manager + wrapper-modules flake modules
     15 modules/hosts/laptop/default.nix      flake.nixosConfigurations.nixos (modules = [ self.nixosModules.laptop ])
     16 modules/hosts/laptop/configuration.nix  nixosModules.laptop: imports laptop-* + features by name; daemon.desktop.* switches
     17 modules/hosts/laptop/*.nix            nixosModules.laptop-<name>: hardware, nvidia, ssd, nix-settings (nh), sops, toolbox, fan-*
     18 modules/features/workstation.nix      nixosModules.workstation (Claude, Obsidian, gh, glab)
     19 modules/features/home-manager.nix     nixosModules.home-manager: HM as a NixOS module → self.homeModules.daemonsec
     20 modules/features/desktop/options.nix  daemon.desktop.hyprland.enable · daemon.desktop.niri.enable
     21 modules/features/desktop/niri.nix     packages.niri (wrapped) + nixosModules.desktop-niri
     22 modules/features/desktop/noctalia.nix packages.noctalia (wrapped, Rosé Pine)
     23 modules/home/default.nix              homeModules.daemonsec: imports every homeModules.<name>
     24 modules/home/tools.nix                packages: the toolbox closure and the general CLI tools  ← add packages here
     25 modules/home/dotfiles.nix             every dotfile from ~/git/daemon-sec-dotfiles, as symlinks  ← add dotfile paths here
     26 modules/home/hyprland.nix, hypr/*.lua Hyprland config + helpers (gated on daemon.desktop.hyprland)
     27 modules/home/caelestia.nix            the Caelestia shell, its CLI, wallpaper dir (same gate)
     28 modules/hosts/laptop/sops.nix         sops-nix (system) · modules/home/sops.nix (user) · secrets/secrets.yaml · .sops.yaml
     29 ```
     30 
     31 ## rebuild — nixos-rebuild, the plain way
     32 
     33 ```sh
     34 cd ~/NixDaemon
     35 sudo nixos-rebuild switch --flake .#nixos      # build, activate now, make it the boot default
     36 sudo nixos-rebuild boot   --flake .#nixos      # build, activate on next boot only (kernel/driver changes)
     37 sudo nixos-rebuild test   --flake .#nixos      # activate now, NOT the boot default (try something out)
     38 nixos-rebuild build       --flake .#nixos      # just build → ./result, no sudo, nothing changes
     39 sudo nixos-rebuild switch --flake ~/NixDaemon#nixos   # same, from any directory
     40 ```
     41 
     42 - `#nixos` is the configuration name (= hostname) and the only one in the flake (the old `#bootstrap` stage is gone).
     43 - **New files must be known to git** or the flake does not see them: `git add path` first (see *repo*).
     44 - A rebuild activates home-manager too; its log: `journalctl --user -u home-manager-daemonsec -b` or `systemctl status home-manager-daemonsec`.
     45 
     46 ## remote — build straight from the GitLab repo
     47 
     48 The flake does not have to be checked out: any `nix` command takes a **flake reference** to the repo.
     49 The repo is **private**, so use the ssh form (the key in ~/.ssh/config is registered on GitLab); `?ref=main` pins a branch, `?rev=<sha>` a commit.
     50 
     51 ```sh
     52 REPO='git+ssh://git@gitlab.com/DAEMON-404/NixDaemon.git'
     53 sudo nixos-rebuild switch --flake "$REPO#nixos"                  # this machine, from the pushed main
     54 sudo nixos-rebuild boot   --flake "$REPO?ref=main#nixos"
     55 nh os switch "$REPO"                                             # nh takes the same reference
     56 nix build "$REPO#nixosConfigurations.nixos.config.system.build.toplevel" --no-link --print-out-paths
     57 nix flake show "$REPO"                                           # what the repo exports
     58 nix flake metadata "$REPO"                                       # which commit nix resolved
     59 # root (sudo) fetches with root's ssh: either run the fetch as you first (nix build …, cached), or give
     60 # root the key via /root/.ssh/config, or use an https token in ~/.config/nix/netrc (machine gitlab.com …).
     61 ```
     62 
     63 **Fresh machine, from the live ISO** (disk already partitioned and mounted at /mnt):
     64 ```sh
     65 sudo nixos-generate-config --root /mnt --show-hardware-config > hardware-configuration.nix   # compare with modules/hosts/laptop/hardware.nix
     66 git clone git@gitlab.com:DAEMON-404/NixDaemon.git ~/NixDaemon && cd ~/NixDaemon   # ssh key first (see *secrets*)
     67 # paste its body into modules/hosts/laptop/hardware.nix (inside the laptop-hardware wrapper; a raw
     68 # hardware-configuration.nix under modules/ would be loaded by import-tree as a flake-parts module), then:
     69 sudo nixos-install --flake .#nixos
     70 ```
     71 Afterwards: clone `~/git/daemon-sec-dotfiles` (every dotfile links there), restore the age key to
     72 `~/.config/sops/age/keys.txt` and `/var/lib/sops-nix/key.txt` (see *secrets*), and the toolbox to `~/.local/bin`.
     73 
     74 A remote reference is fetched fresh each time (nix caches it briefly, `--refresh` forces it); the local
     75 checkout at `~/NixDaemon` (`NH_FLAKE`) is the default for day-to-day work.
     76 
     77 ## nh — the same, with a diff and a progress tree
     78 
     79 `nh` wraps nixos-rebuild: builds with nix-output-monitor, shows an **nvd diff** of what changes, then asks for sudo.
     80 
     81 ```sh
     82 nh os switch                 # = sudo nixos-rebuild switch --flake ~/NixDaemon (NH_FLAKE), config picked by hostname
     83 nh os boot                   # build + boot default, activate on reboot
     84 nh os test                   # activate now, not the boot default
     85 nh os build                  # build only, no activation, no sudo
     86 nh os switch --dry           # show what would happen, do nothing
     87 nh os switch --ask           # show the diff, then confirm before activating
     88 nh os switch -H nixos        # pick a configuration by name (-H = hostname/attr); this flake has one
     89 nh os switch -u              # `nix flake update` first, then switch (updates EVERY input — see *update*)
     90 nh os info                   # list system generations
     91 nh os rollback               # go back one generation (see *rollback*)
     92 nh os switch -- --show-trace # anything after -- goes to nix build (debug an eval error)
     93 ```
     94 
     95 ## home — home-manager in this setup
     96 
     97 - home-manager is **inside** the NixOS build (modules/features/home-manager.nix: user `daemonsec` → `self.homeModules.daemonsec`, i.e. modules/home/).
     98   **`nh os switch` rebuilds it**; `nh home switch` / `home-manager switch` do not apply here (no standalone HM profile).
     99 - Did my home change apply? `ls -l ~/.config/<thing>` (links point into /nix/store or the dotfiles checkout),
    100   `systemctl --user status home-manager-daemonsec`, or `journalctl --user -u home-manager-daemonsec -b`.
    101 - HM backs up a file it has to replace as `*.hm-bak` (`backupFileExtension` in modules/features/home-manager.nix). Delete the backup once happy.
    102 - Only build the home part (fast check, no sudo):
    103   `nix build ~/NixDaemon#nixosConfigurations.nixos.config.home-manager.users.daemonsec.home.activationPackage`
    104 
    105 ## search — finding packages and options
    106 
    107 ```sh
    108 ns                          # fuzzy search everything indexed: nixpkgs packages, NixOS and home-manager options
    109 ns kitty                    # start with a query
    110 ```
    111 Inside `ns` (fzf): type to filter, ↑↓ move, the right pane shows the description, version, homepage and
    112 the option's default. **Enter** prints the attribute name (paste it into `home.packages` in tools.nix),
    113 **ctrl-y** copies it, **ctrl-o** opens the homepage, **ctrl-s** the nixpkgs source, **Esc** quits.
    114 The index is built by `nix-search-tv` on first use (a few seconds) and refreshed by itself; by hand:
    115 ```sh
    116 nix-search-tv print | head                 # the raw list   ·  nix-search-tv preview firefox   # one entry
    117 nh search firefox                          # search.nixos.org from the terminal (needs network)
    118 nix search nixpkgs firefox                 # nix's own search (slow first run: builds an eval cache)
    119 nix eval ~/NixDaemon#nixosConfigurations.nixos.pkgs.firefox.version          # version THIS flake would install
    120 nix shell nixpkgs#firefox                  # try it without installing  ·  nix run nixpkgs#cowsay -- hi
    121 ```
    122 
    123 ## update — moving the inputs
    124 
    125 ```sh
    126 cd ~/NixDaemon
    127 nix flake update                       # all inputs → flake.lock (nixpkgs, home-manager, hyprland, caelestia, llm-agents)
    128 nix flake update nixpkgs home-manager  # only these inputs
    129 nix flake update hyprland              # Hyprland alone (uses hyprland.cachix.org, so usually no compile)
    130 nix flake lock                         # (re)write the lock without updating
    131 nix flake metadata                     # which revisions are locked right now
    132 nh os boot                             # then build it; boot = safest for kernel/driver bumps
    133 ```
    134 
    135 Hyprland and Caelestia pin each other (flake.nix comments): update `hyprland` and `caelestia-*` together if the shell breaks.
    136 
    137 ## rollback — when a generation misbehaves
    138 
    139 ```sh
    140 nh os rollback                          # previous generation, now
    141 sudo nixos-rebuild switch --rollback    # the same, plain
    142 nh os info                              # generation numbers
    143 sudo nix-env --switch-generation 5 -p /nix/var/nix/profiles/system && sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch
    144 ```
    145 
    146 - At power-on the **systemd-boot menu** lists every generation: pick an older one, it boots as it was.
    147 - A broken Hyprland login: Ctrl+Alt+F2 for a console, then one of the above.
    148 
    149 ## clean — store and generations
    150 
    151 ```sh
    152 nh clean all --keep 5 --keep-since 14d  # what the weekly timer runs (nix-settings.nix): drop old generations, GC
    153 nh clean all --dry                      # show what it would remove
    154 sudo nix-collect-garbage -d             # the blunt version: delete all old generations, then GC
    155 nix store gc                            # GC only (nothing referenced by a generation is touched)
    156 du -sh /nix/store                       # how big is it
    157 ```
    158 
    159 ## inspect — see before you switch
    160 
    161 ```sh
    162 nh os build && nvd diff /run/current-system result     # what a switch would change
    163 nix build ~/NixDaemon#nixosConfigurations.nixos.config.system.build.toplevel --no-link --print-out-paths
    164 nix eval ~/NixDaemon#nixosConfigurations.nixos.pkgs.uv.version                 # version a package would get
    165 nix eval ~/NixDaemon#nixosConfigurations.nixos.config.programs.zsh.enable      # read an option value
    166 ns <query>                              # fuzzy search nixpkgs + NixOS/HM options, with descriptions (nix-search-tv)
    167 nh search <package>                     # nixpkgs search (search.nixos.org)
    168 nix search nixpkgs <package>            # local search (first run builds an index)
    169 nix shell nixpkgs#<package>             # try a tool without installing it
    170 nix run nixpkgs#<package> -- --help
    171 nix flake check ~/NixDaemon             # evaluate every output
    172 nix flake show ~/NixDaemon
    173 ```
    174 
    175 ## add — packages, options, dotfiles, modules
    176 
    177 - **A package for the user**: `modules/home/tools.nix` → `home.packages` list → `nh os switch`.
    178 - **A system package / service**: `modules/hosts/laptop/configuration.nix` (`environment.systemPackages`, `services.*`).
    179 - **A dotfile from the checkout**: `modules/home/dotfiles.nix` → add its path to the list → `nh os switch`.
    180 - **A new home module**: `modules/home/<name>.nix` declaring `flake.homeModules.<name> = { pkgs, ... }: { … };`,
    181   then `<name>` in the imports list of `modules/home/default.nix`. A NixOS one: `flake.nixosModules.<name>` in
    182   `modules/features/<name>.nix`, named in `configuration.nix`. `scripts/wrap.sh FILE ATTR` wraps a plain module file.
    183 - **A Hyprland bind**: `modules/home/hypr/bindings.lua` (user) or `defaults.lua` (stock); a helper script goes in `hyprland.nix`.
    184 - **A Niri bind**: `modules/features/desktop/niri.nix` → `settings.binds` (validated at build: a typo fails `nix build .#niri`).
    185 - **A Caelestia setting**: `modules/home/caelestia.nix` → `programs.caelestia.settings`.
    186 - **A Noctalia setting**: tweak it in the GUI, `dump-noctalia-shell`, paste the differing keys into `noctalia.nix` → `settings`.
    187 - Then: `git add` anything new, `nh os switch`, and for Hyprland `hyprctl reload`. A running Niri keeps its old
    188   store config until you log in again, or: `niri msg action load-config-file --path "$(nix eval --raw ~/NixDaemon#niri)/niri-config.kdl"`.
    189 
    190 ## desktop — Hyprland + Caelestia or Niri + Noctalia
    191 
    192 Both are installed; tuigreet lists both sessions and remembers the last one, so switching is log out → pick the other.
    193 
    194 ```sh
    195 nix run ~/NixDaemon#niri          # try Niri nested in the current desktop (Alt is the modifier); Noctalia starts inside it
    196 nix run ~/NixDaemon#noctalia      # the bar alone
    197 ```
    198 ```nix
    199 # modules/hosts/laptop/configuration.nix — set one to false and `nh os switch` to drop it entirely
    200 daemon.desktop = { hyprland.enable = true; niri.enable = true; };
    201 ```
    202 
    203 ## dotfiles — the checkout is the source of truth
    204 
    205 - `~/git/daemon-sec-dotfiles/home/` is mirrored into `$HOME` by **symlinks** (dotfiles.nix): edit a file there and the live config changes at once — no rebuild.
    206 - A rebuild is needed only to add/remove a *path* in dotfiles.nix. `ls -l ~/.config/starship.toml` shows where a link goes.
    207 - zsh: `~/.dotfiles/config/*.zsh` → open a new shell (`exec zsh`). Plugins are vendored in `~/.dotfiles/config/plugins`.
    208 - Not linked on purpose (hypr, kitty, git, mimeapps, bash rc, .claude …): the list and reasons are at the top of dotfiles.nix.
    209 
    210 ## secrets — sops-nix and secretspec
    211 
    212 Two tools, two jobs. **sops-nix**: secrets that belong in this repo, encrypted with **age**, decrypted at
    213 activation to `/run/secrets/NAME` (system) or `~/.config/sops-nix/secrets/NAME` (user).
    214 **secretspec**: runtime secrets for a *project*, declared in its `secretspec.toml`, stored in the keyring.
    215 
    216 ```sh
    217 # sops — the one key: ~/.config/sops/age/keys.txt (user) = /var/lib/sops-nix/key.txt (system, root copy)
    218 sudo install -D -m 600 -o root -g root ~/.config/sops/age/keys.txt /var/lib/sops-nix/key.txt   # once per machine
    219 age-keygen -y ~/.config/sops/age/keys.txt         # the public key (recipient) in .sops.yaml
    220 sops secrets/secrets.yaml                          # edit: opens decrypted in $EDITOR, re-encrypts on save
    221 sops set secrets/secrets.yaml '["wifi-psk"]' '"hunter2"'   # add/replace one value without the editor
    222 sops -d secrets/secrets.yaml                       # print decrypted
    223 sops -d --extract '["example"]' secrets/secrets.yaml
    224 sops updatekeys secrets/secrets.yaml               # after editing the recipients in .sops.yaml
    225 ```
    226 
    227 Then declare it and rebuild:
    228 ```nix
    229 # modules/hosts/laptop/sops.nix (system)                     # modules/home/sops.nix (user)
    230 sops.secrets.wifi-psk = { };                         sops.secrets.my-token = { };
    231 sops.secrets."ssd.key".path = "/etc/secrets/ssd.key";   # e.g. the LUKS key instead of restoring it by hand
    232 ```
    233 `nh os switch` → `/run/secrets/wifi-psk` (root, 0400; `owner = "daemonsec";` to read it as the user).
    234 Check: `sudo ls -l /run/secrets/`, `systemctl --user status sops-nix` (user side), `journalctl -b | grep sops`.
    235 
    236 ```sh
    237 # secretspec — in a project directory (config: ~/.config/secretspec/config.toml → keyring, profile default)
    238 secretspec init                 # writes secretspec.toml (commit it; it holds names, never values)
    239 secretspec add DATABASE_URL     # declare a secret (flags: --required/--default/--description)
    240 secretspec check                # prompts for every missing value, stores it in the keyring
    241 secretspec set NAME            # (re)store one value
    242 secretspec run -- ./server      # run with the secrets in the environment
    243 secretspec export               # print them for another tool (shell `eval`)
    244 secretspec claude configure     # let Claude Code fetch its API credential through secretspec
    245 ```
    246 
    247 ## repo — committing ~/NixDaemon
    248 
    249 The repo is **jj, colocated with git** (`.jj` + `.git`). Nix flakes only see files git knows about, and jj keeps git's index at `@-`, so a *new* file is invisible to nix until it is added:
    250 
    251 ```sh
    252 cd ~/NixDaemon
    253 git add modules/home/new.nix       # make nix see a new file (modified tracked files are seen as-is)
    254 nh os build                        # or switch
    255 jj status                          # jj snapshots the working copy
    256 jj describe -m "what changed" && jj new      # seal it (the vault ritual) — or plain: git add -A && git commit
    257 jjmsg -c                           # or let the message write itself from the diff (jjmsg: print · -d describe · -e edit · -c commit)
    258 jj log                             # history
    259 ```
    260 
    261 A dirty tree only prints `warning: Git tree '/home/daemonsec/NixDaemon' is dirty`; it still builds.
    262 
    263 ## shell — after a switch
    264 
    265 - New packages are on PATH in a **new** shell (`exec zsh`); the login shell itself changes at the next login.
    266 - Hyprland: `hyprctl reload` re-reads `~/.config/hypr/*.lua`. Caelestia: `caelestia shell -r` restarts the shell.
    267 - Everything in the Nix profile: `ls /etc/profiles/per-user/daemonsec/bin`.