NixDaemon

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

commit 18e943da85bf6f040c9319fec6583cd8323228f5
parent b1cf7609bfd7fad00480e2a6c1e3d4650362f1ea
Author: DAEMON <zer0sec.xp@icloud.com>
Date:   Sat, 10 Oct 2026 09:54:47 +0100

feat: XFCE and i3 desktops for VMs, VMware shared folders, Claude Code skill; x86-only checks

Diffstat:
MREADME.md | 15+++++++++++----
Mdocs/install.md | 32+++++++++++++++++++++++++-------
Mflake.lock | 48++++++++++++++++++++++++------------------------
Amodules/features/desktop/i3.nix | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amodules/features/desktop/x11.nix | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amodules/features/desktop/xfce.nix | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmodules/hosts/generic/_settings.nix | 19++++++++++++++++---
Mmodules/hosts/generic/default.nix | 27+++++++++++++++++++++------
Mmodules/hosts/generic/home.nix | 5+++++
Mmodules/parts.nix | 12++++++++++++
Askills/nixdaemon/SKILL.md | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 533 insertions(+), 44 deletions(-)

diff --git a/README.md b/README.md @@ -62,7 +62,9 @@ $ htbbox flag user 3f2a… # status follows: active → user | **Toolkit** | 23 categories, each one switch. 12 on by default (the CPTS set): recon, AD, web, pivot, crack, shells, payloads, wordlists, BloodHound CE, Python/impacket, GUI tools, core. 11 more one line away: DFIR, reversing, wireless, radio, hardware, C2, database, cloud, OSINT, social, mobile. | | **HTB workflow** | `htbvpn`/`htbup` (OpenVPN as a systemd unit), `htbtarget` (`$TARGET` in every terminal + `/etc/hosts` for Kerberos), `htbtime` (clock skew), `htbbox` (per-box tree and `box.json`), `htbscan`, `revshell`, `hcmode`, `htbcast` (session recording → write-up transcript), `payload-serve` | | **Arsenal** | `~/pentesting/` with permanent `$privesc $potatoes $mimikatz $ad $sharp $ligolo $chisel …` variables and `htbpaths` to find things. ligolo-ng, chisel, fscan and pspy cross-compiled from source for every OS/arch; the potato family, SharpCollection, PEASS, mimikatz, static nmap/socat — every download pinned by hash. | -| **Desktop** | Niri (scrolling Wayland) + Noctalia shell in Rosé Pine, or XFCE for VMs without 3D, or headless + SSH. | +| **Desktop** | Niri (scrolling Wayland) + Noctalia shell in Rosé Pine by default. For VMs without 3D, two X11 desktops in Rosé Pine, **off until you choose one**: XFCE (full desktop) or i3 (lightweight tiling). Or headless + SSH. | +| **VM support** | guest tools for VMware, VirtualBox, QEMU/KVM/UTM and Hyper-V from one setting; VMware/VirtualBox shared folders with `sharedFolders = true`. | +| **Claude Code skill** | `~/.claude/skills/nixdaemon` is installed for you, so `claude` knows how the system is built and how to troubleshoot it ([skills/nixdaemon](skills/nixdaemon/SKILL.md)). | | **Shell** | zsh, starship prompt with `$TARGET`, fzf, zoxide, kitty with tmux-style keys, Neovim (nvf), yazi. | | **Cards** | `pentest-cheat`, `niri-cheat`, `nix-cheat` — the whole workflow in the terminal, section by section. | | **Tests** | `nix flake check`: every category's binaries are resolved and run, impacket alias collisions are caught, and NixOS VM tests boot the VPN, target and clock helpers. | @@ -116,7 +118,8 @@ like this: system = "x86_64-linux"; # or "aarch64-linux" boot = "efi"; # or "bios" vm = "none"; # vmware | virtualbox | qemu | hyperv - desktop = "niri"; # xfce for VMs without 3D, none for headless + desktop = "niri"; # "xfce" / "i3" for VMs without 3D (off by default), "none" for headless + sharedFolders = false; # VMware /mnt/hgfs, VirtualBox /media/sf_* pentest = { dfir = false; reversing = true; wireless = false; … }; } ``` @@ -223,7 +226,8 @@ NixDaemon/ │ │ ├── _sets.nix category factory: package list → module + check + dev shell │ │ ├── htb.nix vpn.nix boxes.nix time.nix casts.nix helpers.nix │ │ └── payloads.nix paths.nix _pkgs/ the pinned, cross-built arsenal - │ └── desktop/ niri + noctalia as wrapped packages (`nix run .#niri`) + │ └── desktop/ niri + noctalia (`nix run .#niri`); xfce.nix, i3.nix, x11.nix for VMs +├── skills/nixdaemon/ the Claude Code skill: system map, traps, diagnosis └── home/ home-manager modules (terminal, prompt, neovim, cheats …) ``` @@ -240,7 +244,8 @@ without anyone's keys. | Symptom | Fix | |---|---| | "path does not exist" / missing attribute | `git add -A` — flakes only see tracked files | -| Black screen after logging in to Niri | no 3D in the VM: `desktop = "xfce"`, rebuild from a text console (Ctrl+Alt+F2) | +| Black screen after logging in to Niri | no 3D in the VM: `desktop = "xfce"` or `"i3"`, rebuild from a text console (Ctrl+Alt+F2) | +| Stuck on anything | run `claude` — the installed `nixdaemon` skill knows this system's layout and traps | | `Failed assertions: _settings.nix: …` | a value is misspelled; the message names it | | Doesn't boot after install | wrong `boot` mode or `biosDevice`; fix from the ISO and re-run `nixos-install` | | Build killed / frozen | out of RAM: add `--max-jobs 1 --cores 2` | @@ -249,6 +254,8 @@ without anyone's keys. More in [docs/install.md § 9](docs/install.md#9-when-something-goes-wrong). +> **Honest note.** NixDaemon is one person's toolkit opened up. The Niri desktop and the toolkit are what get daily use; XFCE, i3, aarch64 and some hypervisor combinations are newer and less tested, so things will not always work flawlessly. Issues and fixes are welcome. + <div align="center"><img src="docs/images/dividers/phosphor.png" width="600" alt=""/></div> <a id="08-documentation"></a> diff --git a/docs/install.md b/docs/install.md @@ -230,8 +230,8 @@ nix develop gitlab:DAEMON-404/NixDaemon#pentest-recon # one category: -ad -w Limits: the dev shell has the **tools**, not the machine. `htbvpn`, `htbtarget` (`/etc/hosts`), the `~/pentesting` arsenal and `sudo nmap` need the NixOS -install. On aarch64 Linux the per-category shells mostly work, the full -`#pentest` shell does not (some tools are x86_64-only). macOS is not +install. The dev shells are x86_64-linux only (several tools exist only +for x86_64); on ARM, install the generic host instead (Path A). macOS is not supported — use a VM ([UTM](https://mac.getutm.app/) + Path A). **WSL**: Path C works in WSL2. A full NixDaemon *inside* WSL is not supported @@ -245,21 +245,33 @@ Set `vm` in `_settings.nix` to install the matching guest tools (shared clipboard, automatic resolution, time sync). Give the VM **8 GB RAM, 4 CPUs, 100 GB disk** if you can. +> **The X11 desktops are off until you pick one.** `desktop` defaults to +> `"niri"`. For a VM without working 3D set it to `"xfce"` (a full desktop, +> Rosé Pine) or `"i3"` (a lightweight tiling window manager, Rosé Pine — the +> whole VM idles around 0.9 GB). Both are newer than the Niri desktop and less +> tested: expect the odd rough edge. + | Hypervisor | `vm =` | Firmware → `boot =` | Graphics → `desktop =` | |---|---|---|---| | **VMware** Workstation / Fusion (x86) | `"vmware"` | set *UEFI* in VM options → `"efi"` | enable *Accelerate 3D graphics* → `"niri"` | | **VMware Fusion** on Apple Silicon | `"vmware"` | UEFI → `"efi"`, `system = "aarch64-linux"` | 3D on → `"niri"`, else `"xfce"` | -| **VirtualBox** | `"virtualbox"` | default is BIOS → `"bios"` (`biosDevice = "/dev/sda"`); or tick *Enable EFI* → `"efi"` | VirtualBox's 3D is unreliable → `"xfce"` | +| **VirtualBox** | `"virtualbox"` | default is BIOS → `"bios"` (`biosDevice = "/dev/sda"`); or tick *Enable EFI* → `"efi"` | VirtualBox's 3D is unreliable → `"xfce"` or `"i3"` | | **QEMU / KVM / virt-manager** | `"qemu"` | OVMF (UEFI) → `"efi"`, SeaBIOS → `"bios"` | *Virtio* video with *3D acceleration* + Spice *OpenGL* → `"niri"`; otherwise `"xfce"` | -| **Proxmox** | `"qemu"` | OVMF → `"efi"` | no host GPU → `"xfce"`, or `"none"` + SSH | +| **Proxmox** | `"qemu"` | OVMF → `"efi"` | no host GPU → `"xfce"` / `"i3"`, or `"none"` + SSH | | **UTM** (Apple Silicon) | `"qemu"` | UEFI → `"efi"`, `system = "aarch64-linux"` | *virtio-gpu-gl-pci* → `"niri"`, else `"xfce"` | | **Parallels** (Apple Silicon) | `"none"` | UEFI → `"efi"`, `system = "aarch64-linux"` | `"xfce"` | -| **Hyper-V** | `"hyperv"` | Generation 2, **Secure Boot off** → `"efi"` | no 3D → `"xfce"` | +| **Hyper-V** | `"hyperv"` | Generation 2, **Secure Boot off** → `"efi"` | no 3D → `"xfce"` / `"i3"` | | **Cloud / headless server** | `"qemu"` or `"none"` | provider's default | `"none"`, `ssh = true` + your key | **Niri needs working 3D.** If you log in and get a black screen or are thrown back to the greeter, the VM has no usable OpenGL: switch to -`desktop = "xfce"` (see [§9](#9-when-something-goes-wrong)). +`desktop = "xfce"` or `"i3"` (see [§9](#9-when-something-goes-wrong)). + +**Shared folders**: set `sharedFolders = true;` and enable sharing in the +hypervisor too. VMware mounts them at `/mnt/hgfs/<name>` (on first access); +VirtualBox puts them at `/media/sf_<name>` and adds your user to `vboxsf`. +Under XFCE and i3, VMware's clipboard and auto-resize come from +open-vm-tools; on Niri (Wayland) the clipboard is not shared. **Disk device names differ**: VirtIO disks are `/dev/vda`, SATA/SCSI are `/dev/sda`, NVMe is `/dev/nvme0n1`. Check with `lsblk` before partitioning, @@ -345,7 +357,7 @@ then `git add` both files and install it with `--flake .#vm`. |---|---| | `error: … does not provide attribute … nixdaemon` or "path does not exist" | A new file is not tracked: `git add -A`, build again. | | `Failed assertions: _settings.nix: …` | A value in `_settings.nix` is misspelled; the message names it. | -| Black screen / back at the greeter after choosing Niri | No 3D in the VM. Set `desktop = "xfce"`, then from a text console (Ctrl+Alt+F2) run `sudo nixos-rebuild switch --flake ~/NixDaemon#nixdaemon`. | +| Black screen / back at the greeter after choosing Niri | No 3D in the VM. Set `desktop = "xfce"` (or `"i3"`), then from a text console (Ctrl+Alt+F2) run `sudo nixos-rebuild switch --flake ~/NixDaemon#nixdaemon`. | | Machine does not boot after install | Wrong `boot` mode, or BIOS `biosDevice` pointing at the wrong disk. Boot the ISO, mount (3.4), fix `_settings.nix`, run `nixos-install` again. | | `No space left on device` during install | The disk is too small (60 GB minimum), or the target is not mounted at `/mnt`. | | Install killed / machine freezes while building | Out of RAM: `--max-jobs 1 --cores 2`, or give the VM more memory. | @@ -353,5 +365,11 @@ then `git add` both files and install it with `--flake .#vm`. | Clock-skew errors from Kerberos tools | `htbtime` syncs your clock to the target's DC (and restores it after). | | You want the previous system back | Choose an older generation in the boot menu, or `sudo nixos-rebuild switch --rollback`. | +**Ask Claude.** NixDaemon installs a Claude Code skill at +`~/.claude/skills/nixdaemon` (source: [skills/nixdaemon/SKILL.md](../skills/nixdaemon/SKILL.md)): +run `claude` and describe the problem — it knows which file to edit, the +traps above and where the logs are. It shows changes before making them and +leaves `sudo` to you. + Still stuck? `nix flake check` runs every category's smoke test and the VM tests, and usually names the broken piece. diff --git a/flake.lock b/flake.lock @@ -145,11 +145,11 @@ ] }, "locked": { - "lastModified": 1791350709, - "narHash": "sha256-tZNylOUnjpSwXCbYUx6Ts5HjH7QI7eH2CRKfWCsN2RI=", + "lastModified": 1791609844, + "narHash": "sha256-SHgkXo3bVca+AjacDdyfy9uCpG9SiVWCOyKCsejryA8=", "owner": "caelestia-dots", "repo": "cli", - "rev": "ffaf093d5a486506709b881e0a057cd51c7babb6", + "rev": "b9a223a2cb7d98e67dd1219e16d67dda9ac4d052", "type": "github" }, "original": { @@ -170,11 +170,11 @@ "quickshell": "quickshell" }, "locked": { - "lastModified": 1791442302, - "narHash": "sha256-DvefCOgAzqMZbXbSJsg/R4CriOgQuUCWMXKtsUcPAFY=", + "lastModified": 1791557614, + "narHash": "sha256-E7lur3xaOv3z4PIpbTQ4V/a+hUKewisIlasfIDK0agk=", "owner": "caelestia-dots", "repo": "shell", - "rev": "c4abc387a2ee5b351620a12839f3378649c0a7a2", + "rev": "5f1c59c0665aad66e94710f67e4bebaeb65c35f4", "type": "github" }, "original": { @@ -335,11 +335,11 @@ ] }, "locked": { - "lastModified": 1791484883, - "narHash": "sha256-iKxFHJrg7Sltwhq3ssCZYQ4CFEDVvReVVuVuO9dj3sE=", + "lastModified": 1791600313, + "narHash": "sha256-/2gb2WTztITD/JU1XUCg63vDsIfJGLr0LDsjSCTTJc8=", "owner": "nix-community", "repo": "home-manager", - "rev": "dfadbe5162d5e86bc0808badec8f346f81b4b3f0", + "rev": "427c74a88a93cc9f856c8bb99bf6b4a5187a6b33", "type": "github" }, "original": { @@ -456,11 +456,11 @@ "xdph": "xdph" }, "locked": { - "lastModified": 1791470510, - "narHash": "sha256-gedubEBaDPiQZOWRPaGn/ZbbT1zqv4Fy6mcElfDC+iM=", + "lastModified": 1791545463, + "narHash": "sha256-EuTde/DbLBZ0nm8Ctj4Il3M5SKsWAKeDITvUuatu5+g=", "owner": "hyprwm", "repo": "Hyprland", - "rev": "5e04ae1f9fc44705799ed9c0f97b99a305c6db25", + "rev": "663b29b846e4bad17cfa29941f6d2fbb6b61f139", "type": "github" }, "original": { @@ -720,11 +720,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1791522838, - "narHash": "sha256-Q6VbwYvayJgN/7x4jI7KnSkd5NGyUwfUw57Dho4jJAE=", + "lastModified": 1791612911, + "narHash": "sha256-gG794N9c3eyZgIolqXwNuIuSbTNzOlMZ2p5MkXvNdGs=", "owner": "numtide", "repo": "llm-agents.nix", - "rev": "4bb57cff45b5554a02dba0cf7a8c7f4f010a864d", + "rev": "32b0d7443782bdeacea2bed59ef66d522b8a2097", "type": "github" }, "original": { @@ -852,11 +852,11 @@ ] }, "locked": { - "lastModified": 1791488733, - "narHash": "sha256-9rIjqnq43mKaiUCv0DBe8H0HFZQue0fADosuJxemoaU=", + "lastModified": 1791602170, + "narHash": "sha256-lKqmYifuU1gWhtmcgalvsk62zitc71fPX6wx9f6VX24=", "owner": "notashelf", "repo": "nvf", - "rev": "01d245e20e19c9e283fce3b0db8719c2a6bba5f5", + "rev": "9723961413fcd7268f73e27c0277a6da307bbce7", "type": "github" }, "original": { @@ -965,11 +965,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1790880877, - "narHash": "sha256-j42XqSKOAtuqtfHiElyHre2YS/h3Y3q1DjFnKODN1/k=", + "lastModified": 1791574276, + "narHash": "sha256-UMnLnZ3JlA7UTBNNNx1rNsXYoPP/cirXLCKGiQ/yDx8=", "owner": "nix-community", "repo": "stylix", - "rev": "7c065d1ed05381fceb2403b963c5ad150f32fe39", + "rev": "2856c4c6a845b9bec98828ef197abe176e4806bb", "type": "github" }, "original": { @@ -1189,11 +1189,11 @@ ] }, "locked": { - "lastModified": 1791521685, - "narHash": "sha256-S4ZBR4y3cQ6NbLDJqoQR48718TWCM1ofpjt8Mzo83Hg=", + "lastModified": 1791587008, + "narHash": "sha256-LOIfH4MtVc2KIFQI8ElOOgmgrgUzJjfx3OYu4VnTvIU=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "841dba01deda5e994a9838da9d2216f493fbaea9", + "rev": "a6359784912804e2bad0826b63454cf4493e885e", "type": "github" }, "original": { diff --git a/modules/features/desktop/i3.nix b/modules/features/desktop/i3.nix @@ -0,0 +1,160 @@ +# modules/features/desktop/i3.nix — i3, the lightweight option, in Rosé Pine. +# +# OFF unless the generic host sets `desktop = "i3";` (_settings.nix). A tiling +# window manager on X11: no compositor, no desktop environment, and it runs in +# any VM. In testing, a whole i3 VM with a terminal open sat at about 0.9 GB. The shared look +# (greeter, GTK colours, icons, cursor) is x11.nix. +# +# Super+Return terminal (kitty) Super+d launcher (rofi) +# Super+q close window Super+1..9 workspace +# Super+h/j/k/l focus Super+Shift+… move +# Super+f fullscreen Super+Shift+e log out +# Super+Shift+c reload config Super+Shift+r restart i3 +{ self, ... }: +{ + flake.nixosModules.desktop-i3 = + { pkgs, lib, user, ... }: + let + rp = self.lib.rosePine; + mod = "Mod4"; + in + { + imports = [ self.nixosModules.desktop-x11 ]; + + services.xserver.windowManager.i3.enable = true; + services.displayManager.defaultSession = "none+i3"; + fonts.packages = [ pkgs.font-awesome ]; # the bar's icons (i3status-rust awesome6) + + home-manager.users.${user} = hm: { + xsession.windowManager.i3 = { + enable = true; + config = { + modifier = mod; + # home-manager sorts the bindings, so without this i3 starts on the first + # one it reads — Super+0, workspace 10. + defaultWorkspace = "workspace number 1"; + terminal = "kitty"; + menu = "rofi -show drun"; + fonts = { names = [ "JetBrainsMono Nerd Font" ]; size = 10.0; }; + gaps = { inner = 6; outer = 2; }; + window = { border = 2; titlebar = false; }; + floating.border = 2; + + colors = { + background = rp.base; + focused = { border = rp.iris; background = rp.overlay; text = rp.text; indicator = rp.rose; childBorder = rp.iris; }; + focusedInactive = { border = rp.overlay; background = rp.base; text = rp.subtle; indicator = rp.overlay; childBorder = rp.overlay; }; + unfocused = { border = rp.overlay; background = rp.base; text = rp.muted; indicator = rp.overlay; childBorder = rp.overlay; }; + urgent = { border = rp.love; background = rp.love; text = rp.base; indicator = rp.love; childBorder = rp.love; }; + }; + + keybindings = lib.mkOptionDefault { + "${mod}+q" = "kill"; + "${mod}+h" = "focus left"; + "${mod}+j" = "focus down"; + "${mod}+k" = "focus up"; + "${mod}+l" = "focus right"; + "${mod}+Shift+h" = "move left"; + "${mod}+Shift+j" = "move down"; + "${mod}+Shift+k" = "move up"; + "${mod}+Shift+l" = "move right"; + }; + + startup = [ + { command = "xsetroot -solid '${rp.base}'"; notification = false; } + ]; + + bars = [ + { + position = "top"; + statusCommand = "i3status-rs ~/.config/i3status-rust/config-top.toml"; + fonts = { names = [ "JetBrainsMono Nerd Font" ]; size = 10.0; }; + colors = { + background = rp.base; + statusline = rp.text; + separator = rp.muted; + focusedWorkspace = { border = rp.iris; background = rp.iris; text = rp.base; }; + activeWorkspace = { border = rp.overlay; background = rp.overlay; text = rp.text; }; + inactiveWorkspace = { border = rp.base; background = rp.base; text = rp.subtle; }; + urgentWorkspace = { border = rp.love; background = rp.love; text = rp.base; }; + }; + } + ]; + }; + }; + + programs.i3status-rust = { + enable = true; + bars.top = { + theme = "native"; + icons = "awesome6"; + settings.theme.overrides = { + idle_bg = rp.base; + idle_fg = rp.text; + info_bg = rp.base; + info_fg = rp.foam; + good_bg = rp.base; + good_fg = rp.foam; + warning_bg = rp.base; + warning_fg = rp.gold; + critical_bg = rp.base; + critical_fg = rp.love; + separator = " "; + separator_bg = rp.base; + separator_fg = rp.base; + }; + blocks = [ + # the VPN tunnel, so you can see at a glance that you are on it + { block = "net"; device = "^tun"; format = " $icon tun $ip "; missing_format = ""; } + { block = "cpu"; format = " $icon $utilization "; } + { block = "memory"; format = " $icon $mem_used_percents "; } + { block = "disk_space"; path = "/"; format = " $icon $available "; } + { block = "sound"; } + { block = "time"; format = " $timestamp.datetime(f:'%a %d %b %H:%M') "; interval = 30; } + ]; + }; + }; + + programs.rofi = { + enable = true; + terminal = "kitty"; + font = "JetBrainsMono Nerd Font 11"; + theme = + let inherit (hm.config.lib.formats.rasi) mkLiteral; in + { + "*" = { + background-color = mkLiteral rp.base; + text-color = mkLiteral rp.text; + border-color = mkLiteral rp.iris; + }; + window = { border = mkLiteral "2px"; padding = mkLiteral "12px"; width = mkLiteral "40%"; }; + inputbar = { padding = mkLiteral "6px"; children = map mkLiteral [ "prompt" "entry" ]; }; + prompt = { text-color = mkLiteral rp.iris; padding = mkLiteral "0 8px 0 0"; }; + listview = { lines = 10; padding = mkLiteral "6px 0 0 0"; }; + element = { padding = mkLiteral "4px 6px"; }; + "element selected" = { background-color = mkLiteral rp.overlay; text-color = mkLiteral rp.rose; }; + element-text = { background-color = mkLiteral "inherit"; text-color = mkLiteral "inherit"; }; + element-icon = { background-color = mkLiteral "inherit"; size = mkLiteral "1.2em"; }; + }; + }; + + services.dunst = { + enable = true; + settings = { + global = { + font = "JetBrainsMono Nerd Font 10"; + frame_color = rp.iris; + frame_width = 2; + offset = "12x40"; + }; + urgency_low = { background = rp.base; foreground = rp.subtle; }; + urgency_normal = { background = rp.base; foreground = rp.text; }; + urgency_critical = { background = rp.base; foreground = rp.love; frame_color = rp.love; }; + }; + }; + + home.packages = with pkgs; [ xorg.xsetroot pavucontrol ]; + fonts.fontconfig.enable = true; + }; + }; +} diff --git a/modules/features/desktop/x11.nix b/modules/features/desktop/x11.nix @@ -0,0 +1,107 @@ +# modules/features/desktop/x11.nix — what the X11 desktops (xfce.nix, i3.nix) +# share: the LightDM greeter, GTK in Rosé Pine, icons, cursor and fonts. +# +# These exist for virtual machines. Niri (Wayland) wants working 3D, which +# VirtualBox, Hyper-V, Proxmox and plenty of VMware setups do not give a +# guest; X11 with a software renderer runs everywhere. Imported by the +# generic host only when `desktop` is "xfce" or "i3" (modules/hosts/generic). +# +# Rosé Pine for GTK: nixpkgs dropped rose-pine-gtk-theme (it needed the dead +# GTK2 murrine engine), so this is adw-gtk3-dark recoloured through GTK's +# named colours — adw-gtk3 reads @define-color overrides from gtk.css, so +# every GTK3/4 window, the XFCE panel and Thunar pick the palette up. +{ ... }: +let + # Rosé Pine (main) + rp = { + base = "#191724"; + surface = "#1f1d2e"; + overlay = "#26233a"; + muted = "#6e6a86"; + subtle = "#908caa"; + text = "#e0def4"; + love = "#eb6f92"; + gold = "#f6c177"; + rose = "#ebbcba"; + pine = "#31748f"; + foam = "#9ccfd8"; + iris = "#c4a7e7"; + hlMed = "#403d52"; + }; + + gtkCss = '' + @define-color accent_color ${rp.iris}; + @define-color accent_bg_color ${rp.iris}; + @define-color accent_fg_color ${rp.base}; + @define-color destructive_color ${rp.love}; + @define-color destructive_bg_color ${rp.love}; + @define-color success_color ${rp.foam}; + @define-color warning_color ${rp.gold}; + @define-color error_color ${rp.love}; + @define-color window_bg_color ${rp.base}; + @define-color window_fg_color ${rp.text}; + @define-color view_bg_color ${rp.surface}; + @define-color view_fg_color ${rp.text}; + @define-color headerbar_bg_color ${rp.surface}; + @define-color headerbar_fg_color ${rp.text}; + @define-color headerbar_backdrop_color ${rp.base}; + @define-color card_bg_color ${rp.surface}; + @define-color card_fg_color ${rp.text}; + @define-color popover_bg_color ${rp.overlay}; + @define-color popover_fg_color ${rp.text}; + @define-color dialog_bg_color ${rp.overlay}; + @define-color dialog_fg_color ${rp.text}; + @define-color sidebar_bg_color ${rp.surface}; + @define-color sidebar_fg_color ${rp.text}; + ''; +in +{ + flake.lib.rosePine = rp; + + flake.nixosModules.desktop-x11 = + { pkgs, lib, user, ... }: + { + services.xserver.enable = true; + + services.xserver.displayManager.lightdm = { + enable = true; + background = rp.base; # a colour, not an image: nothing to fetch + greeters.gtk = { + enable = true; + theme = { name = "adw-gtk3-dark"; package = pkgs.adw-gtk3; }; + iconTheme = { name = "rose-pine"; package = pkgs.rose-pine-icon-theme; }; + cursorTheme = { name = "BreezeX-RosePine-Linux"; package = pkgs.rose-pine-cursor; size = 24; }; + indicators = [ "~host" "~spacer" "~clock" "~spacer" "~session" "~power" ]; + clock-format = "%H:%M %a %d %b"; + }; + }; + + fonts.packages = with pkgs; [ inter ]; + + environment.systemPackages = with pkgs; [ + kitty # the terminal both desktops open + xclip # clipboard from the shell under X11 + ]; + + home-manager.users.${user} = { + gtk = { + enable = true; + theme = { name = "adw-gtk3-dark"; package = pkgs.adw-gtk3; }; + iconTheme = { name = "rose-pine"; package = pkgs.rose-pine-icon-theme; }; + cursorTheme = { name = "BreezeX-RosePine-Linux"; package = pkgs.rose-pine-cursor; size = 24; }; + font = { name = "Inter"; size = 10; }; + gtk3.extraCss = gtkCss; + gtk4.extraCss = gtkCss; + }; + home.pointerCursor = { + enable = true; + name = "BreezeX-RosePine-Linux"; + package = pkgs.rose-pine-cursor; + size = 24; + x11.enable = true; + gtk.enable = true; + }; + dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark"; + }; + }; +} diff --git a/modules/features/desktop/xfce.nix b/modules/features/desktop/xfce.nix @@ -0,0 +1,79 @@ +# modules/features/desktop/xfce.nix — XFCE in Rosé Pine, for VMs without 3D. +# +# OFF unless the generic host sets `desktop = "xfce";` (_settings.nix). The +# full desktop — panel, Whisker menu, Thunar, settings manager — on X11, which +# runs in every hypervisor including VirtualBox and Hyper-V. The shared look +# (greeter, GTK colours, icons, cursor) is x11.nix. +# +# The XFCE side is set through xfconf (home-manager's xfconf.settings), so it +# is applied at login and can still be changed in the settings manager; a +# rebuild puts these values back. +{ self, ... }: +{ + flake.nixosModules.desktop-xfce = + { pkgs, lib, user, ... }: + let + rp = self.lib.rosePine; + # xfce4-terminal and xfconf both want "#rrggbb" strings. + palette = lib.concatStringsSep ";" [ + rp.overlay rp.love rp.pine rp.gold rp.foam rp.iris rp.rose rp.text + rp.muted rp.love rp.pine rp.gold rp.foam rp.iris rp.rose rp.text + ]; + # The desktop background is per monitor, and the monitor's name depends + # on the hypervisor's display adapter. These cover VMware, VirtualBox, + # QEMU/virtio and Hyper-V; on real hardware set it once in Desktop + # settings. + monitors = [ "monitorVirtual-1" "monitorVirtual1" "monitorVGA-1" "monitorDP-1" "monitorHDMI-1" "monitor0" ]; + backdrop = lib.listToAttrs (lib.concatMap (m: [ + { name = "backdrop/screen0/${m}/workspace0/image-style"; value = 0; } # none: colour only + { name = "backdrop/screen0/${m}/workspace0/color-style"; value = 0; } # solid + { name = "backdrop/screen0/${m}/workspace0/rgba1"; value = [ 0.098 0.090 0.141 1.0 ]; } # base #191724 + ]) monitors); + in + { + imports = [ self.nixosModules.desktop-x11 ]; + + services.xserver.desktopManager.xfce.enable = true; + services.displayManager.defaultSession = "xfce"; + + environment.systemPackages = with pkgs; [ + xfce4-whiskermenu-plugin + xfce4-pulseaudio-plugin + xfce4-clipman-plugin + ]; + + home-manager.users.${user} = { + xfconf.settings = { + xsettings = { + "Net/ThemeName" = "adw-gtk3-dark"; + "Net/IconThemeName" = "rose-pine"; + "Gtk/CursorThemeName" = "BreezeX-RosePine-Linux"; + "Gtk/FontName" = "Inter 10"; + "Gtk/MonospaceFontName" = "JetBrainsMono Nerd Font 10"; + }; + xfwm4 = { + "general/theme" = "Default"; # draws its frame from the GTK colours above + "general/title_font" = "Inter Bold 10"; + "general/use_compositing" = false; # cheaper, and VMs rarely accelerate it + "general/workspace_count" = 4; + }; + xfce4-desktop = backdrop // { + "desktop-icons/style" = 0; # no icons on the desktop + }; + xfce4-terminal = { + "color-use-theme" = false; + "color-foreground" = rp.text; + "color-background" = rp.base; + "color-cursor" = rp.rose; + "color-selection" = rp.text; + "color-selection-background" = rp.hlMed; + "color-palette" = palette; + "font-name" = "JetBrainsMono Nerd Font 11"; + "font-use-system" = false; + "scrolling-unlimited" = true; + }; + xfce4-session."general/SaveOnExit" = false; # the declared layout, every login + }; + }; + }; +} diff --git a/modules/hosts/generic/_settings.nix b/modules/hosts/generic/_settings.nix @@ -36,12 +36,25 @@ vm = "none"; # "niri" Niri (scrolling Wayland compositor) + Noctalia shell, Rosé Pine. - # Needs working 3D: bare metal, QEMU virtio-gpu with GL, VMware with - # 3D acceleration on. Super+Return opens a terminal. - # "xfce" XFCE on X11. Works in every VM, including VirtualBox without 3D. + # The default. Needs working 3D: bare metal, QEMU virtio-gpu with + # GL, VMware with 3D acceleration on. Super+Return opens a terminal. + # + # The two below are switched OFF until you choose one here. They are X11, + # so they run in every VM (VirtualBox, Hyper-V, Proxmox, VMware without 3D): + # "xfce" XFCE in Rosé Pine: panel, menu, file manager — a full desktop. + # "i3" i3 tiling window manager in Rosé Pine: the lightweight one (a + # whole i3 VM idles around 0.9 GB). Super+Return terminal, Super+d menu. + # They are newer than the Niri desktop and less tested: expect the odd rough + # edge (theming in some apps, resize in some hypervisors). Report what breaks. + # # "none" no desktop: console + SSH only. desktop = "niri"; + # Host folders shared into the VM. VMware: mounted at /mnt/hgfs/<name>. + # VirtualBox: your user joins vboxsf and shares appear at /media/sf_<name>. + # Turn the sharing on in the hypervisor's VM settings as well. + sharedFolders = false; + # Accept SSH logins (password auth stays off; put your public key below). ssh = false; sshKeys = [ ]; # [ "ssh-ed25519 AAAA… you@laptop" ] diff --git a/modules/hosts/generic/default.nix b/modules/hosts/generic/default.nix @@ -49,6 +49,7 @@ s = settings; niri = s.desktop == "niri"; xfce = s.desktop == "xfce"; + i3 = s.desktop == "i3"; gui = s.desktop != "none"; in { @@ -61,7 +62,10 @@ ++ lib.optionals niri [ self.nixosModules.desktop-options self.nixosModules.desktop-niri - ]; + ] + # The X11 desktops for VMs without 3D (modules/features/desktop/). + ++ lib.optional xfce self.nixosModules.desktop-xfce + ++ lib.optional i3 self.nixosModules.desktop-i3; config = lib.mkMerge [ { @@ -70,9 +74,10 @@ assertion = builtins.elem s.desktop [ "niri" "xfce" + "i3" "none" ]; - message = "_settings.nix: desktop must be \"niri\", \"xfce\" or \"none\" (got \"${s.desktop}\")"; + message = "_settings.nix: desktop must be \"niri\", \"xfce\", \"i3\" or \"none\" (got \"${s.desktop}\")"; } { assertion = builtins.elem s.vm [ @@ -129,7 +134,9 @@ extraGroups = [ "networkmanager" "wheel" - ]; + ] + # VirtualBox shared folders appear under /media/sf_<name> for this group. + ++ lib.optional (s.vm == "virtualbox" && (s.sharedFolders or false)) "vboxsf"; shell = pkgs.zsh; initialPassword = s.initialPassword; openssh.authorizedKeys.keys = s.sshKeys; @@ -173,6 +180,17 @@ services.qemuGuest.enable = s.vm == "qemu"; services.spice-vdagentd.enable = s.vm == "qemu"; # clipboard + resize in virt-manager/UTM + # VMware: open-vm-tools above gives time sync, clean shutdown, and — + # under X11 (xfce, i3) — clipboard and automatic resize. The SVGA + # adapter's 3D needs Mesa's vmwgfx driver, hence graphics on. + hardware.graphics.enable = lib.mkIf (s.vm == "vmware" && gui) true; + # Host folders shared in VMware's settings, at /mnt/hgfs/<name>. + fileSystems."/mnt/hgfs" = lib.mkIf (s.vm == "vmware" && (s.sharedFolders or false)) { + device = ".host:/"; + fsType = "fuse./run/current-system/sw/bin/vmhgfs-fuse"; + options = [ "umask=22" "uid=1000" "gid=100" "allow_other" "auto_unmount" "defaults" "nofail" "x-systemd.automount" ]; + }; + ##### Desktop ############################################################ services.greetd = lib.mkIf niri { @@ -188,9 +206,6 @@ ]; }; - services.xserver.enable = xfce; - services.xserver.desktopManager.xfce.enable = xfce; - services.xserver.displayManager.lightdm.enable = xfce; security.polkit.enable = lib.mkIf gui true; services.udisks2.enable = lib.mkIf gui true; diff --git a/modules/hosts/generic/home.nix b/modules/hosts/generic/home.nix @@ -25,6 +25,11 @@ stateVersion = "26.05"; }; programs.home-manager.enable = true; + + # The Claude Code skill for this system (skills/nixdaemon/SKILL.md), so + # `claude` anywhere on the machine knows how NixDaemon is put together and + # how to troubleshoot it — the way Omarchy ships one for its own system. + home.file.".claude/skills/nixdaemon/SKILL.md".source = ../../../skills/nixdaemon/SKILL.md; xdg.enable = true; # zsh from home-manager, since there is no dotfiles ZDOTDIR here. diff --git a/modules/parts.nix b/modules/parts.nix @@ -4,6 +4,18 @@ # aarch64-linux is here for the generic host (modules/hosts/generic) on ARM: # it needs the wrapped niri/noctalia packages built for that system. systems = [ "x86_64-linux" "aarch64-linux" ]; + + # aarch64 is here only for the generic host's desktop packages (niri, + # noctalia, gradia). The checks are the toolkit's x86_64 test suite — VM + # tests, and smoke tests of tools such as neo4j and aapt that only exist for + # x86_64 — and so are the dev shells; on aarch64 they would only fail + # `nix flake check --all-systems`. They stay x86_64-only. + perSystem = + { system, lib, ... }: + lib.mkIf (system == "aarch64-linux") { + checks = lib.mkForce { }; + devShells = lib.mkForce { }; + }; imports = [ inputs.home-manager.flakeModules.home-manager # flake.homeModules / flake.homeConfigurations inputs.wrapper-modules.flakeModules.default # flake.wrappers, perSystem.wrappers diff --git a/skills/nixdaemon/SKILL.md b/skills/nixdaemon/SKILL.md @@ -0,0 +1,73 @@ +--- +name: nixdaemon +description: Use when the user is on a NixDaemon machine (NixOS pentest flake, usually at ~/NixDaemon) and asks to fix, change, configure, update or troubleshoot it — desktop black screen, rebuild errors, adding tools or categories, VM guest tools, shared folders, HTB VPN, htbbox/htbtarget/htbtime, Kerberos clock skew, rollback. +--- + +# NixDaemon + +## Overview + +NixDaemon is a NixOS flake: the whole machine is `~/NixDaemon`. Nothing is +installed by hand — change a file, rebuild, done; a bad rebuild is undone by +booting the previous generation. Answer from this map first, read code second. + +## Which machine is this? + +```sh +hostname; readlink /run/current-system # nixos-system-<hostname>-… +``` + +| Config | Who | Edit | +|---|---|---| +| `nixdaemon` (`modules/hosts/generic/`) | **every user** | `_settings.nix`, `_hardware-configuration.nix` | +| `nixos` (`modules/hosts/laptop/`) | the author's own laptop only | never on a user's machine — it needs his secrets | + +If `hostname` is not `nixdaemon`, the user renamed it in `_settings.nix`; +the flake output is still `#nixdaemon`. + +## Quick reference + +| Want | Do | +|---|---| +| Rebuild | `sudo nixos-rebuild switch --flake ~/NixDaemon#nixdaemon` (or `nh os switch -H nixdaemon`) | +| Update everything | `cd ~/NixDaemon && nix flake update`, then rebuild | +| Undo last rebuild | `sudo nixos-rebuild switch --rollback`, or pick an older generation at boot | +| Toolkit category on/off | `pentest = { reversing = true; … }` in `_settings.nix` | +| Desktop | `desktop = "niri" \| "xfce" \| "i3" \| "none"` in `_settings.nix` | +| VM guest tools | `vm = "vmware" \| "virtualbox" \| "qemu" \| "hyperv"` | +| VM shared folders | `sharedFolders = true;` → VMware `/mnt/hgfs/<name>`, VirtualBox `/media/sf_<name>` (also enable sharing in the hypervisor) | +| One extra package | `environment.systemPackages` in `modules/hosts/generic/default.nix` | +| A tool in a category | its `modules/features/pentest/<cat>.nix`: `packages` + `expectedBins` | +| Check before rebuilding | `nix flake check` (smoke-tests every category) | +| Cheat cards | `pentest-cheat [section]`, `niri-cheat`, `nix-cheat` | + +## Traps (each one has bitten someone) + +- **New file → `git add` it.** Flakes only see tracked files. Symptom: "path … does not exist" / missing attribute. Editing existing files needs no add. +- **New `.nix` under `modules/` is a flake-parts module**, not a NixOS module (import-tree loads every `*.nix` whose path has no `/_`). Wrap NixOS settings as `{ flake.nixosModules.generic = { pkgs, ... }: { … }; }`, or name it `_x.nix` and import it explicitly. +- **Niri needs 3D.** Black screen / thrown back to the greeter in a VM = no OpenGL → `desktop = "xfce"` or `"i3"`. Fix from a text console: Ctrl+Alt+F2 (VirtualBox: Right Ctrl+F2). +- **`htbtime` skews the clock → TLS fails.** `htbtime status`; run `htbtime off` before any rebuild, update or `git pull`. +- **A rebuild rewrites `/etc/hosts`** → run `htbtarget <ip> <names…>` (or `htbbox use <box>`) again after it. +- **aarch64**: BloodHound is off automatically; `pentest.mobile` cannot be enabled. +- **`hash mismatch` on a payload download** → upstream replaced a file: `pentest-update`, or switch that category off. +- **xfce and i3 are newer** than the niri desktop: theming and resize can be rough in some hypervisors. Say so rather than promising. + +## Diagnose + +| Symptom | Look at | +|---|---| +| Rebuild fails | the first `error:` line; `nix flake check` names the broken category | +| `Failed assertions: _settings.nix: …` | the message names the misspelled value | +| Desktop won't start | `journalctl -b -u display-manager -e` (xfce/i3) or `-u greetd` (niri) | +| Home config missing (prompt, kitty, i3 config) | `journalctl -b -u home-manager-$USER -e` | +| VPN | `htbvpn status`, `journalctl -u htbvpn@<profile> -e`; profiles live in `~/.config/htb/vpn/*.ovpn`, names `A-Za-z0-9_.-` only | +| Kerberos `KRB_AP_ERR_SKEW` | `htbtarget <dc-ip> dc01.<domain>`, then `htbtime`; `htbtime off` when done | +| Shared folder empty | hypervisor sharing enabled? `ls /mnt/hgfs` (automounts on access) | + +## Working rules + +- Show the change (diff) before editing `_settings.nix`; let the user run `sudo` + commands unless they ask you to. +- Prefer `nixos-rebuild boot` + reboot for kernel, boot-loader or desktop changes. +- Never "fix" by editing `/etc/nixos` or installing with `nix-env`/`nix profile`: + it is overwritten or drifts. The flake is the only source of truth.