style: align comments with the project conventions

Apply a codebase-wide comment audit against the comment conventions:
split banned semicolons and multi-sentence lines into one sentence per
line, cut cross-file and history narration, trim file-top headers to a
single purpose line, and drop verbosity that did not earn its place.
Prose docs (CLAUDE.md, install.md) get the same one-sentence-per-line
and no-semicolon treatment.
This commit is contained in:
2026-07-24 16:17:09 -04:00
parent d2fbf78927
commit 48a81bb8a2
25 changed files with 132 additions and 125 deletions

View File

@@ -1,13 +1,15 @@
# dotfiles-nixos # dotfiles-nixos
One flake that builds every machine the user owns. 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 ## 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. - 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. 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`. - 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. 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. 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. 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. 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 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 <contact@alexion.dev>` — the identity all history uses — on any host enabling `modules.git`. - Git identity is declared in the flake by `modules/git.nix`, which writes `alexion <contact@alexion.dev>` — 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. 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 <contact@alexion.dev>` with no override. It is deployed on `neogaia` and verified: a commit in a repository outside this checkout is authored `alexion <contact@alexion.dev>` 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. 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. It is not: drop `--global` and both files appear, each key resolving to the last file that sets it.
A `git config --global <key> <value>` write also lands in `~/.gitconfig`, the file that outranks the flake-managed one. A `git config --global <key> <value>` 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.<host>` (the system toplevel); cheap targeted checks use `nix eval .#nixosConfigurations.<host>.config...`. - The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.<host>` (the system toplevel).
Cheap targeted checks use `nix eval .#nixosConfigurations.<host>.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. - 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`. 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). - 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`. - 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. `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. 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`. 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. 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. - `~/.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/<name>/` here, applied by a rebuild. Editing a skill in place fails.
Its source is `modules/agents/claude-code/skills/<name>/` 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. 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. 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.<user>.home.file` is keyed by whatever path string the **defining module wrote**, absolute or relative, not by one canonical form. - `home-manager.users.<user>.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. 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.<host>.config.home-manager.users.<user>.home.file --apply builtins.attrNames` rather than guessing one. List the real keys with `nix eval --json .#nixosConfigurations.<host>.config.home-manager.users.<user>.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. 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.nord-nvim` is `shaunsingh/nord.nvim` (no `require("nord").setup()`).
- 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()`). The config wants `gbprod/nord.nvim`, which is packaged as `vimPlugins.gbprod-nord`.
- Neovim is configured via **nixvim** (flake input `nixvim`, consumed as `inputs.nixvim.homeModules.nixvim` added to `home-manager.sharedModules`, config under `home-manager.users.<user>.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.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.<user>.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.<user>` the module-function form (`hm: { programs.nixvim = { ... hm.config.programs.nixvim... }; }`), since the outer `config` is the NixOS config, not the home-manager one. - To reference the nixvim-built package's own attrs (e.g. treesitter `builtGrammars`) inside our NixOS module, give `home-manager.users.<user>` 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. - 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. 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. - 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`. 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. 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. - 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". 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. 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.<user>` 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`. - `home-manager.users.<user>` 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. 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} = { ... }`. 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**. - Host GPUs: `neogaia` is Intel and `zeus` (the desktop) is **AMD**.
`raichu`, a server with no desktop, is the only Nvidia machine. `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. `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). 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 <search.json.mozlz4>` shows the `_metaData.hidden` flags and `defaultEngineId`. Prove the result by decoding the built file: `mozlz4a -d <search.json.mozlz4>` 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.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). 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. 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.

View File

@@ -103,7 +103,8 @@ $ cd dotfiles
Do **not** point `disko-install` straight at the Gitea flake URL. 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. 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. 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 ### 3. Generate the host identity
@@ -201,17 +202,20 @@ $ sudo nix --extra-experimental-features 'nix-command flakes' run \
What each part does: What each part does:
- `--flake .#neogaia` installs the `neogaia` `Host` from the local clone. - `--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. - `--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 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 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. 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. 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**. 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`. 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. `disko-install` unmounts the target filesystem on exit, so nothing is mounted at this point — step 6 remounts it.

View File

