Personal NixOS configuration managing three machines: a desktop, a laptop, and a Steam Deck. The same flake defines every host via a shared module tree, with per-host overrides for hardware, networking, and services.
| Host | Arch | Root FS | Disk |
|---|---|---|---|
nixos-desktop |
x86_64 | btrfs on LUKS | Patriot P400L 1 TB |
nixos-laptop |
x86_64 | bcachefs | Samsung MZVLB1T0HALR 1 TB |
nixos-steamdeck |
x86_64 | btrfs | Micron 2500 1 TB |
- AMD Ryzen 7 5700X3D + Radeon RX 7900 XTX
- Dual 2560x1440@240 Hz monitors (DP-1, DP-3)
linuxPackages_latest, AMD pstate + prefcore, full preempt- aarch64 binfmt emulation
- GPU overclocking via
applyGpuSettingssystemd service (3050 MHz core, 334 W power cap) - AMDGPU overdrive enabled
- BTRFS
@rootwiped every boot viacleanupRoot - ROCm support enabled (Ollama)
- Samsung SSD, bcachefs root filesystem
linuxPackages_latest- Power management:
power-profiles-daemon,thermald,upower,powertop - No Steam or discrete GPU
- Jovian NixOS module (vendored prebuilt through
chaotic): Steam Deck device support, Decky Loader, Steam auto-start - Sway session (Plasma6 forced off)
- Extest layer, Gamescope, Proton CachyOS
- RetroArch with libretro cores, Prism Launcher, Moonlight
- VRAM-based swapfile (2 GB)
- Distributed builds off the desktop
/persistent/etc/nixos/
├── flake.nix # Entry point: hosts, inputs, packages, devShell
├── flake.lock
├── modules/ # Shared NixOS modules (every host)
│ ├── default.nix
│ ├── chaotic.nix # Chaotic NG (vendors prebuilt Jovian; Mesa Git off)
│ ├── lix.nix # Lix Nix implementation overlay
│ ├── hardware/ # Bluetooth, uinput, graphics, QMK, redistributable firmware
│ ├── programs/ # nix-index, UWSM/sway, fish, wireshark, gnupg,
│ │ gaming (Steam/Gamescope), sops-pass
│ ├── security/ # ACME, polkit, apparmor, rtkit, no sudo
│ ├── services/ # scx_lavd, tailscale, pipewire, openssh, ...
│ ├── stylix/ # kanagawa scheme, Papirus icons, capitaine cursor,
│ │ JetBrainsMono Nerd Font, synthwave wallpaper
│ ├── system/ # boot, cleanup-root, env, fonts, journald,
│ │ networking, nix, nixpkgs (overlays), preservation,
│ │ streaming-mode, sysctl, systemd, time, xdg, zram
│ └── users/ # Root + codebam (immutable, fish, Yubikey SSH keys)
├── desktop/
│ ├── disko.nix # GPT → LUKS → btrfs subvolumes
│ └── configuration/ # cleanupRoot, Viewport, AMDGPU, nftables
│ VPN-bypass, services (Lidarr/Prowlarr/Transmission/
│ Navidrome/Ollama/OpenRGB/nginx/SearXNG/FlareSolverr),
│ audio routing (media ducker, DeepFilterNet), SOPS
│ secrets, GPU OC, makano user, nix-serve (tailnet cache),
│ daily flake update + kernel/firmware reboot notice
├── desktop-laptop/ # Shared: Podman, IVPN, OBS Studio
├── laptop/
│ └── configuration/ # cleanupRoot (bcachefs), power-profiles-daemon, thermald
├── steamdeck/
│ └── configuration/ # cleanupRoot (btrfs), Jovian, Decky, Steam, RetroArch,
│ gaming/extest, lsfg-vk, Moonlight, distributed builds
├── home/ # Shared home-manager for codebam
│ ├── home.nix # Packages, env vars, custom scripts
│ ├── programs.nix # fish, git, gh, tmux, starship, fastfetch, gpg, wlogout,
│ │ helix, firefox, mpv (Anime4K), mangohud, browsers,
│ │ terminals (ghostty, rio)
│ ├── agents.nix # opencode/opencode2/pi providers, MCP servers, AGENTS.md
│ ├── opensandbox.nix # rootless-podman OpenSandbox server, osb/MCP wrappers
│ ├── opensandbox-work.nix # pinned per-work-type sandbox images + osb-work helper
│ ├── qwen-image.nix # Qwen-Image-2.1 CLI over stable-diffusion.cpp (desktop)
│ ├── services.nix # swayidle, wl-clip-persist, gpg-agent, tmux user unit
│ ├── shell-common.nix # bash, carapace, zoxide, direnv, nushell, tmux, fzf
│ ├── stylix.nix # Per-user theming targets
│ ├── sway.nix # Sway config (keybindings, outputs, inputs, gaps, bars)
│ ├── terminal.nix # defaultTerminal option
│ ├── viewport.nix # Viewport bootstrap config
│ ├── voxtype.nix # Offline dictation daemon and transcript processing
│ ├── waybar.nix # Base Waybar bar config
│ └── xdg.nix # MIME apps
├── pkgs/ # Local derivations (also a standalone flake)
│ ├── default.nix # The one definition of every local derivation
│ ├── flake.nix # `nix run github:codebam/nixos?dir=pkgs#<name>`
│ ├── unfree.nix # Unfree names the hosts and package flake allow
│ ├── agent-overview.nix # tmux agent dashboard
│ ├── dsh.nix # DeepSeek Harness CLI
│ ├── dsh-tool-nu.nix # dsh's Nushell (`nu`) tool plugin
│ ├── opencode-cli.nix # @opencode/cli v2 (opencode2)
│ ├── opencode-desktop-beta.nix
│ ├── opensandbox-{sdk,cli,mcp}.nix # OpenSandbox Python SDK, osb CLI, MCP server
│ ├── pinentry-auto.nix # terminal-aware pinentry
│ ├── polariumcode/ # Polarium Code desktop app (AppImage wrapper)
│ ├── ripwire.nix # C++ codebase-map CLI + MCP server
│ ├── sigmashake-desktop.nix
│ ├── ssg.nix # sigmashake CLI
│ ├── voxtype-plainify.nix # transcript filter (a sed program, not a package)
│ └── zvec-grep.nix # hybrid workspace search + MCP server
├── scripts/ # manual desktop tools, see scripts/README.md
├── secrets/ # SOPS-encrypted secrets (Yubikey + age)
└── .sops.yaml # SOPS key configuration
Every derivation in pkgs/ is defined once, in pkgs/default.nix, and exposed
two ways, so one can be installed without adopting a host. Prefer the
?dir=pkgs flake: its only input is nixpkgs, so it does not pull this
repo's disko/lanzaboote/chaotic/... inputs for a single tool.
nix run github:codebam/nixos?dir=pkgs#ripwire
nix build github:codebam/nixos?dir=pkgs#dsh
nix profile install github:codebam/nixos?dir=pkgs#zvec-grepFor a NixOS config or another flake, consume the overlay instead:
{
inputs.packages.url = "github:codebam/nixos?dir=pkgs";
# ...
nixpkgs.overlays = [ inputs.packages.overlays.default ]; # then pkgs.ripwire, ...
}The parent flake exposes the same set (packages.<system>.<name> and
overlays.default), so nix run github:codebam/nixos#ripwire works too — it
just evaluates the whole input graph. Unfree names are allowed through
pkgs/unfree.nix, shared with the hosts, so ssg, sigmashake-desktop and
polariumcode resolve without extra config. voxtype-plainify is not here: it
is a sed program, not a derivation.
| Input | Source |
|---|---|
nixpkgs |
nixos-unstable |
chaotic |
chaotic-cx/nyx (nyxpkgs-unstable; vendors prebuilt Jovian) |
home-manager |
nix-community/home-manager |
disko |
nix-community/disko |
lanzaboote |
nix-community/lanzaboote (Secure Boot) |
rust-overlay |
oxalica/rust-overlay |
sops-nix |
Mic92/sops-nix |
stylix |
danth/stylix |
preservation |
nix-community/preservation |
nix-index-database |
nix-community/nix-index-database |
lsfg-vk-flake |
pabloaul/lsfg-vk-flake (Steam Deck) |
voxtype |
peteonrails/voxtype v0.7.5 |
sops-pass |
codebam/sops-pass |
viewport |
codebam/viewport (compositor; default servoshell backend) |
Every bare-metal host wipes / to a fresh subvolume on every boot using
cleanup-root, a custom stage-1 systemd service supporting both btrfs and
bcachefs. Old roots archived under old_roots/ for 30 days. A noCleanup boot
specialisation preserves the current root for troubleshooting.
mutableUsers = false. codebam (uid 1000, all hosts) and makano (uid 1001,
desktop) with declarative passwords, groups, SSH keys, and shell.
Nix implementation replaced by Lix, bringing nixpkgs-review, nix-eval-jobs,
nix-fast-build, and colmena.
- Media ducker: LSP sidechain compressor ducks media when game audio detected
- Game listen: Direct-to-DAC loopback for low-latency game audio
- DeepFilterNet: AI noise cancellation for microphone input
- Low-latency quantum (256/512), configurable sample rates (44.1k–96k)
- Media: Lidarr, Prowlarr, Transmission, Navidrome behind nginx + ACME
- Search: SearXNG on 127.0.0.1:8081 (JSON API for agents)
- Cloudflare: FlareSolverr container (loopback) for interstitial solving
- Local AI: Ollama (loopback, AMD ROCm override)
- Networking: Tailscale, IVPN, NetworkManager/iwd, systemd-resolved (DoT)
- Gaming: Steam (extest, Gamescope, Proton CachyOS); Steam firewall holes closed
- GPU: OpenRGB
- Monitoring: SMART disk monitoring
- Cache:
nix-serve-ngon the desktop serves the tailnet (laptop/Deck pull locally-built paths instead of compiling them)
- Viewport (Smithay rewrite): primary Wayland compositor,
servoshell(Servo) backend - Sway (
sway_git): fallback under UWSM - Waybar: transparent status bar with system stats, MPRIS controls, GPU telemetry
- Swaylock, Wlogout: screen lock and session management
- ArRPC + Mprisence: Discord Rich Presence
- Shells: nushell (default login shell), atuin, fish, bash, tmux, starship, zoxide, direnv, fzf
- Editors: Helix (git, nixd LSP), vim → hx alias
- Browsers: Firefox, Google Chrome, Ungoogled Chromium
- Terminals: Ghostty, Rio
- Agents: OpenCode (v1 stable + v2
opencode2), OpenCode Desktop, Pi, and dsh, plus Hermes (CLI, gateway, and Desktop) on the desktop. The host harnesses keep their zvec-grep/ripwire/OpenSandbox/Playwright MCP rows and are trusted host tools; Hermes' terminal and file tools are the exception (its OpenSandbox backend, under Sandboxes below); dsh's OpenSandbox world (the web profile) also registers the shared memory MCP and the isolated, headless Playwright MCP (its file access is scoped to the session workspace by Playwright's default guardrail, and it runs on the host network). Hermes and dsh additionally register the agentic-inbox email MCP, a host-side stdio bridge that authenticates with the localwrangler logintoken (runnpx wrangler loginonce from that checkout); both harnesses carry a standing instruction that outbound mail is sent only after an explicit authorization for that exact message. - Sandboxes: rootless-podman OpenSandbox service plus pinned per-work-type
images (
osb-work list: nix, python, web, bun, rust, c-cpp, dotnet, lua, steel, shell, browser, code). opencode2's shell tool executes throughopencode-sandbox-shell(home/opencode-sandbox-shell.nix): one container per workspace root, the workspace and a read-only/nix/storebind-mounted at their host paths, no credentials;OPENCODE2_NO_SANDBOX=1in the launching environment restores the host shell for that session. Hermes' terminal and file tools execute through the registeredopensandboxterminal backend (home/hermes-opensandbox.nix) with home/opensandbox-exec.nix as the per-command executor: the same container shape, one per workspace root. A session directory that cannot be a workspace -- the home directory contains~/.gnupg-- falls back to the host shell with a notice in its first command's output;HERMES_OPENSANDBOX_FALLBACK=errorrefuses instead, andHERMES_NO_SANDBOX=1restores the host shell for that process. The dsh plugin is installed per profile with dsh's own plugin manager (dsh plugin --profile web add @codebam/dsh-opensandbox). The default world is the untrusted-agent tier: project plus/nix/storeread-only, no credentials or host Nix daemon, a mount-fencedctx.fs, and no host-side MCP bridges other than the isolated, headless Playwright browser row and the Wrangler-authenticated agentic-inbox email bridge. Both run as the host user and read their host state (browser profile, Wrangler token) outside the sandbox. A human scopes one directory with/directory-addin a session, or launchesdsh-host-accessfor reviewed host builds/commits/pushes.dsh-no-opensandboxis the unhardened built-in bwrap/Landlock fallback for a session when the local OpenSandbox server is unhealthy. The sandbox network is created without podman's per-network DNS server (--disable-dnsin home/opensandbox.nix):aardvark-dnsis a single process per network with no self-healing, and when it dies, or is left behind in a torn-down rootless netns, every sandbox silently loses name resolution while egress keeps working (podman-container-tools/podman#20396). Without it the containers get the host's resolvers through pasta --169.254.1.1(systemd-resolved) first -- so the host's DoT policy and the tailnet search domain still apply. Egress filtering is the known gap: sandbox traffic still reaches the LAN/tailnet as the host user, and rootless podman's network mode makes a host-nft source rule unreliable; the intended follow-up is OpenSandboxnetwork_policywith thebridgenetwork. - Dev: gh, git (signed commits), claude-code
- Images:
qwen-imagerenders a prompt with Qwen-Image-2.1 through stable-diffusion.cpp (HIP on the desktop card) into the current directory;qwen-image-modelsdownloads and verifies the ~10 GiB of GGUF weights into~/.local/share/qwen-imagefirst (also done on demand byqwen-image). The desktop-only package pin and the model flags live inhome/qwen-image.nix, the version pin inpkgs/default.nix. - Media: mpv (Anime4K upscaling), OBS Studio (VAAPI)
- Gaming: MangoHud, Prism Launcher (Deck), Moonlight (Deck)
- AppArmor enabled, polkit (local/active wheel passwordless), no sudo
- OpenSSH: key-only, no root, kbd-interactive off, tailnet-only (
openFirewall = false) - GPG agent with Yubikeys (graphical pinentry)
- SOPS secrets via age + Yubikeys
- Secure Boot via lanzaboote
# Development shell with Nix tooling
nix develop
# Format all Nix files
nix fmt
# Check all configurations (builds every host)
nix flake check
# Build a single local package
nix build .#ripwire
# Rebuild
nh os switch