diff --git a/CLAUDE.md b/CLAUDE.md index a6ba779..67d23e9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,13 +1,15 @@ # dotfiles-nixos One flake that builds every machine the user owns. -The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overlays) lives in `.claude/CONTEXT.md`; the current deliverable's spec is `.claude/spec/laptop-mvi.md`. +The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overlays) lives in `.claude/CONTEXT.md`. +The current deliverable's spec is `.claude/spec/laptop-mvi.md`. ## Conventions - Comments posted to Gitea (pull requests, issues, reviews) go out under the operator's account, so sign every one to make clear the author is the agent, not the operator. End the comment with a `— Claude` sign-off. - (A dedicated bot account may replace this later; until then, the sign-off is the only marker.) + (A dedicated bot account may replace this later. + Until then, the sign-off is the only marker.) - Commit messages follow Conventional Commits, specified in `docs/conventional-commits.md`. Scope is the module or host the change belongs to (`fish`, `nvim`, `neogaia`), omitted for repo-wide changes. Keep messages free of Gitea-specific references: this repository is mirrored to GitHub, where issue and pull-request numbers resolve to unrelated things. @@ -21,7 +23,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla Flakes and the chaotic substituter come from this flake's own `nix.settings`, so no `NIX_CONFIG` export or per-command `--extra-experimental-features` is needed, and building a toplevel with `boot.kernelPackages = linuxPackages_cachyos` fetches the kernel from `nyx-cache` rather than compiling it. Both were true only while the machine still ran CachyOS against a distro Nix daemon. - The substituters a `nix build` fetches from are the **daemon's** (`/etc/nix/nix.conf`), *not* the `nix.settings` of the config being built — those only govern the built system. - The two coincide here because the dev host runs this flake; they diverge on any machine that does not. + The two coincide here because the dev host runs this flake. + They diverge on any machine that does not. - Git identity is declared in the flake by `modules/git.nix`, which writes `alexion ` — the identity all history uses — on any host enabling `modules.git`. Every new host has to enable it, so that a host reads as a full checklist of what it carries. It is deployed on `neogaia` and verified: a commit in a repository outside this checkout is authored `alexion ` with no override. @@ -32,12 +35,15 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla With both global files present it prints only `~/.gitconfig`, which reads as proof that `~/.config/git/config` is being ignored entirely. It is not: drop `--global` and both files appear, each key resolving to the last file that sets it. A `git config --global ` write also lands in `~/.gitconfig`, the file that outranks the flake-managed one. -- The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.` (the system toplevel); cheap targeted checks use `nix eval .#nixosConfigurations..config...`. +- The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.` (the system toplevel). + Cheap targeted checks use `nix eval .#nixosConfigurations..config...`. - A flake only sees **git-tracked** files, so a new file that has not been `git add`ed is invisible to evaluation even though it exists on disk. The failure names the path and reads as if the file were missing: `error: Path 'secrets/shared.yaml' does not exist in Git repository`. - Staging is enough; the file need not be committed. + Staging is enough. + The file need not be committed. - chaotic-nyx must **not** follow our `nixpkgs`, and its packages are built against chaotic's own pinned nixpkgs (its overlay defaults to `onTopOf = "flake-nixpkgs"`, the cache-friendly path). - That is what lets the `nyx-cache.chaotic.cx` binary cache hit instead of compiling the CachyOS kernel from source; the tradeoff is that chaotic packages do not see our `unstable`/`stable` overlays. + That is what lets the `nyx-cache.chaotic.cx` binary cache hit instead of compiling the CachyOS kernel from source. + The tradeoff is that chaotic packages do not see our `unstable`/`stable` overlays. - The remote is self-hosted Gitea (`git.alexion.dev`), and the forge CLI is `gitea-axi` rather than `tea`. `gitea-axi` resolves the repository from the `origin` remote and discovers credentials from a `tea` login whose host matches the remote, so both are implicit inside a checkout. It is installed on `neogaia` by `modules.agents.tools.gitea-axi`, and verified: `gitea-axi` run from this checkout renders the `alexion/dotfiles` dashboard authenticated, so the claude-code `SessionStart` hook that runs it now resolves to a real binary rather than a missing one. @@ -54,7 +60,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla On this machine the two are provably distinct: the daemon presents `SHA256:2ysuBX0+Z6GbdCTujz5JHX6rqnJzIyWhYNrxdhhGwEM`, while pushes to `git.alexion.dev` authenticate with `SHA256:nEhHwtHDnLlsuFxyfp+cETgHUZ8xDMxaPVmYM5vuCkA`. Renaming host keys after user keys, or vice versa, is therefore always wrong. - `~/.claude/skills` is generated by home-manager with `recursive = true`, so the directories are real and writable but every leaf file is a read-only symlink into the store. - Editing a skill in place fails; its source is `modules/agents/claude-code/skills//` here, applied by a rebuild. + Editing a skill in place fails. + Its source is `modules/agents/claude-code/skills//` here, applied by a rebuild. Creating a new file under `~/.claude/skills/` succeeds silently and is the trap — it stays outside the repo and reaches no other machine. Copying out of that tree needs `cp -rL` plus `chmod -R u+w`: a plain `cp -r` copies the symlinks, putting store paths into the destination, and dereferenced files keep the store's read-only mode. - `home-manager.users..home.file` is keyed by whatever path string the **defining module wrote**, absolute or relative, not by one canonical form. @@ -62,9 +69,14 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla The other form fails with "does not provide attribute", so overriding an entry (e.g. setting `.force = true` on it) requires matching the writer's exact key. List the real keys with `nix eval --json .#nixosConfigurations..config.home-manager.users..home.file --apply builtins.attrNames` rather than guessing one. A key's `.source` is the input file, whose store path differs from the deployed symlink's target (home-manager copies it to a `hm_`-prefixed path) even though the contents match. -- nixpkgs `vimPlugins.nord-nvim` is `shaunsingh/nord.nvim` (no `require("nord").setup()`); the config wants `gbprod/nord.nvim`, which is packaged as `vimPlugins.gbprod-nord`. -- nixpkgs `vimPlugins.nvim-treesitter` tracks the rewritten `main` branch: there is no `require("nvim-treesitter.configs").setup{ensure_installed,highlight,indent}`. Under nixvim, use `plugins.treesitter` with `highlight.enable`/`indent.enable` and `grammarPackages = with config.programs.nixvim.plugins.treesitter.package.builtGrammars; [ ... ]` — the module's own `package.builtGrammars`, **not** `pkgs.vimPlugins.nvim-treesitter.*` (whose query files can mismatch). The module targets the main branch and enables features via neovim-native APIs (`vim.treesitter.start()`, `require'nvim-treesitter'.indentexpr()`). -- Neovim is configured via **nixvim** (flake input `nixvim`, consumed as `inputs.nixvim.homeModules.nixvim` added to `home-manager.sharedModules`, config under `home-manager.users..programs.nixvim`). `nixvim.inputs.nixpkgs.follows = "nixpkgs"` is set; nixvim then emits a benign eval warning that its pinned nixpkgs differs from the followed one — builds and runs fine, do not "fix" it by dropping the follows. +- nixpkgs `vimPlugins.nord-nvim` is `shaunsingh/nord.nvim` (no `require("nord").setup()`). + The config wants `gbprod/nord.nvim`, which is packaged as `vimPlugins.gbprod-nord`. +- nixpkgs `vimPlugins.nvim-treesitter` tracks the rewritten `main` branch: there is no `require("nvim-treesitter.configs").setup{ensure_installed,highlight,indent}`. + Under nixvim, use `plugins.treesitter` with `highlight.enable`/`indent.enable` and `grammarPackages = with config.programs.nixvim.plugins.treesitter.package.builtGrammars; [ ... ]` — the module's own `package.builtGrammars`, **not** `pkgs.vimPlugins.nvim-treesitter.*` (whose query files can mismatch). + The module targets the main branch and enables features via neovim-native APIs (`vim.treesitter.start()`, `require'nvim-treesitter'.indentexpr()`). +- Neovim is configured via **nixvim** (flake input `nixvim`, consumed as `inputs.nixvim.homeModules.nixvim` added to `home-manager.sharedModules`, config under `home-manager.users..programs.nixvim`). + `nixvim.inputs.nixpkgs.follows = "nixpkgs"` is set. + nixvim then emits a benign eval warning that its pinned nixpkgs differs from the followed one — builds and runs fine, do not "fix" it by dropping the follows. - To reference the nixvim-built package's own attrs (e.g. treesitter `builtGrammars`) inside our NixOS module, give `home-manager.users.` the module-function form (`hm: { programs.nixvim = { ... hm.config.programs.nixvim... }; }`), since the outer `config` is the NixOS config, not the home-manager one. - The agent's Bash sandbox blocks `sudo` and swallows it into a bare exit 1 with **no stderr**, which looks identical to the command itself failing. Re-run with the sandbox disabled to see the real error (`sudo: a password is required`) before diagnosing anything else. @@ -72,7 +84,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla - Sudo's credential cache is keyed per user rather than per terminal (`timestamp_type=global`, 60-minute window, declared by the claude-code module), so an authentication made in one terminal counts for commands the agent runs. Warming it with `sudo -v` through the agent's own shell — including the `!` prefix — never works: that shell has no controlling terminal, and sudo reports `a terminal is required to read the password`. It has to be a separate terminal. - A `PreToolUse` hook refuses privileged commands while the cache is cold, so a cold cache announces itself instead of stalling; a failure *without* that message is the sandbox, not the cache. + A `PreToolUse` hook refuses privileged commands while the cache is cold, so a cold cache announces itself instead of stalling. + A failure *without* that message is the sandbox, not the cache. - An `mkOption` of a list or attribute-set type is **not** mandatory the way a scalar one is. Those types carry an `emptyValue`, so an option declared with no `default` and never set evaluates to `[ ]` or `{ }` instead of failing with "option used but not defined". A declaration that is genuinely required cannot be expressed by omitting the default — it needs an assertion, or a default chosen so that the silent case is the safe one. @@ -80,7 +93,9 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla - `home-manager.users.` cannot be assigned twice at the same level in one module: `home-manager.users.${user}.home.packages` alongside `home-manager.users.${user}.programs.x` fails with `error: dynamic attribute 'alexion' already defined`. The interpolated key makes it a dynamic attribute, which nix will not merge the way it merges static paths. Nest both under a single `home-manager.users.${user} = { ... }`. -- **Verifying a nixvim change headless:** `programs.nixvim.build.package`'s wrapper has **no `-u`**, so running `$OUT/bin/nvim` loads the caller's `~/.config/nvim` (the dev host's real config), *not* the built config — silently. To exercise the built config, launch with `-u "$(nix build --no-link --print-out-paths .#…programs.nixvim.build.initFile)"` and a scratch `HOME`/`XDG_CONFIG_HOME`. `conceallevel` is window-local: set it with `opt_local`/`vim.wo`, never `vim.bo[buf]` (which errors). +- **Verifying a nixvim change headless:** `programs.nixvim.build.package`'s wrapper has **no `-u`**, so running `$OUT/bin/nvim` loads the caller's `~/.config/nvim` (the dev host's real config), *not* the built config — silently. + To exercise the built config, launch with `-u "$(nix build --no-link --print-out-paths .#…programs.nixvim.build.initFile)"` and a scratch `HOME`/`XDG_CONFIG_HOME`. + `conceallevel` is window-local: set it with `opt_local`/`vim.wo`, never `vim.bo[buf]` (which errors). - Host GPUs: `neogaia` is Intel and `zeus` (the desktop) is **AMD**. `raichu`, a server with no desktop, is the only Nvidia machine. `laptop-mvi.md`'s out-of-scope line calls zeus Nvidia, but that is stale and the document is kept historical and unchanged, so do not infer any host's GPU from it. @@ -110,7 +125,9 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla Engines are referenced by their current id, which the module maps from the old display names, so the default is `default = "ddg"`, not `"DuckDuckGo"` (the latter only warns and migrates). Prove the result by decoding the built file: `mozlz4a -d ` shows the `_metaData.hidden` flags and `defaultEngineId`. - `home.sessionVariables` do **not** reach the Hyprland session, so anything the compositor reads from its environment (cursor theme, toolkit hints) has to be declared elsewhere. - home-manager writes those variables to `hm-session-vars.sh`, which only a login shell sources; UWSM starts the graphical session without it, so the compositor's environment never gains them. + home-manager writes those variables to `hm-session-vars.sh`, which only a login shell sources. + UWSM starts the graphical session without it, so the compositor's environment never gains them. This is why a Stylix cursor (`XCURSOR_THEME`/`XCURSOR_SIZE` via `home.pointerCursor`) silently fails to apply and Hyprland draws its built-in cursor: the variables exist in `sessionVariables` but not in the running session (`tr '\0' '\n' < /proc/$(pgrep -x Hyprland)/environ` shows them absent). - The fix is Hyprland's own `env = KEY,VALUE` directive in `settings`, read at compositor startup regardless of the shell profile; `modules/desktop/hyprland/hyprland.nix` sets the cursor that way, sourced from `config.stylix.cursor`. + The fix is Hyprland's own `env = KEY,VALUE` directive in `settings`, read at compositor startup regardless of the shell profile. + `modules/desktop/hyprland/hyprland.nix` sets the cursor that way, sourced from `config.stylix.cursor`. Bibata ships XCursor format only (no `hyprcursor/` dir), which Hyprland renders through its XCursor fallback, so both `XCURSOR_*` and `HYPRCURSOR_*` naming the same theme are safe. diff --git a/docs/install.md b/docs/install.md index d514029..5c411d9 100644 --- a/docs/install.md +++ b/docs/install.md @@ -103,7 +103,8 @@ $ cd dotfiles Do **not** point `disko-install` straight at the Gitea flake URL. Gitea serves HTTPS with a self-signed certificate and expects authentication, and Nix's flake fetcher has no easy way to skip certificate verification or supply those credentials mid-install. A plain `git clone` sidesteps that entirely — over SSH there is no TLS, and over HTTPS git takes the `sslVerify=false` above that the flake fetcher won't — and then `disko-install` consumes the flake from a local path, where no fetch of our repo happens during the build. -(Every other flake input is public and still fetched from GitHub over ordinary, valid TLS; only our own repo is the problem the local clone solves.) +(Every other flake input is public and still fetched from GitHub over ordinary, valid TLS. +Only our own repo is the problem the local clone solves.) ### 3. Generate the host identity @@ -201,17 +202,20 @@ $ sudo nix --extra-experimental-features 'nix-command flakes' run \ What each part does: - `--flake .#neogaia` installs the `neogaia` `Host` from the local clone. -- `--disk main /dev/nvme0n1` maps disko's `main` disk to the NVMe device; it matches the device declared in `hosts/neogaia/disk.nix` and is stated explicitly so there is no doubt about the target. +- `--disk main /dev/nvme0n1` maps disko's `main` disk to the NVMe device. + It matches the device declared in `hosts/neogaia/disk.nix` and is stated explicitly so there is no doubt about the target. - `--write-efi-boot-entries` writes the systemd-boot entry into this machine's NVRAM, because the disk stays in the machine it was installed from. - The two `--option` lines are the important part: they hand the **chaotic binary cache** to the install-time Nix daemon on the live ISO. The chaotic substituter must be passed here explicitly. -The `nix.settings` in the flake configure the substituters of the *installed* system, not the live ISO's daemon that runs this build; the ISO's daemon has no `substituters` beyond `cache.nixos.org`. +The `nix.settings` in the flake configure the substituters of the *installed* system, not the live ISO's daemon that runs this build. +The ISO's daemon has no `substituters` beyond `cache.nixos.org`. Without these two `--option` flags, the build cannot fetch the prebuilt CachyOS kernel and **compiles `linuxPackages_cachyos` (and its toolchain) from source on the USB stick** — a very long detour that the cache avoids. Because the install runs as root, and root is a trusted Nix user, the daemon honours these client-supplied substituter settings. Partway through, disko formats the LUKS container and **prompts for a disk-encryption passphrase**. -This is the passphrase you will type at every boot to unlock the disk; choose it deliberately. +This is the passphrase you will type at every boot to unlock the disk. +Choose it deliberately. When it finishes it prints `disko-install succeeded`. `disko-install` unmounts the target filesystem on exit, so nothing is mounted at this point — step 6 remounts it. diff --git a/flake.nix b/flake.nix index b79cb19..68d7157 100644 --- a/flake.nix +++ b/flake.nix @@ -22,13 +22,15 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - # Declarative disk partitioning; each host declares its own layout. + # Declarative disk partitioning. + # Each host declares its own layout. disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; - # Upstream per-machine hardware profiles; each host imports its own. + # Upstream per-machine hardware profiles. + # Each host imports its own. nixos-hardware = { url = "github:NixOS/nixos-hardware"; inputs.nixpkgs.follows = "nixpkgs"; @@ -47,22 +49,20 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - # Agent-ergonomic CLI for Gitea, with its home-manager module wiring in the - # Claude Code context where that harness is present. + # Agent-ergonomic CLI for Gitea, with a home-manager module for the agent context. gitea-axi = { url = "git+https://git.alexion.dev/alexion/gitea-axi"; inputs.nixpkgs.follows = "nixpkgs"; }; - # Personal agent skills, packaged as per-skill derivations with a - # home-manager module that places them under Claude Code's skills directory. + # Personal agent skills, packaged as per-skill derivations with a home-manager module. skills = { url = "git+https://git.alexion.dev/alexion/skills"; inputs.nixpkgs.follows = "nixpkgs"; }; - # CachyOS kernel and binary cache. Pins its own nixpkgs so its cache stays - # usable and the kernel is fetched from it. + # CachyOS kernel and binary cache. + # Pins its own nixpkgs so its cache stays usable and the kernel is fetched from it. chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; }; diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index f132ad6..22953b0 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -5,7 +5,8 @@ ... }: # neogaia — Dell XPS 13 9380 laptop. -# Disk layout is in ./disk.nix; `fileSystems` are derived from it, none declared here. +# Disk layout is in ./disk.nix. +# `fileSystems` are derived from it, none declared here. { imports = [ inputs.nixos-hardware.nixosModules.dell-xps-13-9380 @@ -22,10 +23,10 @@ boot.kernelPackages = pkgs.linuxPackages_cachyos; # Redistributable firmware for the QCA6174 wifi (ath10k blobs). - # Intel microcode updates follow from this; none declared here. + # Intel microcode updates follow from this, so none is declared here. hardware.enableRedistributableFirmware = true; - # RAM-backed swap; no on-disk swap partition. + # RAM-backed swap, no on-disk swap partition. zramSwap.enable = true; # So wifi can be joined from the console. diff --git a/hosts/neogaia/disk.nix b/hosts/neogaia/disk.nix index ecabf4e..db83081 100644 --- a/hosts/neogaia/disk.nix +++ b/hosts/neogaia/disk.nix @@ -1,7 +1,8 @@ { ... }: # neogaia's disk layout for disko: one NVMe disk, GPT, with an EFI system -# partition and a LUKS container holding btrfs subvolumes. No swap partition; -# swap is zram. disko derives `fileSystems` and `boot.initrd.luks.devices` from this. +# partition and a LUKS container holding btrfs subvolumes. +# No swap partition, since swap is zram. +# disko derives `fileSystems` and `boot.initrd.luks.devices` from this. { disko.devices.disk.main = { type = "disk"; @@ -10,8 +11,8 @@ type = "gpt"; partitions = { ESP = { - # Each generation stores a kernel and initrd here and the CachyOS - # kernel is large; an exhausted partition fails bootloader installs. + # Each generation stores a kernel and initrd here and the CachyOS kernel is large. + # An exhausted partition fails bootloader installs. size = "2G"; type = "EF00"; content = { diff --git a/hosts/neogaia/hardware-configuration.nix b/hosts/neogaia/hardware-configuration.nix index cf45488..79395d9 100644 --- a/hosts/neogaia/hardware-configuration.nix +++ b/hosts/neogaia/hardware-configuration.nix @@ -1,6 +1,6 @@ { lib, modulesPath, ... }: # Hardware detected by nixos-generate-config on this machine. -# disko derives `fileSystems` and the LUKS device; none declared here. +# disko derives `fileSystems` and the LUKS device, none declared here. { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; diff --git a/modules/agents/claude-code/claude-code.nix b/modules/agents/claude-code/claude-code.nix index a550b7c..d2bef0f 100644 --- a/modules/agents/claude-code/claude-code.nix +++ b/modules/agents/claude-code/claude-code.nix @@ -5,8 +5,8 @@ ... }: # Claude Code for the primary user, configured through home-manager, which ships -# the package and manages ~/.claude. Login credentials are left unmanaged so they -# survive rebuilds. +# the package and manages ~/.claude. +# Login credentials are left unmanaged so they survive rebuilds. let cfg = config.modules.agents.claude-code; user = config.user.name; @@ -22,10 +22,8 @@ in cached credential while it lasts. Suitable for a single-user machine''; config = lib.mkIf cfg.enable { - # Keying sudo's credential cache per user rather than per terminal lets one - # authentication cover commands issued by processes holding no terminal of - # their own. Any process running as this user can spend that credential - # until it lapses, so this suits a single-user machine. + # Key the credential cache per user rather than per terminal, so one + # authentication covers the agent's terminal-less commands. security.sudo.extraConfig = '' Defaults timestamp_type=global Defaults timestamp_timeout=60 @@ -38,7 +36,7 @@ in programs.claude-code = { enable = true; - # Global agent instructions, rendered to ~/.claude/CLAUDE.md. + # The global agent-instructions file. context = ./CLAUDE.md; # One directory per skill, symlinked under ~/.claude/skills. diff --git a/modules/agents/claude-code/hooks/agent-sudo-guard.sh b/modules/agents/claude-code/hooks/agent-sudo-guard.sh index 29ba861..e8c9f7a 100755 --- a/modules/agents/claude-code/hooks/agent-sudo-guard.sh +++ b/modules/agents/claude-code/hooks/agent-sudo-guard.sh @@ -1,10 +1,10 @@ #!/bin/sh -# agent-sudo-guard.sh — refuse a privileged command while sudo's credential -# cache is cold, naming the command that warms it. +# Refuse a privileged command while sudo's credential cache is cold, naming the +# command that warms it. # # Commands arrive here from subprocesses holding no terminal, so an uncached -# sudo fails with a bare non-zero exit and no output, reading as an unexplained -# stall. The probe below reads a cache keyed per user rather than per terminal, +# sudo fails with a bare non-zero exit and no output, reading as an unexplained stall. +# The probe below reads a cache keyed per user rather than per terminal, # so an authentication made in the operator's own terminal counts. input=$(cat) diff --git a/modules/agents/skills.nix b/modules/agents/skills.nix index d45472f..ad50170 100644 --- a/modules/agents/skills.nix +++ b/modules/agents/skills.nix @@ -1,13 +1,6 @@ { config, inputs, ... }: -# Global agent skills from the skills flake, placed under the agent harness's -# skills directory so they are active in every project. The flake's home-manager -# module self-gates on the harness being enabled and installs nothing for an -# empty selection, so a host without one carries no skills either way. -# -# Unlike every other module, this one declares no `enable` flag and wires -# unconditionally, by design. -# The flake's self-gating above already makes it inert where the harness is -# absent, so a gate would guard nothing. +# Global agent skills, placed under the skills directory so they are active in +# every project. let user = config.user.name; diff --git a/modules/agents/tools/gitea-axi.nix b/modules/agents/tools/gitea-axi.nix index db28a12..a0ffeca 100644 --- a/modules/agents/tools/gitea-axi.nix +++ b/modules/agents/tools/gitea-axi.nix @@ -5,8 +5,6 @@ ... }: # gitea-axi for the primary user, installed through its own home-manager module. -# That module also declares the Claude Code context when that harness is -# enabled on the host; enabling this alone installs the CLI and nothing else. let cfg = config.modules.agents.tools.gitea-axi; user = config.user.name; diff --git a/modules/desktop/firefox.nix b/modules/desktop/firefox.nix index ed9e5b6..89d8c9c 100644 --- a/modules/desktop/firefox.nix +++ b/modules/desktop/firefox.nix @@ -35,8 +35,7 @@ in Snippets = false; }; - # An ad and content blocker, the operator's password manager, and a - # video sponsor-skipper. All three are self-contained web extensions. + # An ad blocker, a password manager, and a video sponsor-skipper. ExtensionSettings = { "uBlock0@raymondhill.net" = forceInstalled "ublock-origin"; "78272b6fa58f4a1abaac99321d503a20@proton.me" = forceInstalled "proton-pass"; @@ -65,16 +64,15 @@ in }; settings = { - # Scale the UI and page by a fixed factor. Left at auto (-1), - # Firefox reads the panel's 1.5x and inflates its whole chrome on - # this display while point-sized apps stay put; a shade under that - # brings it into line without dropping to true 1:1, which reads too - # small at this DPI. + # Scale the UI and page by a fixed factor. + # Left at auto (-1), Firefox reads the panel's 1.5x and inflates its + # whole chrome while point-sized apps stay put. + # A shade under that brings it into line without dropping to true + # 1:1, which reads too small at this DPI. "layout.css.devPixelsPerPx" = "1.25"; - # Auto-enable the sideloaded Firefox Color add-on that carries the - # Nord chrome theme, which Firefox otherwise leaves disabled. - # The functional extensions come through policy and are unaffected. + # Auto-enable the sideloaded Firefox Color add-on carrying the Nord + # chrome theme, which Firefox otherwise leaves disabled. "extensions.autoDisableScopes" = 0; # Sponsored surfaces the policies above do not reach. @@ -103,9 +101,8 @@ in }; }; - # Nord chrome from the shared Stylix scheme, against the one profile. - # colorTheme recolours the toolbar and tabs, which the target does not do - # on its own, through the Stylix-managed Firefox Color add-on. + # Nord chrome for the one profile, applied through the Stylix-managed + # Firefox Color add-on that colorTheme enables. stylix.targets.firefox = { enable = true; profileNames = [ "default" ]; diff --git a/modules/desktop/hyprland/hypridle.nix b/modules/desktop/hyprland/hypridle.nix index 4fdc76a..61acc9c 100644 --- a/modules/desktop/hyprland/hypridle.nix +++ b/modules/desktop/hyprland/hypridle.nix @@ -4,8 +4,7 @@ pkgs, ... }: -# Idle management: hypridle locks on idle, powers the displays off, and locks -# before every suspend, so an unattended session always lands at hyprlock. +# hypridle: idle-triggered locking and display power management. let cfg = config.modules.desktop.hyprland.hypridle; user = config.user.name; diff --git a/modules/desktop/hyprland/hyprland.nix b/modules/desktop/hyprland/hyprland.nix index d056e4f..f616c8d 100644 --- a/modules/desktop/hyprland/hyprland.nix +++ b/modules/desktop/hyprland/hyprland.nix @@ -4,7 +4,7 @@ pkgs, ... }: -# The Hyprland compositor, sourced from nixpkgs. +# The Hyprland compositor. let cfg = config.modules.desktop.hyprland; user = config.user.name; @@ -63,12 +63,13 @@ in "$mod" = "SUPER"; "$terminal" = "alacritty"; - # Hand the cursor theme to the compositor directly. UWSM launches the - # session without the shell profile that carries the pointer-cursor - # variables, so without this Hyprland never sees a theme and falls back - # to its built-in cursor. Bibata ships XCursor only; the hyprcursor - # variables name the same theme, which Hyprland resolves through its - # XCursor fallback. + # Hand the cursor theme to the compositor directly. + # UWSM launches the session without the shell profile that carries the + # pointer-cursor variables, so without this Hyprland never sees a theme + # and falls back to its built-in cursor. + # Bibata ships XCursor only. + # The hyprcursor variables name the same theme, which Hyprland resolves + # through its XCursor fallback. env = lib.optionals (cursor != null) [ "XCURSOR_THEME,${cursor.name}" "XCURSOR_SIZE,${toString cursor.size}" @@ -81,7 +82,7 @@ in # Caps is a second Escape. # Shift+Caps still toggles a real CapsLock. kb_options = "caps:escape_shifted_capslock"; - # Snappy: a short delay before repeat begins, then a fast repeat rate. + # Snappy key repeat. repeat_delay = 250; repeat_rate = 45; accel_profile = "flat"; @@ -156,8 +157,9 @@ in ++ workspaceBinds; # Volume and brightness keys repeat while held, each raising a popup - # through the OSD client. Volume is capped at 100 percent; the client - # floors brightness so a full hold cannot black the screen out. + # through the OSD client. + # Volume is capped at 100 percent. + # The client floors brightness so a full hold cannot black the screen out. binde = [ ", XF86AudioRaiseVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100" ", XF86AudioLowerVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume lower" diff --git a/modules/desktop/hyprland/hyprlock.nix b/modules/desktop/hyprland/hyprlock.nix index 03ba4e9..45fd047 100644 --- a/modules/desktop/hyprland/hyprlock.nix +++ b/modules/desktop/hyprland/hyprlock.nix @@ -4,13 +4,12 @@ pkgs, ... }: -# The lock screen: hyprlock, a session-lock client whose surface the compositor owns, so it survives a crash of the locker rather than exposing the session. +# The lock screen: a hyprlock session-lock surface the compositor owns. let cfg = config.modules.desktop.hyprland.hyprlock; user = config.user.name; - # The hyprlock this module installs, so the keybind and the idle daemon lock - # with one package and never split versions. + # The hyprlock this module installs, used by the lock keybind below. hyprlock = "${config.home-manager.users.${user}.programs.hyprlock.package}/bin/hyprlock"; in { @@ -30,7 +29,8 @@ in disable_loading_bar = true; }; - # A centered password field; its colors are the Stylix target's. + # A centered password field. + # Its colors come from the Stylix hyprlock target. input-field = { size = "260, 52"; rounding = 8; diff --git a/modules/desktop/osd.nix b/modules/desktop/osd.nix index a794c72..bf97bc7 100644 --- a/modules/desktop/osd.nix +++ b/modules/desktop/osd.nix @@ -16,14 +16,13 @@ in environment.systemPackages = [ pkgs.swayosd ]; # The udev rule chgrps each backlight's brightness node to `video` and adds - # group write, so the server dims the panel without root. Membership below - # grants the running session that access. + # group write, so the server dims the panel without root. + # Membership below grants the running session that access. services.udev.packages = [ pkgs.swayosd ]; users.users.${user}.extraGroups = [ "video" ]; # The server draws the popups, so it runs for the whole graphical session. - # Bound to the target uwsm activates, like the bar, rather than an - # exec-once in the compositor config. + # It binds to the target uwsm activates. home-manager.users.${user}.systemd.user.services.swayosd = { Unit = { Description = "SwayOSD on-screen display server"; diff --git a/modules/desktop/portals.nix b/modules/desktop/portals.nix index c7466f4..a2124a6 100644 --- a/modules/desktop/portals.nix +++ b/modules/desktop/portals.nix @@ -10,8 +10,9 @@ in config = lib.mkIf cfg.enable { # The backend packages arrive with the compositor, so only the routing is set # here. - # GTK is the default backend, and the three compositor-native requests go to - # Hyprland. + # Screen sharing, screenshots, and global shortcuts need the compositor's + # own backend. + # Everything else uses GTK. xdg.portal.config.common = { default = [ "gtk" ]; "org.freedesktop.impl.portal.ScreenCast" = [ "hyprland" ]; diff --git a/modules/desktop/screenshot.nix b/modules/desktop/screenshot.nix index e996919..8d6a346 100644 --- a/modules/desktop/screenshot.nix +++ b/modules/desktop/screenshot.nix @@ -15,9 +15,8 @@ let wl-copy = "${pkgs.wl-clipboard}/bin/wl-copy"; xdgUserDir = "${pkgs.xdg-user-dirs}/bin/xdg-user-dir"; - # satty is the annotation step, and its copy action is set to save as well, - # so a single keystroke through it lands the shot in both the clipboard and a - # file. + # satty's copy action also saves, so one keystroke lands the shot in both the + # clipboard and a file. capture = target: pkgs.writeShellScript "screenshot-${target}" '' @@ -46,8 +45,8 @@ in ]; # Print with plain/Shift/Ctrl for region/window/full. - # Super+L, the spec's chosen key, is already the hjkl focus and movement - # bind, so screenshots take the Print key instead. + # Super+L is already the hjkl focus and movement bind, so screenshots take + # the Print key instead. wayland.windowManager.hyprland.settings.bind = [ ", Print, exec, ${capture "area"}" "SHIFT, Print, exec, ${capture "active"}" diff --git a/modules/desktop/terminal.nix b/modules/desktop/terminal.nix index e5ca254..58074bc 100644 --- a/modules/desktop/terminal.nix +++ b/modules/desktop/terminal.nix @@ -4,8 +4,7 @@ pkgs, ... }: -# Alacritty as the desktop terminal, a lightweight GPU renderer that opens -# fast on the laptop's integrated graphics. +# Alacritty as the desktop terminal. let cfg = config.modules.desktop.terminal; user = config.user.name; diff --git a/modules/desktop/userdirs.nix b/modules/desktop/userdirs.nix index 89f4aee..6d10a8b 100644 --- a/modules/desktop/userdirs.nix +++ b/modules/desktop/userdirs.nix @@ -9,8 +9,7 @@ in options.modules.desktop.userdirs.enable = lib.mkEnableOption "XDG user directories"; config = lib.mkIf cfg.enable { - # enable writes ~/.config/user-dirs.dirs from the option defaults, which - # xdg-user-dir then reads. + # Write ~/.config/user-dirs.dirs from the option defaults. home-manager.users.${user}.xdg.userDirs.enable = true; }; } diff --git a/modules/fish/fish.nix b/modules/fish/fish.nix index 706c232..0f84506 100644 --- a/modules/fish/fish.nix +++ b/modules/fish/fish.nix @@ -84,9 +84,9 @@ in }; functions = { - # Pick the logo that fits the terminal so the greeting never wraps: - # the full logo needs about 118 columns beside the info block, the - # compact one about 89, and the info block alone about 71. + # Pick the logo that fits the terminal so the greeting never wraps. + # The full logo needs about 118 columns beside the info block, the + # compact one about 89, the info block alone about 71. fish_greeting = '' if test "$COLUMNS" -ge 118 fastfetch @@ -97,10 +97,9 @@ in end ''; - # vi insert mode omits the emacs ctrl-f and ctrl-r bindings, so - # restore both. + # vi insert mode omits the emacs ctrl-f and ctrl-r bindings, so restore both. # ctrl-f accepts an autosuggestion and ctrl-r opens the history pager. - # Only insert mode is bound: vi normal mode keeps ctrl-r as redo. + # vi normal mode keeps its ctrl-r redo, since only insert mode is bound. fish_user_key_bindings = '' bind -M insert ctrl-f forward-char bind -M insert ctrl-r history-pager diff --git a/modules/nvim/config.lua b/modules/nvim/config.lua index ed161bd..af5fd01 100644 --- a/modules/nvim/config.lua +++ b/modules/nvim/config.lua @@ -1,13 +1,12 @@ --- Imperative configuration that has no typed nixvim option: the colorscheme --- call and two autocmds. Everything expressible as Nix lives in ./nvim.nix. +-- Imperative configuration that has no typed nixvim option: the colorscheme call and autocmds. --- gbprod/nord.nvim, provided as an extra plugin from nixpkgs. +-- gbprod/nord.nvim. require("nord").setup({ transparent = true, }) vim.cmd.colorscheme("nord") --- Conceal markdown syntax in markdown buffers (previously an after/ftplugin). +-- Conceal markdown syntax in markdown buffers. -- conceallevel is window-local, so it is set with opt_local when the filetype -- is applied to the buffer's window. vim.api.nvim_create_autocmd("FileType", { diff --git a/modules/nvim/nvim.nix b/modules/nvim/nvim.nix index dc560e1..6649ad9 100644 --- a/modules/nvim/nvim.nix +++ b/modules/nvim/nvim.nix @@ -5,8 +5,7 @@ inputs, ... }: -# Neovim for the primary user, configured declaratively through nixvim. The -# imperative remainder (colorscheme, Neogit blame autocmd) lives in ./config.lua. +# Neovim for the primary user, configured declaratively through nixvim. let cfg = config.modules.nvim; user = config.user.name; @@ -176,8 +175,8 @@ in }; }; - # gbprod/nord.nvim (nixvim's colorschemes.nord is a different plugin); - # set up in ./config.lua. + # gbprod/nord.nvim, set up in ./config.lua. + # nixvim's colorschemes.nord is a different plugin. extraPlugins = [ pkgs.vimPlugins.gbprod-nord ]; extraConfigLua = builtins.readFile ./config.lua; diff --git a/modules/tmux/extra.conf b/modules/tmux/extra.conf index a2efb6b..6931ec2 100644 --- a/modules/tmux/extra.conf +++ b/modules/tmux/extra.conf @@ -6,8 +6,8 @@ bind -T copy-mode-vi v send -X begin-selection bind -T copy-mode-vi y send -X copy-selection-and-cancel bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel -# \ splits the pane side-by-side, - splits it stacked; both open the new pane -# in the current pane's directory, as does c for a new window. +# \ splits the pane side-by-side, - splits it stacked. +# Both open the new pane in the current pane's directory, as does c for a new window. unbind % unbind '"' bind \\ split-window -h -c "#{pane_current_path}" @@ -27,13 +27,14 @@ bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux.conf reload set -ag terminal-overrides ",*:RGB" # monitor-bell flags a background window in the status bar when it rings the -# terminal bell while unfocused. bell-action=none suppresses the actual BEL -# (no beep or flash); the per-window status highlight is independent of it. +# terminal bell while unfocused. +# bell-action=none suppresses the actual BEL (no beep or flash). +# The per-window status highlight is independent of it. setw -g monitor-bell on set -g bell-action none # Minimal status bar: session name plus window list only. -# Colours are left to Stylix, which themes the status and pane styles. +# Colours are left to Stylix. set -g status-position bottom set -g status-left " #S " set -g status-left-length 20 diff --git a/modules/tmux/tmux.nix b/modules/tmux/tmux.nix index 2a2aa38..c049ac1 100644 --- a/modules/tmux/tmux.nix +++ b/modules/tmux/tmux.nix @@ -3,8 +3,8 @@ lib, ... }: -# tmux for the primary user, configured through home-manager. Settings without a -# home-manager option are read from ./extra.conf. +# tmux for the primary user, configured through home-manager. +# Settings without a home-manager option are read from ./extra.conf. let cfg = config.modules.tmux; user = config.user.name; diff --git a/system.nix b/system.nix index 9fd38dd..552b239 100644 --- a/system.nix +++ b/system.nix @@ -52,8 +52,8 @@ in "flakes" ]; - # chaotic's binary cache, so the CachyOS kernel is fetched rather than - # compiled. The `extra-` prefix keeps cache.nixos.org alongside it. + # chaotic's binary cache, so the CachyOS kernel is fetched rather than compiled. + # The `extra-` prefix keeps cache.nixos.org alongside it. nix.settings.extra-substituters = [ "https://nyx-cache.chaotic.cx/" ]; nix.settings.extra-trusted-public-keys = [ "nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk=" @@ -80,7 +80,8 @@ in environment.systemPackages = [ pkgs.git ]; - # Caps Lock is a second Escape; Shift+Caps Lock still toggles Caps Lock. + # Caps Lock is a second Escape. + # Shift+Caps Lock still toggles Caps Lock. services.xserver.xkb.layout = "us"; services.xserver.xkb.options = "caps:escape_shifted_capslock"; @@ -108,7 +109,8 @@ in # That is early enough to precede the account that reads it. sops.secrets.${passwordSecret}.neededForUsers = true; - # Primary user, in the wheel group. + # Primary user. + # The wheel group is the way in, since root is locked. users.users.${user.name} = { isNormalUser = true; description = user.description;