@@ -22,13 +22,15 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
# Declarative disk partitioning; each host declares its own layout. # Declarative disk partitioning.
# Each host declares its own layout.
disko = { disko = {
url = "github:nix-community/disko"; url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs"; 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 = { nixos-hardware = {
url = "github:NixOS/nixos-hardware"; url = "github:NixOS/nixos-hardware";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -47,22 +49,20 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
# Agent-ergonomic CLI for Gitea, with its home-manager module wiring in the # Agent-ergonomic CLI for Gitea, with a home-manager module for the agent context.
# Claude Code context where that harness is present.
gitea-axi = { gitea-axi = {
url = "git+https://git.alexion.dev/alexion/gitea-axi"; url = "git+https://git.alexion.dev/alexion/gitea-axi";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
# Personal agent skills, packaged as per-skill derivations with a # Personal agent skills, packaged as per-skill derivations with a home-manager module.
# home-manager module that places them under Claude Code's skills directory.
skills = { skills = {
url = "git+https://git.alexion.dev/alexion/skills"; url = "git+https://git.alexion.dev/alexion/skills";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
# CachyOS kernel and binary cache. Pins its own nixpkgs so its cache stays # CachyOS kernel and binary cache.
# usable and the kernel is fetched from it. # Pins its own nixpkgs so its cache stays usable and the kernel is fetched from it.
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable";
}; };

View File

@@ -5,7 +5,8 @@
... ...
}: }:
# neogaia — Dell XPS 13 9380 laptop. # 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 = [ imports = [
inputs.nixos-hardware.nixosModules.dell-xps-13-9380 inputs.nixos-hardware.nixosModules.dell-xps-13-9380
@@ -22,10 +23,10 @@
boot.kernelPackages = pkgs.linuxPackages_cachyos; boot.kernelPackages = pkgs.linuxPackages_cachyos;
# Redistributable firmware for the QCA6174 wifi (ath10k blobs). # 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; hardware.enableRedistributableFirmware = true;
# RAM-backed swap; no on-disk swap partition. # RAM-backed swap, no on-disk swap partition.
zramSwap.enable = true; zramSwap.enable = true;
# So wifi can be joined from the console. # So wifi can be joined from the console.

View File

@@ -1,7 +1,8 @@
{ ... }: { ... }:
# neogaia's disk layout for disko: one NVMe disk, GPT, with an EFI system # 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; # partition and a LUKS container holding btrfs subvolumes.
# swap is zram. disko derives `fileSystems` and `boot.initrd.luks.devices` from this. # No swap partition, since swap is zram.
# disko derives `fileSystems` and `boot.initrd.luks.devices` from this.
{ {
disko.devices.disk.main = { disko.devices.disk.main = {
type = "disk"; type = "disk";
@@ -10,8 +11,8 @@
type = "gpt"; type = "gpt";
partitions = { partitions = {
ESP = { ESP = {
# Each generation stores a kernel and initrd here and the CachyOS # Each generation stores a kernel and initrd here and the CachyOS kernel is large.
# kernel is large; an exhausted partition fails bootloader installs. # An exhausted partition fails bootloader installs.
size = "2G"; size = "2G";
type = "EF00"; type = "EF00";
content = { content = {

View File

@@ -1,6 +1,6 @@
{ lib, modulesPath, ... }: { lib, modulesPath, ... }:
# Hardware detected by nixos-generate-config on this machine. # 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") ]; imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];

View File

@@ -5,8 +5,8 @@
... ...
}: }:
# Claude Code for the primary user, configured through home-manager, which ships # Claude Code for the primary user, configured through home-manager, which ships
# the package and manages ~/.claude. Login credentials are left unmanaged so they # the package and manages ~/.claude.
# survive rebuilds. # Login credentials are left unmanaged so they survive rebuilds.
let let
cfg = config.modules.agents.claude-code; cfg = config.modules.agents.claude-code;
user = config.user.name; user = config.user.name;
@@ -22,10 +22,8 @@ in
cached credential while it lasts. Suitable for a single-user machine''; cached credential while it lasts. Suitable for a single-user machine'';
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
# Keying sudo's credential cache per user rather than per terminal lets one # Key the credential cache per user rather than per terminal, so one
# authentication cover commands issued by processes holding no terminal of # authentication covers the agent's terminal-less commands.
# their own. Any process running as this user can spend that credential
# until it lapses, so this suits a single-user machine.
security.sudo.extraConfig = '' security.sudo.extraConfig = ''
Defaults timestamp_type=global Defaults timestamp_type=global
Defaults timestamp_timeout=60 Defaults timestamp_timeout=60
@@ -38,7 +36,7 @@ in
programs.claude-code = { programs.claude-code = {
enable = true; enable = true;
# Global agent instructions, rendered to ~/.claude/CLAUDE.md. # The global agent-instructions file.
context = ./CLAUDE.md; context = ./CLAUDE.md;
# One directory per skill, symlinked under ~/.claude/skills. # One directory per skill, symlinked under ~/.claude/skills.

View File

@@ -1,10 +1,10 @@
#!/bin/sh #!/bin/sh
# agent-sudo-guard.sh — refuse a privileged command while sudo's credential # Refuse a privileged command while sudo's credential cache is cold, naming the
# cache is cold, naming the command that warms it. # command that warms it.
# #
# Commands arrive here from subprocesses holding no terminal, so an uncached # 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 # sudo fails with a bare non-zero exit and no output, reading as an unexplained stall.
# stall. The probe below reads a cache keyed per user rather than per terminal, # The probe below reads a cache keyed per user rather than per terminal,
# so an authentication made in the operator's own terminal counts. # so an authentication made in the operator's own terminal counts.
input=$(cat) input=$(cat)

View File

@@ -1,13 +1,6 @@
{ config, inputs, ... }: { config, inputs, ... }:
# Global agent skills from the skills flake, placed under the agent harness's # Global agent skills, placed under the skills directory so they are active in
# skills directory so they are active in every project. The flake's home-manager # every project.
# 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.
let let
user = config.user.name; user = config.user.name;

View File

@@ -5,8 +5,6 @@
... ...
}: }:
# gitea-axi for the primary user, installed through its own home-manager module. # 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 let
cfg = config.modules.agents.tools.gitea-axi; cfg = config.modules.agents.tools.gitea-axi;
user = config.user.name; user = config.user.name;

View File

@@ -35,8 +35,7 @@ in
Snippets = false; Snippets = false;
}; };
# An ad and content blocker, the operator's password manager, and a # An ad blocker, a password manager, and a video sponsor-skipper.
# video sponsor-skipper. All three are self-contained web extensions.
ExtensionSettings = { ExtensionSettings = {
"uBlock0@raymondhill.net" = forceInstalled "ublock-origin"; "uBlock0@raymondhill.net" = forceInstalled "ublock-origin";
"78272b6fa58f4a1abaac99321d503a20@proton.me" = forceInstalled "proton-pass"; "78272b6fa58f4a1abaac99321d503a20@proton.me" = forceInstalled "proton-pass";
@@ -65,16 +64,15 @@ in
}; };
settings = { settings = {
# Scale the UI and page by a fixed factor. Left at auto (-1), # Scale the UI and page by a fixed factor.
# Firefox reads the panel's 1.5x and inflates its whole chrome on # Left at auto (-1), Firefox reads the panel's 1.5x and inflates its
# this display while point-sized apps stay put; a shade under that # whole chrome while point-sized apps stay put.
# brings it into line without dropping to true 1:1, which reads too # A shade under that brings it into line without dropping to true
# small at this DPI. # 1:1, which reads too small at this DPI.
"layout.css.devPixelsPerPx" = "1.25"; "layout.css.devPixelsPerPx" = "1.25";
# Auto-enable the sideloaded Firefox Color add-on that carries the # Auto-enable the sideloaded Firefox Color add-on carrying the Nord
# Nord chrome theme, which Firefox otherwise leaves disabled. # chrome theme, which Firefox otherwise leaves disabled.
# The functional extensions come through policy and are unaffected.
"extensions.autoDisableScopes" = 0; "extensions.autoDisableScopes" = 0;
# Sponsored surfaces the policies above do not reach. # Sponsored surfaces the policies above do not reach.
@@ -103,9 +101,8 @@ in
}; };
}; };
# Nord chrome from the shared Stylix scheme, against the one profile. # Nord chrome for the one profile, applied through the Stylix-managed
# colorTheme recolours the toolbar and tabs, which the target does not do # Firefox Color add-on that colorTheme enables.
# on its own, through the Stylix-managed Firefox Color add-on.
stylix.targets.firefox = { stylix.targets.firefox = {
enable = true; enable = true;
profileNames = [ "default" ]; profileNames = [ "default" ];

View File

@@ -4,8 +4,7 @@
pkgs, pkgs,
... ...
}: }:
# Idle management: hypridle locks on idle, powers the displays off, and locks # hypridle: idle-triggered locking and display power management.
# before every suspend, so an unattended session always lands at hyprlock.
let let
cfg = config.modules.desktop.hyprland.hypridle; cfg = config.modules.desktop.hyprland.hypridle;
user = config.user.name; user = config.user.name;

View File

@@ -4,7 +4,7 @@
pkgs, pkgs,
... ...
}: }:
# The Hyprland compositor, sourced from nixpkgs. # The Hyprland compositor.
let let
cfg = config.modules.desktop.hyprland; cfg = config.modules.desktop.hyprland;
user = config.user.name; user = config.user.name;
@@ -63,12 +63,13 @@ in
"$mod" = "SUPER"; "$mod" = "SUPER";
"$terminal" = "alacritty"; "$terminal" = "alacritty";
# Hand the cursor theme to the compositor directly. UWSM launches the # Hand the cursor theme to the compositor directly.
# session without the shell profile that carries the pointer-cursor # UWSM launches the session without the shell profile that carries the
# variables, so without this Hyprland never sees a theme and falls back # pointer-cursor variables, so without this Hyprland never sees a theme
# to its built-in cursor. Bibata ships XCursor only; the hyprcursor # and falls back to its built-in cursor.
# variables name the same theme, which Hyprland resolves through its # Bibata ships XCursor only.
# XCursor fallback. # The hyprcursor variables name the same theme, which Hyprland resolves
# through its XCursor fallback.
env = lib.optionals (cursor != null) [ env = lib.optionals (cursor != null) [
"XCURSOR_THEME,${cursor.name}" "XCURSOR_THEME,${cursor.name}"
"XCURSOR_SIZE,${toString cursor.size}" "XCURSOR_SIZE,${toString cursor.size}"
@@ -81,7 +82,7 @@ in
# Caps is a second Escape. # Caps is a second Escape.
# Shift+Caps still toggles a real CapsLock. # Shift+Caps still toggles a real CapsLock.
kb_options = "caps:escape_shifted_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_delay = 250;
repeat_rate = 45; repeat_rate = 45;
accel_profile = "flat"; accel_profile = "flat";
@@ -156,8 +157,9 @@ in
++ workspaceBinds; ++ workspaceBinds;
# Volume and brightness keys repeat while held, each raising a popup # Volume and brightness keys repeat while held, each raising a popup
# through the OSD client. Volume is capped at 100 percent; the client # through the OSD client.
# floors brightness so a full hold cannot black the screen out. # Volume is capped at 100 percent.
# The client floors brightness so a full hold cannot black the screen out.
binde = [ binde = [
", XF86AudioRaiseVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100" ", XF86AudioRaiseVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100"
", XF86AudioLowerVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume lower" ", XF86AudioLowerVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume lower"

View File

@@ -4,13 +4,12 @@
pkgs, 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 let
cfg = config.modules.desktop.hyprland.hyprlock; cfg = config.modules.desktop.hyprland.hyprlock;
user = config.user.name; user = config.user.name;
# The hyprlock this module installs, so the keybind and the idle daemon lock # The hyprlock this module installs, used by the lock keybind below.
# with one package and never split versions.
hyprlock = "${config.home-manager.users.${user}.programs.hyprlock.package}/bin/hyprlock"; hyprlock = "${config.home-manager.users.${user}.programs.hyprlock.package}/bin/hyprlock";
in in
{ {
@@ -30,7 +29,8 @@ in
disable_loading_bar = true; 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 = { input-field = {
size = "260, 52"; size = "260, 52";
rounding = 8; rounding = 8;

View File

@@ -16,14 +16,13 @@ in
environment.systemPackages = [ pkgs.swayosd ]; environment.systemPackages = [ pkgs.swayosd ];
# The udev rule chgrps each backlight's brightness node to `video` and adds # 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 # group write, so the server dims the panel without root.
# grants the running session that access. # Membership below grants the running session that access.
services.udev.packages = [ pkgs.swayosd ]; services.udev.packages = [ pkgs.swayosd ];
users.users.${user}.extraGroups = [ "video" ]; users.users.${user}.extraGroups = [ "video" ];
# The server draws the popups, so it runs for the whole graphical session. # 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 # It binds to the target uwsm activates.
# exec-once in the compositor config.
home-manager.users.${user}.systemd.user.services.swayosd = { home-manager.users.${user}.systemd.user.services.swayosd = {
Unit = { Unit = {
Description = "SwayOSD on-screen display server"; Description = "SwayOSD on-screen display server";

View File

@@ -10,8 +10,9 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
# The backend packages arrive with the compositor, so only the routing is set # The backend packages arrive with the compositor, so only the routing is set
# here. # here.
# GTK is the default backend, and the three compositor-native requests go to # Screen sharing, screenshots, and global shortcuts need the compositor's
# Hyprland. # own backend.
# Everything else uses GTK.
xdg.portal.config.common = { xdg.portal.config.common = {
default = [ "gtk" ]; default = [ "gtk" ];
"org.freedesktop.impl.portal.ScreenCast" = [ "hyprland" ]; "org.freedesktop.impl.portal.ScreenCast" = [ "hyprland" ];

View File

@@ -15,9 +15,8 @@ let
wl-copy = "${pkgs.wl-clipboard}/bin/wl-copy"; wl-copy = "${pkgs.wl-clipboard}/bin/wl-copy";
xdgUserDir = "${pkgs.xdg-user-dirs}/bin/xdg-user-dir"; xdgUserDir = "${pkgs.xdg-user-dirs}/bin/xdg-user-dir";
# satty is the annotation step, and its copy action is set to save as well, # satty's copy action also saves, so one keystroke lands the shot in both the
# so a single keystroke through it lands the shot in both the clipboard and a # clipboard and a file.
# file.
capture = capture =
target: target:
pkgs.writeShellScript "screenshot-${target}" '' pkgs.writeShellScript "screenshot-${target}" ''
@@ -46,8 +45,8 @@ in
]; ];
# Print with plain/Shift/Ctrl for region/window/full. # Print with plain/Shift/Ctrl for region/window/full.
# Super+L, the spec's chosen key, is already the hjkl focus and movement # Super+L is already the hjkl focus and movement bind, so screenshots take
# bind, so screenshots take the Print key instead. # the Print key instead.
wayland.windowManager.hyprland.settings.bind = [ wayland.windowManager.hyprland.settings.bind = [
", Print, exec, ${capture "area"}" ", Print, exec, ${capture "area"}"
"SHIFT, Print, exec, ${capture "active"}" "SHIFT, Print, exec, ${capture "active"}"

View File

@@ -4,8 +4,7 @@
pkgs, pkgs,
... ...
}: }:
# Alacritty as the desktop terminal, a lightweight GPU renderer that opens # Alacritty as the desktop terminal.
# fast on the laptop's integrated graphics.
let let
cfg = config.modules.desktop.terminal; cfg = config.modules.desktop.terminal;
user = config.user.name; user = config.user.name;

View File

@@ -9,8 +9,7 @@ in
options.modules.desktop.userdirs.enable = lib.mkEnableOption "XDG user directories"; options.modules.desktop.userdirs.enable = lib.mkEnableOption "XDG user directories";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
# enable writes ~/.config/user-dirs.dirs from the option defaults, which # Write ~/.config/user-dirs.dirs from the option defaults.
# xdg-user-dir then reads.
home-manager.users.${user}.xdg.userDirs.enable = true; home-manager.users.${user}.xdg.userDirs.enable = true;
}; };
} }

View File

@@ -84,9 +84,9 @@ in
}; };
functions = { functions = {
# Pick the logo that fits the terminal so the greeting never wraps: # Pick the logo that fits the terminal so the greeting never wraps.
# the full logo needs about 118 columns beside the info block, the # The full logo needs about 118 columns beside the info block, the
# compact one about 89, and the info block alone about 71. # compact one about 89, the info block alone about 71.
fish_greeting = '' fish_greeting = ''
if test "$COLUMNS" -ge 118 if test "$COLUMNS" -ge 118
fastfetch fastfetch
@@ -97,10 +97,9 @@ in
end end
''; '';
# vi insert mode omits the emacs ctrl-f and ctrl-r bindings, so # vi insert mode omits the emacs ctrl-f and ctrl-r bindings, so restore both.
# restore both.
# ctrl-f accepts an autosuggestion and ctrl-r opens the history pager. # 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 = '' fish_user_key_bindings = ''
bind -M insert ctrl-f forward-char bind -M insert ctrl-f forward-char
bind -M insert ctrl-r history-pager bind -M insert ctrl-r history-pager

View File

@@ -1,13 +1,12 @@
-- Imperative configuration that has no typed nixvim option: the colorscheme -- Imperative configuration that has no typed nixvim option: the colorscheme call and autocmds.
-- call and two autocmds. Everything expressible as Nix lives in ./nvim.nix.
-- gbprod/nord.nvim, provided as an extra plugin from nixpkgs. -- gbprod/nord.nvim.
require("nord").setup({ require("nord").setup({
transparent = true, transparent = true,
}) })
vim.cmd.colorscheme("nord") 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 -- conceallevel is window-local, so it is set with opt_local when the filetype
-- is applied to the buffer's window. -- is applied to the buffer's window.
vim.api.nvim_create_autocmd("FileType", { vim.api.nvim_create_autocmd("FileType", {

View File

@@ -5,8 +5,7 @@
inputs, inputs,
... ...
}: }:
# Neovim for the primary user, configured declaratively through nixvim. The # Neovim for the primary user, configured declaratively through nixvim.
# imperative remainder (colorscheme, Neogit blame autocmd) lives in ./config.lua.
let let
cfg = config.modules.nvim; cfg = config.modules.nvim;
user = config.user.name; user = config.user.name;
@@ -176,8 +175,8 @@ in
}; };
}; };
# gbprod/nord.nvim (nixvim's colorschemes.nord is a different plugin); # gbprod/nord.nvim, set up in ./config.lua.
# set up in ./config.lua. # nixvim's colorschemes.nord is a different plugin.
extraPlugins = [ pkgs.vimPlugins.gbprod-nord ]; extraPlugins = [ pkgs.vimPlugins.gbprod-nord ];
extraConfigLua = builtins.readFile ./config.lua; extraConfigLua = builtins.readFile ./config.lua;

View File

@@ -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 y send -X copy-selection-and-cancel
bind -T copy-mode-vi MouseDragEnd1Pane 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 # \ splits the pane side-by-side, - splits it stacked.
# in the current pane's directory, as does c for a new window. # Both open the new pane in the current pane's directory, as does c for a new window.
unbind % unbind %
unbind '"' unbind '"'
bind \\ split-window -h -c "#{pane_current_path}" 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" set -ag terminal-overrides ",*:RGB"
# monitor-bell flags a background window in the status bar when it rings the # 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 # terminal bell while unfocused.
# (no beep or flash); the per-window status highlight is independent of it. # 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 setw -g monitor-bell on
set -g bell-action none set -g bell-action none
# Minimal status bar: session name plus window list only. # 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-position bottom
set -g status-left " #S " set -g status-left " #S "
set -g status-left-length 20 set -g status-left-length 20

View File

@@ -3,8 +3,8 @@
lib, lib,
... ...
}: }:
# tmux for the primary user, configured through home-manager. Settings without a # tmux for the primary user, configured through home-manager.
# home-manager option are read from ./extra.conf. # Settings without a home-manager option are read from ./extra.conf.
let let
cfg = config.modules.tmux; cfg = config.modules.tmux;
user = config.user.name; user = config.user.name;

View File

@@ -52,8 +52,8 @@ in
"flakes" "flakes"
]; ];
# chaotic's binary cache, so the CachyOS kernel is fetched rather than # chaotic's binary cache, so the CachyOS kernel is fetched rather than compiled.
# compiled. The `extra-` prefix keeps cache.nixos.org alongside it. # The `extra-` prefix keeps cache.nixos.org alongside it.
nix.settings.extra-substituters = [ "https://nyx-cache.chaotic.cx/" ]; nix.settings.extra-substituters = [ "https://nyx-cache.chaotic.cx/" ];
nix.settings.extra-trusted-public-keys = [ nix.settings.extra-trusted-public-keys = [
"nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk=" "nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk="
@@ -80,7 +80,8 @@ in
environment.systemPackages = [ pkgs.git ]; 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.layout = "us";
services.xserver.xkb.options = "caps:escape_shifted_capslock"; services.xserver.xkb.options = "caps:escape_shifted_capslock";
@@ -108,7 +109,8 @@ in
# That is early enough to precede the account that reads it. # That is early enough to precede the account that reads it.
sops.secrets.${passwordSecret}.neededForUsers = true; 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} = { users.users.${user.name} = {
isNormalUser = true; isNormalUser = true;
description = user.description; description = user.description;