Development workstation
Flat, explicit NixOS modules with locked inputs. Required setup belongs here—not in manual package installs, copied dotfiles or an extra framework.
Select the host, not the login name: nixosConfigurations.nixos is this physical UEFI/NVMe laptop; nixosConfigurations.dev is EC2 only. Both use the dev account. Never activate the EC2 closure on the laptop.
Layout
| File | Owns |
|---|---|
flake.nix, flake.lock |
Host entry points, exact Nixpkgs/Home Manager/tool/dotfile revisions and hashes |
common.nix |
Shared environment, locale/timezone, Nix features, update policy and compatibility version |
physical.nix, hardware-configuration.nix |
Laptop boot/storage, panel scale, CPU sensor and checkout/target identity |
configuration.nix |
AWS boot/storage/network/recovery integration and checkout/target identity |
users.nix |
dev, SSH authorization, sudo, Home Manager and workspace ownership |
tools.nix, network.nix |
Development toolkit, terminal/shell, rootless Podman, VPN/proxy clients, local Tor service and network diagnostics |
colors.nix, wallpaper.nix, wallpaper.svg |
Shared One Ring palette, hash-pinned wallpaper and original fallback artwork |
desktop.nix, hyprland.lua, anyrun.css, swaync.css, desktop-help.py, desktop-actions.py |
Session/bar, launchers, described help, capture, clipboard, notifications, lock/idle and styling |
apps.nix, element-nightly.nix |
Firefox ESR, Tor Browser, KeePassXC, Thunderbird, Steam, pinned Element Nightly, Slack, Tauon, file/media viewers and MIME defaults |
neovim.nix, neovim-test.lua |
Unmodified upstream editor deployment and opt-in native runtime audit |
updates.nix, update-system.sh, update-test.py |
Shared dev-owned checkouts, daily boot-staged updates and failure/concurrency regression tests |
switch-system.sh, switch-test.py |
Same installed manual apply/preview command on both hosts, with host identity supplied by Nix |
physical-test.nix, tools-test.nix, git-credentials-test.nix |
Physical/AWS safety, shared-policy assertions, offline tool/help smoke tests and disposable Git credential-cache checks |
desktop-test.nix, desktop-test.py, audit-desktop.sh |
Disposable graphical/PAM/audio/scaling audit |
workstation.nix, greeter-theme.nix, appearance-test.nix, nvidia.nix |
Local hardware/SDDM integration, One Ring login theme and isolated rendering/font check; separate opt-in NVIDIA support |
| DESKTOP.md | Live audit, wallpaper provenance, JaKooLit comparison and explicit feature-completion plan |
agent-vm/, templates/agent/, AGENT-VM.md |
Reusable rootless DSH microVM, shared project toolchain, live RW cwd/config/credentials/skills and access/networking guide |
Account and session
- Daily account: dev, UID 1001,
/home/dev, workspace~/projects, Zsh. devhas full passwordless sudo, not a rebuild-only privilege. Nix daemon trusted-user access is not separately granted.- The physical host uses the locally established
devpassword; mutable users preserve it. No plaintext password, private key or password hash is embedded in this repo/store. - At the user's request, Plasma and the previously managed
kbotaccount are removed. This configuration does not delete/home/kbot; retained files and old generations are not erased. Home Manager only managesdev. - SDDM exposes Hyprland (uwsm-managed) only. Plain Hyprland is deliberately hidden because it does not start the session-bound bar, wallpaper, idle and polkit services. UWSM is the single session owner.
- The login greeter uses SDDM Astronaut, customized to the One Ring wallpaper, charcoal/parchment/gold palette, JetBrains Mono, a minimal left-hand form and matching cursor. This is a Qt6 login theme, not Plasma. PAM, passwords, session selection and the bootloader are unchanged; host autologin remains disabled.
- Both checkouts are dev-owned, including Git metadata:
/etc/nixon the laptop and/etc/nixoson EC2. Existing paths are retained to avoid moving deployed repositories. NixOS maintains ownership without following symlinks into the Nix store; the laptop's original/etc/nixosfiles stay untouched. - On EC2, AWS boot/storage/networking and SSM/SSH recovery remain intact; no physical greeter, Wi-Fi/Bluetooth or battery services are added. User settings, locale/timezone, packages and update policy come from the same modules. The graphical VM now uses the same SDDM/UWSM integration as the laptop.
- The SSH key in
dev-authorized-keysis public. Account/cloud/browser/VPN credentials and vaults remain user-controlled.
Everyday controls
| Key / command | Action |
|---|---|
Super-H, bar ? |
Search described shortcuts from the live compositor; selecting a row does not execute it |
Super-Enter |
Kitty |
Super-D |
Anyrun applications/calculator |
Super-E, Super-Ctrl-E, y |
Thunar / Yazi; y includes shell-directory integration |
Super-B, Super-Alt-P |
Firefox / KeePassXC |
Ctrl-Alt-L, Super-Escape |
Lock |
Super-Q, Super-Shift-F, Super-Space |
Close / fullscreen / floating |
Super-1…0, Super-Shift-1…0 |
Focus workspace / move and follow; Ctrl moves silently |
Super-U, Super-Shift-U |
Scratch workspace / move window to it |
Super-Shift-Enter |
Drop-down terminal |
Super-Shift-E, Ctrl-Alt-P |
Actions / confirmed session-power menu |
Super-Shift-N, Super-Ctrl-N |
Notification history / DND |
Super-Alt-V |
Clipboard history |
Print, Super-Shift-S |
Screenshot menu / region annotation |
Super-Alt-R |
Start/stop recording; red REC indicator when active |
Super-N, Super-Alt-E |
Night light / emoji |
Super-A, Alt-Tab |
Window search / cycle windows |
Ctrl-R, Ctrl-T, Alt-C in Zsh |
fzf history / files / directories |
z, zi |
zoxide directory navigation |
direnv allow |
Explicitly approve a project's .envrc; no automatic trust whitelist |
tmux, zellij, lazygit |
Persistent terminal workspaces and Git UI; not auto-started or forced |
JetBrains Mono Nerd Font is the shared system default for monospace, sans-serif and serif requests, GTK/Qt application UI, bar, launchers, notifications, dialogs and lock/login screens. The family is declared once in tools.nix; UI sizes remain 11pt where appropriate and Kitty stays 12pt. Noto fonts preserve emoji and international-character coverage. App/site-specific embedded fonts and Tor Browser's privacy defaults are not forcibly overridden; the Linux text console still uses its bitmap font.
Kitty uses an opaque charcoal background, 12pt JetBrains Mono and compact padding. Font zoom remains Ctrl-Shift-+ / Ctrl-Shift--; reset with Ctrl-Shift-Backspace. Semantic ANSI colors remain distinguishable from the gold UI accent. Neovim's own theme is unchanged.
The informative top bar includes workspaces/window context, clock, CPU/RAM/host temperature, media, notification count, privacy and laptop status. Click its right-hand status area for audio, microphone, brightness, network/Bluetooth, idle inhibition and power-profile controls. Actions and Health open the searchable action palette and real system/update diagnostics. Notification history is a separate compact drawer, not another settings dashboard.
See DESKTOP.md for the screenshot-led audit, functional coverage, wallpaper provenance and remaining hardware/account checks. JaKooLit informed the general shortcuts and workflow coverage—not the visual design.
Tool coverage
tools.nix is organized by purpose, not a single unexplained package dump:
- Native/debug: GCC, Make, pkg-config, CMake, Ninja, Meson, ccache, clang tools, GDB, LLDB, Valgrind, Heaptrack, rr, ELF utilities and bpftrace.
- Rust: rustc/Cargo/rustfmt/Clippy/rust-analyzer, nextest, cargo-audit/deny/expand/edit.
- Go: Go, gopls, Delve, golangci-lint.
- Python: Python, uv, Ruff, Pyright; existing Lua/LuaRocks prerequisites stay for Neovim.
- JS/TS: Node, pnpm, TypeScript, Biome, Bun, Deno.
- Additional ecosystems: OpenJDK 25, Maven, Gradle 9, Kotlin, .NET SDK 10, Ruby/Bundler, PHP/Composer, Zig/ZLS and Elixir/Erlang.
- Project/CI: just, watchexec, hyperfine, tokei, ShellCheck, shfmt, yamllint, actionlint, pre-commit, dprint, StyLua, Taplo, Marksman, markdownlint, SQLFluff, Hadolint, ast-grep and ripgrep-all.
- Source control: Git/LFS, gh, glab, LazyGit, delta, difftastic, Jujutsu, git-absorb/filter-repo. No guessed Git identity or account authentication.
- Nix: direnv/nix-direnv, nix-output-monitor, nvd, nix-tree/diff, nixd, statix, deadnix, nixpkgs-review and the repo formatter.
- Containers/cloud: rootless Podman, Compose, Buildah, Skopeo, Dive, AWS CLI, kubectl, Helm, k9s, kubectx, Stern, Kustomize, OpenTofu, Ansible. No Docker daemon, docker-group privilege, deployed infrastructure or automatic image downloads.
- Data/API: SQLite, DuckDB, pgcli/litecli, Redis tools, Miller, csvlens, jq/yq/jless, xh, grpcurl, websocat, Protobuf/Buf, Hurl and oha. Installing these does not start database servers or load-test an endpoint.
- Security/backup: age, sops, GnuPG, Gitleaks, Trivy, Cosign, Syft, Grype, step, mkcert, restic, rclone, rsync, Mosh and SSHFS. No keys, trusted CA, backup destination, schedules or scan targets are created.
- Diagnostics: btop, procs, lnav, sysstat, iotop, dust/duf/ncdu, strace/lsof, NVMe/SMART/USB/PCI/sensor tools.
- Media/documents: FFmpeg, ImageMagick, ExifTool, MediaInfo, Poppler utilities, Pandoc, yt-dlp, Chafa, Asciinema, VHS and archive/compression tools.
- Networking (
network.nix): WireGuard/OpenVPN, NetworkManager VPN integration on the laptop, mtr, iperf3, nmap (including ncat/nping), tcpdump/tshark, doggo/dig, ldns/drill, fping, iftop/bandwhich, traceroute, whois, ethtool, netcat, socat, torsocks and proxychains-ng. Tor runs as a local client service (below). No tunnels, peers, credentials, extra capture privileges or opened firewall ports.
These are system-owned executables from Nix, not unmanaged npm -g, pip install --user or cargo install bootstraps. Project dependencies may still be downloaded by their ordinary package managers. nix develop / .envrc remain appropriate for project-specific versions; this is not a promise that every language project uses the same global toolchain.
Git credentials: terminal, not a GUI
Git HTTPS authentication uses terminal username/token prompts and Git's native
in-memory cache with a 365-day timeout (31536000 seconds). Inherited helpers
are reset, and Git/SSH graphical askpass fallback is disabled for normal terminal
Git invocations. No credential-manager GUI or plaintext credential-store is used.
Credentials are scoped to the repository path as well as the host.
This is cache retention, not a new token expiry: rebooting, stopping the cache daemon or rejecting a credential clears it, and the provider can expire/revoke a token sooner. A successful re-approval refreshes its cache timeout. Choose a one-year token expiry at your Git provider if it supports it; no real credentials or provider settings are changed by this configuration. SSH keys/agents and KeePassXC's storage for other applications remain unchanged. IDEs or repositories that explicitly override Git helpers/askpass can override these user defaults.
To forget all cached Git HTTPS credentials immediately:
git credential-cache exit
Per-project DeepSeek Harness
AGENT-VM.md documents the project template and nix run .#agent.
The official dsh@latest runs as root in a rootless microVM, with the current
project and your standard DSH home/shared skills mounted read-write. Its
host Web listener defaults to localhost on the first free port in 3080–3100;
RAM, vCPUs, IPs and optional TAP networking are configured through Nix modules. The VM is headless by default and
includes Playwright CLI + matching Firefox, with a writable, once-seeded
playwright-firefox skill for isolated named sessions across subagents.
dsh-context is the only added third-party plugin: installed into the shared
Web profile on first startup, retained on restarts and updated explicitly. No host
service is activated.
System DNS
network.nix enables systemd-resolved.service on both hosts at boot, with automatic restart after five seconds and no retry limit. NetworkManager supplies per-link DNS on the laptop; EC2 retains dhcpcd. NixOS connects /etc/resolv.conf to resolved's stub and provides the D-Bus service used by resolvectl. Upstream DNS still comes from DHCP/VPN configuration, not hard-coded public or private servers. An explicit systemctl stop systemd-resolved still stops it normally.
Use systemctl status systemd-resolved and resolvectl status to inspect it. sudo resolvectl dns krishna-laptop 192.168.1.19 sets DNS on that existing interface at runtime; it does not persist across interface recreation or reboot. Put persistent VPN DNS and any routing domains in the VPN/NetworkManager profile.
Local Tor client
On both hosts, network.nix installs Tor from the system pin and enables tor.service at boot. It runs as the dedicated tor user with the NixOS module's sandbox and private persistent state in /var/lib/tor. Systemd restarts an exited daemon after five seconds without a retry limit; an explicit systemctl stop tor still stops it normally. A running process does not guarantee network connectivity—check for Bootstrapped 100% in the journal.
The SOCKS listener is 127.0.0.1:9050 only, with destination and SOCKS-auth isolation. There is no relay/exit, onion service, control listener, transparent proxy, system DNS change or global proxy environment. Applications must opt in; installing Tor does not anonymize the whole machine. Prefer native SOCKS5 support with proxy-side hostname resolution (socks5h), for example:
systemctl status tor.service
journalctl -u tor.service -b --no-pager
curl --fail --show-error --max-time 60 --proxy socks5h://127.0.0.1:9050 \
https://check.torproject.org/api/ip
# For compatible dynamically linked applications, explicitly wrap one command:
torsocks curl --fail --show-error --max-time 60 https://check.torproject.org/api/ip
torsocks and proxychains4 are opt-in wrappers, not sandboxes: static binaries and applications that bypass their hooks are not reliably covered. Tor carries TCP, not arbitrary UDP/ICMP; do not assume tools such as fping, raw-packet nmap or traceroute run through it. Keep ordinary DNS lookups out of workflows that require Tor-side resolution. Tor Browser is installed by apps.nix: launch tor-browser or select Tor Browser in the application launcher after applying the configuration. It retains its separate profile and upstream privacy defaults; Firefox remains the default browser. Tor Browser is not simply a generic browser pointed at the system SOCKS port, and it is not auto-started.
Build and apply
One-command sync on either host
Save your work, then run the installed command from any directory:
switch-system # explicitly apply now and make it the boot default
switch-system dry-activate # build and preview changes without applying
switch-system boot # stage for the next boot instead
Nix supplies the correct checkout/target to the same script on each host. Builds run as dev; only activation uses sudo. It applies the exact built output, including dev's Home Manager configuration, shares the automatic updater's lock, stops on build failure, keeps recovery generations and never reboots. It does not pull Git or update flake.lock: “latest” means the current checkout. Add new source files to Git for flakes to include them. Open a new terminal afterward for shell environment changes; some desktop changes require a fresh login. --help lists the modes, including temporary test activation.
Direct /etc/nix/switch-system.sh execution still defaults to the laptop. When running the source script on EC2 before the packaged command is installed, explicitly set NIXOS_CONFIG_REPO=/etc/nixos NIXOS_UPDATE_HOST=dev and choose boot. Automatic updates, unlike an explicit manual switch, always stage for the next boot on both hosts.
Detailed validation and activation
On the physical laptop, from an administrator shell:
cd /etc/nix
nixfmt --check ./*.nix
nix flake check --no-build --no-update-lock-file
nix build .#checks.x86_64-linux.updates \
.#checks.x86_64-linux.desktop-config \
.#checks.x86_64-linux.physical-config \
.#checks.x86_64-linux.tools .#checks.x86_64-linux.desktop-actions \
.#checks.x86_64-linux.switch-system --no-update-lock-file --no-link
nix build .#nixosConfigurations.nixos.config.system.build.toplevel \
--no-update-lock-file --out-link result-nixos
built=$(readlink -f result-nixos)
sudo "$built/sw/bin/nixos-rebuild" dry-activate --no-reexec --store-path "$built"
New source files must be added to Git for flakes to include them. A build or dry activation does not activate the result. Review the dry activation; removing Plasma and kbot is intentional, removing dev, NetworkManager or the installed mounts is not.
Save work before activating. A desktop/display-manager change can end a graphical session. Use Ctrl-Alt-F3, log in as dev, and retain that console:
built=$(readlink -f /etc/nix/result-nixos)
sudo "$built/sw/bin/nixos-rebuild" test --no-reexec --store-path "$built"
# After testing login/session, persist exactly that closure:
sudo "$built/sw/bin/nixos-rebuild" switch --no-reexec --store-path "$built"
test is a real activation, not a dry run. For a non-disruptive deployment that takes effect on next boot, use boot instead of test/switch. Both hosts' daily updaters use that policy.
On EC2 only, use /etc/nixos and .#nixosConfigurations.dev.config.system.build.toplevel. Run the same evaluation/checks and activate the exact output with its own nixos-rebuild --no-reexec --store-path. Never select a target merely because it matches your username.
Recovery
Keep existing generations. Inspect what is actually selected and booted:
readlink -f /run/current-system /run/booted-system /nix/var/nix/profiles/system
sudo nix-env --profile /nix/var/nix/profiles/system --list-generations
To restore the booted closure temporarily from the retained console:
previous=$(readlink -f /run/booted-system)
sudo "$previous/sw/bin/nixos-rebuild" test --no-reexec --store-path "$previous"
If boot fails, choose a known-good systemd-boot generation (hold Space at startup). Do not blindly select “generation 1”: generation numbers are machine/history-specific. System rollback does not restore mutable user data, Git changes, application databases or backups.
Automatic freshness, precisely
- Every day,
nixos-update.timerruns with up to one hour of jitter and catches missed runs. It advancesnixpkgs,home-manager, andnixpkgs-latest; the Neovim source remains fixed. - The system/desktop now follow rolling
nixos-unstable(the latest tested channel) and Home Managermaster, rather than a fixed release branch. Most standalone CLI tools use Nixpkgs master for faster updates. They are imported separately, not overlaid onto the desktop's GCC/Python/libraries. Flake locks record each resolved snapshot; input updates, not deleting locks, advance Nix-owned packages. - Same policy on both hosts: run as
dev, build/check/record and stage for next boot. There is no automatic live switch, logout or reboot. Only checkout path and explicit flake target differ: laptop/etc/nix#nixos, EC2/etc/nixos#dev. Installed versions change when that generation is booted, or explicitly switched by the user. - Both skip dirty/detached repositories, serialize runs with manual switching, use an isolated worktree, check for edits again after dry activation and commit only the tested lock. On staging failure, the updater restores the previously selected boot generation, including a generation that was already staged but not running. A failed rollback is reported as failure; recovery generations are retained.
- “Current” means newest successfully checked/built versions packaged in those branch heads, not a guarantee of every upstream release immediately. Master can contain breakage and uncached builds; failure retains the previous working generation. Upstream Pi, Mason/plugin downloads, browser add-ons, firmware and project dependencies are separate update boundaries. No runtime self-updater is bolted on to override Nix-owned executables.
- The updater does not fetch/merge repository code from origin. Configuration code is reviewed separately. A dirty working tree intentionally prevents automatic input changes until work is committed/stashed.
Inspect or trigger:
systemctl list-timers nixos-update.timer
journalctl -u nixos-update
sudo systemctl start nixos-update.service
sudo less /var/cache/nixos-update/last-success
Manual refresh: nix flake update nixpkgs home-manager nixpkgs-latest, review flake.lock, then check/build. system.stateVersion / home.stateVersion remain 26.05: they govern compatibility, not package freshness. Channels are disabled; the Nixpkgs registry and <nixpkgs> lookup follow the locked system input.
Editor and application data
Tauon is installed for dev from nixpkgs-latest (Nixpkgs master), with the
exact snapshot recorded in flake.lock. The current pin provides 12.0.0,
matching the latest upstream stable release when added. It advances through the
existing daily input-update workflow. Launch tauon or choose Tauon
in the application launcher after applying; existing MIME defaults are unchanged.
Neovim imports the existing neovim-dots source, pinned to 380eb86778a7c53a0f1c18e84f14037456155347. Lua, AstroNvim, Lazy, Mason, plugins and keymaps are unchanged. No Nixvim, replacement loader or plugin-manager migration.
Home Manager links configuration files under ~/.config/nvim. Lazy's writable lock lives under ~/.local/state/nvim/locks/<revision>.json, seeded once per dotfile revision. An earlier manual checkout is preserved at ~/projects/neovim-dots-before-nix; activation refuses to overwrite an existing backup. The host input is reproducible; Lazy/Mason's mutable runtime downloads are not claimed to be fully Nix-reproducible.
As dev, the separate runtime audit may download dependencies but does not modify managed Lua or save buffers:
nvim --headless ~/.config/nvim/init.lua -c 'luafile /etc/nix/neovim-test.lua'
KeePassXC preferences are seeded once and stay writable. Create/open your own vault, pair the browser extension and choose a dedicated Secret Service Integration group for application tokens. Element Nightly is forced to libsecret storage; do not select its unencrypted fallback. The native audit confirmed that Nightly starts, but login needs an unlocked/configured vault. No vault, account or VPN credentials are embedded.
element-nightly.nix packages the official Element Nightly 2026090401 Debian artifact by URL and SHA-256, preserving its matched Electron/native modules. It uses Chromium's user-namespace sandbox rather than the unusable setuid helper; --no-sandbox is not used. Its Nightly profile is separate from stable Element; existing account data is not copied or deleted. The launcher is Element-Nightly, command element-desktop-nightly; element-desktop is a compatibility alias.
Nightly update boundary: this upstream binary is not packaged by the pinned Nixpkgs inputs. Its URL/version/hash must be refreshed in element-nightly.nix from the official packages.element.io package index, then rebuilt. The daily flake-input updater does not silently mutate this source file. This intentionally keeps the setup simple and reproducible rather than adding another downloader/self-updater.
Validation and contributions
./audit-desktop.sh runs the disposable graphical VM; ./audit-desktop.sh /dev/dri/renderD128 optionally uses an accessible render node. Logs/screenshots go to ~/.cache/desktop-audit/run.*. The VM tests session services, fonts, audio, scaling and real PAM with test-only credentials. The native live screenshot audit covers this laptop; neither substitutes for real hardware suspend, hotplug or browser portal tests.
Plain nix flake check also builds/runs the VM and may be slow without KVM. Use --no-build for evaluation, then select bounded checks explicitly. Keep changes focused, format/evaluate/test them, and commit reviewed configuration. Do not push to an unapproved remote, invent a human Git identity, commit secrets, screenshots of personal windows, build outputs or agent scratch notes.