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:
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.