diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index e2efec1..2c7cb7c 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -1,4 +1,5 @@ -# Flake CI: formatting gate + evaluation of every host configuration. +# Flake CI: full `nix flake check` (formatting + deadnix + statix + pre-commit) +# plus an explicit per-host evaluation pass for granular output. name: CI on: @@ -27,9 +28,13 @@ jobs: extra_nix_config: | experimental-features = nix-command flakes accept-flake-config = true + substituters = https://cache.nixos.org https://nix-community.cachix.org + trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs= - - name: Check formatting - run: nix build --print-build-logs '.#checks.x86_64-linux.formatting' + # Runs every flake check: treefmt formatting, deadnix, statix, and the + # pre-commit hooks (so a --no-verify commit can't ship unlinted). + - name: Flake check + run: nix flake check --print-build-logs # Evaluate (not build) each host's toplevel so eval errors fail CI cheaply. # aarch64 / darwin hosts evaluate fine on an x86_64 runner; only building diff --git a/README.md b/README.md index 7ef1ec5..257e36c 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,11 @@ Defined in the host table in [`flake.nix`](./flake.nix): | `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) | Shared layers: `lyrathorpe/home` (home-manager: shell, git, editor), -`system/modules/common-nixos.nix` (all NixOS hosts), and -`system/modules/laptop.nix` (the physical laptops). +`system/modules/common-nixos.nix` (all NixOS hosts: fonts, nix-ld, caches), +`system/modules/workstation.nix` (physical graphical hosts: audio, thermald, +earlyoom, fwupd), `system/modules/laptop.nix` (laptops: Wi-Fi, Bluetooth, power, +lid), and `system/modules/ssh.nix` (key-only sshd). The x86 hosts also pull +`nixos-hardware` profiles. ## Applying @@ -74,5 +77,6 @@ A dev shell and a formatting/lint gate are wired through the flake: ## CI -[`.gitea/workflows/ci.yaml`](./.gitea/workflows/ci.yaml) gates `nixfmt` -formatting and evaluates every NixOS and Darwin host configuration on push/PR. +[`.gitea/workflows/ci.yaml`](./.gitea/workflows/ci.yaml) runs `nix flake check` +(formatting, `deadnix`, `statix`, the pre-commit hooks) and evaluates every +NixOS and Darwin host configuration on push/PR. diff --git a/flake.lock b/flake.lock index 1453b12..a040f94 100644 --- a/flake.lock +++ b/flake.lock @@ -271,6 +271,26 @@ "type": "github" } }, + "nixos-hardware": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781020964, + "narHash": "sha256-fS7xTi2j2iso5Hj7RNZLv/acDlCT+fgMVkVk40A7Uco=", + "owner": "NixOS", + "repo": "nixos-hardware", + "rev": "32c2cd9e46286c4eced3dc6b613c659126bf3cca", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixos-hardware", + "type": "github" + } + }, "nixos-wsl": { "inputs": { "flake-compat": "flake-compat_3", @@ -357,6 +377,7 @@ "nix-homebrew": "nix-homebrew", "nix-index-database": "nix-index-database", "nixos-apple-silicon": "nixos-apple-silicon", + "nixos-hardware": "nixos-hardware", "nixos-wsl": "nixos-wsl", "nixpkgs": "nixpkgs", "nixpkgs-unstable": "nixpkgs-unstable", diff --git a/flake.nix b/flake.nix index 2773163..63167b0 100644 --- a/flake.nix +++ b/flake.nix @@ -54,6 +54,12 @@ url = "github:nix-community/nixvim/nixos-26.05"; inputs.nixpkgs.follows = "nixpkgs"; }; + # Curated per-hardware profiles (microcode, SSD, platform quirks) for the + # physical x86 hosts. + nixos-hardware = { + url = "github:NixOS/nixos-hardware"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = @@ -232,6 +238,14 @@ modules = [ ./system/machine/T400/configuration.nix ./system/modules/laptop.nix + ./system/modules/ssh.nix + # No t400-specific profile exists; compose the generic ThinkPad + + # laptop/SSD/Intel building blocks (tp_smapi/acpi_call for battery + # thresholds, SSD + microcode defaults). + inputs.nixos-hardware.nixosModules.lenovo-thinkpad + inputs.nixos-hardware.nixosModules.common-pc-laptop + inputs.nixos-hardware.nixosModules.common-pc-laptop-ssd + inputs.nixos-hardware.nixosModules.common-cpu-intel ./lyrathorpe/swaywm.nix ]; homeModules = [ @@ -248,6 +262,9 @@ modules = [ ./system/machine/MacPro31/configuration.nix ./system/modules/desktop.nix + ./system/modules/ssh.nix + inputs.nixos-hardware.nixosModules.common-pc-ssd + inputs.nixos-hardware.nixosModules.common-cpu-intel ./lyrathorpe/swaywm.nix ]; homeModules = [ diff --git a/lyrathorpe/home/KEYBINDINGS.md b/lyrathorpe/home/KEYBINDINGS.md index c92d1d8..a64d2f1 100644 --- a/lyrathorpe/home/KEYBINDINGS.md +++ b/lyrathorpe/home/KEYBINDINGS.md @@ -177,6 +177,11 @@ across vim splits and tmux panes seamlessly. Everything else is stock vim, plus: | ---------------------- | --------------------------------------------------------- | | `,``,` | Toggle the file tree (nvim-tree) — comma pressed twice | | `Ctrl`+`h`/`j`/`k`/`l` | Move between vim splits / tmux panes (vim-tmux-navigator) | +| `ff` | Find files (telescope) | +| `fg` | Live grep (telescope) | +| `fb` | Switch buffer (telescope) | +| `xx` | Diagnostics list (trouble) | +| `gc` / `gcc` | Toggle comment (selection / line) | | `gd` | Go to definition (LSP) | | `gr` | List references (LSP) | | `K` | Hover documentation (LSP) | @@ -184,7 +189,9 @@ across vim splits and tmux panes seamlessly. Everything else is stock vim, plus: | `ca` | Code action (LSP) | LSP covers Nix, Lua, Python and Terraform (the work box adds C# and Helm); -completion (nvim-cmp) appears as you type. `:Git` opens fugitive. +completion (nvim-cmp) appears as you type. Files are formatted on save +(conform-nvim). `:Git` opens fugitive; gitsigns shows gutter signs. which-key +pops up after `` to show the rest. --- diff --git a/lyrathorpe/home/README.md b/lyrathorpe/home/README.md index d34a606..6df0289 100644 --- a/lyrathorpe/home/README.md +++ b/lyrathorpe/home/README.md @@ -44,15 +44,18 @@ and the C#/Helm language servers). | `eza` | modern `ls` (drives the ls aliases) | | `bat` | syntax-highlighting pager (Catppuccin Mocha theme); behaves like `cat` when piped; also the `MANPAGER` | | `ripgrep` / `fd` | fast search (`rg`) and find (`fd`); also back `fzf` | -| `jq` / `btop` | JSON processor; resource monitor | +| `jq` | JSON processor | | `gh` / `tea` | GitHub and Gitea (`code.emmathe.dev`) CLIs; `gh` uses SSH | | `nix-index` | `command-not-found`: an unknown command tells you which Nix package provides it (prebuilt DB, no manual indexing) | | `comma` (`,`) | run an uninstalled program once: `, cowsay hi` | | `nh` | nicer `nixos-rebuild`/`home-manager` with diffs; `$NH_FLAKE` set to the repo. No scheduled GC (it could reap paths a running generation still references) — collect garbage manually with `nh clean all` / `nix-collect-garbage -d` | +| `btop` | resource monitor, themed Catppuccin Mocha (vendored theme) | +| `lazygit` | git TUI for staging/rebasing, themed to match (`git.nix`) | +| `hyperfine` / `sd` | command-line benchmarking; saner find-and-replace than sed | -**Theming:** `fzf`, `bat` and `git`'s `delta` pager are all Catppuccin Mocha, -driven from the shared `../catppuccin-mocha.nix` palette / the catppuccin/bat -theme. +**Theming:** `fzf`, `bat`, `btop`, `lazygit` and `git`'s `delta` pager are all +Catppuccin Mocha, driven from the shared `../catppuccin-mocha.nix` palette / the +catppuccin upstream themes. **Env & defaults:** `xdg.enable` on; `PAGER`/`MANPAGER` (bat) set in `default.nix` (the editor owns `$EDITOR`/`$VISUAL`); `xdg.mimeApps` maps web→Firefox, @@ -80,17 +83,22 @@ non-interactive shells. Escape hatch: `NO_TMUX=1 ` opens a bare shell. | Clipboard | `set-clipboard on`; foot `terminal-features` advertise truecolor/sync/OSC52/title/cursor | **Plugins:** `sensible`, `vim-tmux-navigator` (Ctrl-h/j/k/l across vim ↔ tmux), -`yank`, `catppuccin` (Mocha statusline), `resurrect` + `continuum` +`yank`, `extrakto` (`prefix`+`Tab`: fzf-grab paths/URLs/text from the pane into +the prompt), `catppuccin` (Mocha statusline), `resurrect` + `continuum` (sessions auto-save and restore across reboots). The statusline draws Nerd-Font glyphs — see Fonts. ## Fonts -**JetBrainsMono Nerd Font** is installed on every host (in `common-nixos.nix`, -because tmux runs everywhere; the Mac installs it to `/Library/Fonts` via the -Darwin config). foot uses it as its main font automatically. iTerm2's font is a -GUI setting — set it to _JetBrainsMono Nerd Font_ (Settings → Profiles → Text → -Font) so the tmux statusline glyphs render instead of `?`. +**JetBrainsMono Nerd Font**, **Noto Sans** and **Noto Color Emoji** are +installed on every host (in `common-nixos.nix`, because tmux/terminals run +everywhere; the Mac installs the Nerd Font to `/Library/Fonts` via the Darwin +config). `fonts.fontconfig.defaultFonts` maps the generic families so anything +asking for `monospace` gets the Nerd Font (with emoji fallback) — this also +gives the WSL box emoji/sans coverage it otherwise lacked. foot uses the Nerd +Font as its main font automatically. iTerm2's font is a GUI setting — set it to +_JetBrainsMono Nerd Font_ (Settings → Profiles → Text → Font) so the tmux +statusline glyphs render instead of `?`. ## Editor (Neovim) @@ -99,17 +107,23 @@ declaratively with **nixvim**, so the same plugins and config are baked in on every host. Migrated from plain vim; the practical gain is a real LSP stack in place of the old (inert) ALE. -| Feature | Notes | -| ------------- | -------------------------------------------------------------------------------------- | -| Colorscheme | Catppuccin Mocha (matches the terminal and the rest of the desktop) | -| File tree | nvim-tree, toggled with `,,` (comma twice; was nerdtree) | -| Indent guides | indent-blankline, on by default (was vim-indent-guides) | -| Git | fugitive (`:Git …`) | -| Pane nav | vim-tmux-navigator — `Ctrl`+`h/j/k/l` moves across vim splits and tmux panes | -| Syntax | tree-sitter (nix, lua, bash, markdown, groovy) — replaces `syntax enable` | -| LSP | nvim-cmp completion + servers `nil` (Nix), `lua_ls`, `pyright` (Python), `terraformls` | -| Indentation | 2-wide hard tabs (`noexpandtab`, `tabstop`/`shiftwidth` = 2); line numbers on | -| Filetypes | `*Jenkinsfile` → groovy | +| Feature | Notes | +| -------------- | -------------------------------------------------------------------------------------- | +| Colorscheme | Catppuccin Mocha (matches the terminal and the rest of the desktop) | +| File tree | nvim-tree, toggled with `,,` (comma twice; was nerdtree) | +| Fuzzy finder | telescope (+fzf-native): `ff` files, `fg` grep, `fb` buffers | +| Format on save | conform-nvim (nixfmt, stylua, ruff, shfmt, prettier, gofumpt; LSP fallback otherwise) | +| Git | fugitive (`:Git …`) + gitsigns gutter signs/blame | +| Diagnostics | inline + trouble list (`xx`) | +| Completion | nvim-cmp (LSP/buffer/path) with luasnip snippet expansion | +| Indent guides | indent-blankline, on by default (was vim-indent-guides) | +| Statusline | lualine (Catppuccin theme) | +| Editing | which-key hints, comment (`gc`/`gcc`), autopairs, treesitter textobjects | +| Pane nav | vim-tmux-navigator — `Ctrl`+`h/j/k/l` moves across vim splits and tmux panes | +| Syntax | tree-sitter (nix, lua, bash, markdown, groovy, c#, python, terraform, yaml) | +| LSP | nvim-cmp completion + servers `nil` (Nix), `lua_ls`, `pyright` (Python), `terraformls` | +| Indentation | 2-wide hard tabs (`noexpandtab`, `tabstop`/`shiftwidth` = 2); line numbers on | +| Filetypes | `*Jenkinsfile` → groovy | Leader is `Space`. LSP keymaps (`gd`, `gr`, `K`, `rn`, `ca`) and the file-tree toggle are listed in @@ -121,14 +135,16 @@ host-specific ones go in that host's module — the work box (`work.nix`) adds ## git Pager is **delta**. **commitizen** is installed on every host; `cz` defaults to -Conventional Commits. +Conventional Commits. **lazygit** (themed) is the TUI. The commit-graph is kept +current (`gc`/`fetch.writeCommitGraph`) so `lg` stays fast. -| Aliases | | -| ------------------------ | ----------------------------------------------------------------- | -| `st` `co` `sw` `br` `ci` | status / checkout / switch / branch / commit | -| `last` `unstage` | last commit / unstage | -| `lg` | graph log, all branches | -| `cz` `cc` | `git cz ` (e.g. `git cz c`) and `git cc` → commitizen prompt | +| Aliases | | +| ------------------------ | ------------------------------------------------------------------ | +| `st` `co` `sw` `br` `ci` | status / checkout / switch / branch / commit | +| `last` `unstage` | last commit / unstage | +| `amend` `fixup` `undo` | amend-no-edit / `commit --fixup` / soft-reset HEAD~1 (keep staged) | +| `lg` | graph log, all branches | +| `cz` `cc` | `git cz ` (e.g. `git cz c`) and `git cc` → commitizen prompt | | Behaviour | | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | diff --git a/lyrathorpe/home/default.nix b/lyrathorpe/home/default.nix index c9fa8d6..8e0b05a 100644 --- a/lyrathorpe/home/default.nix +++ b/lyrathorpe/home/default.nix @@ -1,6 +1,6 @@ # Base home-manager profile, shared by every host (graphical or headless). # Graphical hosts additionally import ./desktop.nix; the work host imports -# ../../system/modules/work/default.nix. See the host table in flake.nix. +# ./work.nix. See the host table in flake.nix. { ... }: { imports = [ diff --git a/lyrathorpe/home/editor.nix b/lyrathorpe/home/editor.nix index 8e98a69..8f84c06 100644 --- a/lyrathorpe/home/editor.nix +++ b/lyrathorpe/home/editor.nix @@ -2,7 +2,7 @@ # tree, indent guides, fugitive, tmux-navigator, Catppuccin Mocha, 2-space hard # tabs, Jenkinsfile=groovy) plus a real LSP stack in place of the inert ALE. # Wanted on every host; vi/vim/$EDITOR all launch nvim. -{ inputs, ... }: +{ inputs, pkgs, ... }: { imports = [ inputs.nixvim.homeModules.nixvim ]; @@ -16,6 +16,17 @@ # warn that its pinned nixpkgs was overridden by the input `follows`. nixpkgs.source = inputs.nixpkgs; + # Formatter binaries for conform-nvim (below), matching the repo's treefmt + # set. On nvim's PATH only. + extraPackages = with pkgs; [ + nixfmt + stylua + ruff + shfmt + prettier + gofumpt + ]; + globals.mapleader = " "; opts = { @@ -77,11 +88,52 @@ cmp = { enable = true; autoEnableSources = true; - settings.sources = [ - { name = "nvim_lsp"; } - { name = "buffer"; } - { name = "path"; } - ]; + settings = { + snippet.expand = "function(args) require('luasnip').lsp_expand(args.body) end"; + sources = [ + { name = "nvim_lsp"; } + { name = "luasnip"; } + { name = "buffer"; } + { name = "path"; } + ]; + }; + }; + + # Fuzzy finder (files / live grep / symbols); rg + fd are already on PATH. + telescope = { + enable = true; + extensions.fzf-native.enable = true; + }; + gitsigns.enable = true; # gutter signs, stage-hunk, blame + which-key.enable = true; # popup of pending keybindings (leader is Space) + trouble.enable = true; # project-wide diagnostics/quickfix list + lualine = { + enable = true; + settings.options.theme = "catppuccin"; + }; + comment.enable = true; # gc / gcc comment toggling + nvim-autopairs.enable = true; + treesitter-textobjects.enable = true; + luasnip.enable = true; # snippet engine (drives cmp's luasnip source above) + + # Format-on-save, mirroring the repo's treefmt set. Filetypes with no + # formatter here (e.g. terraform) fall back to the LSP formatter. + conform-nvim = { + enable = true; + settings = { + formatters_by_ft = { + nix = [ "nixfmt" ]; + lua = [ "stylua" ]; + python = [ "ruff_format" ]; + sh = [ "shfmt" ]; + markdown = [ "prettier" ]; + go = [ "gofumpt" ]; + }; + format_on_save = { + timeout_ms = 2000; + lsp_format = "fallback"; + }; + }; }; }; @@ -92,6 +144,30 @@ action = "NvimTreeToggle"; options.desc = "Toggle file tree"; } + { + mode = "n"; + key = "ff"; + action = "Telescope find_files"; + options.desc = "Find files"; + } + { + mode = "n"; + key = "fg"; + action = "Telescope live_grep"; + options.desc = "Live grep"; + } + { + mode = "n"; + key = "fb"; + action = "Telescope buffers"; + options.desc = "Buffers"; + } + { + mode = "n"; + key = "xx"; + action = "Trouble diagnostics toggle"; + options.desc = "Diagnostics list"; + } ]; # au BufNewFile,BufRead *Jenkinsfile setf groovy diff --git a/lyrathorpe/home/git.nix b/lyrathorpe/home/git.nix index d539e6a..ae11c7f 100644 --- a/lyrathorpe/home/git.nix +++ b/lyrathorpe/home/git.nix @@ -1,11 +1,14 @@ -# Version control: git + delta pager + commitizen. The work host layers -# commit signing and an email override on top (see work/default.nix). +# Version control: git + delta pager + commitizen + lazygit. The work host +# layers commit signing and an email override on top (see work.nix). { pkgs, lib, fullName, ... }: +let + ctp = import ../catppuccin-mocha.nix; +in { home.packages = [ pkgs.commitizen @@ -31,6 +34,9 @@ }; fetch.prune = true; # drop deleted remote-tracking branches + # Keep the commit-graph current (fast `git log --graph`, used by `lg`). + fetch.writeCommitGraph = true; + gc.writeCommitGraph = true; merge.conflictStyle = "zdiff3"; # show the common ancestor in conflicts diff = { algorithm = "histogram"; @@ -61,6 +67,9 @@ ci = "commit"; last = "log -1 HEAD"; unstage = "reset HEAD --"; + amend = "commit --amend --no-edit"; # tack staged changes onto HEAD + fixup = "commit --fixup"; # `git fixup ` -> autosquash on next rebase + undo = "reset --soft HEAD~1"; # undo last commit, keep the changes staged lg = "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(auto)%d%C(reset)' --all"; # commitizen (Conventional Commits, its default ruleset): `git cz c` -> # `cz commit`, `git cz bump`, etc. `git cc` is a shortcut for the prompt. @@ -92,4 +101,17 @@ enable = true; enableGitIntegration = true; }; + + # lazygit: TUI for staging/rebasing, themed to Catppuccin Mocha to match. + programs.lazygit = { + enable = true; + settings.gui.theme = { + activeBorderColor = [ + "#${ctp.blue}" + "bold" + ]; + inactiveBorderColor = [ "#${ctp.surface1}" ]; + selectedLineBgColor = [ "#${ctp.surface0}" ]; + }; + }; } diff --git a/lyrathorpe/home/shell.nix b/lyrathorpe/home/shell.nix index a16d455..72a66f5 100644 --- a/lyrathorpe/home/shell.nix +++ b/lyrathorpe/home/shell.nix @@ -23,10 +23,22 @@ in pkgs.ripgrep pkgs.fd pkgs.jq - pkgs.btop pkgs.tea + pkgs.hyperfine # command-line benchmarking + pkgs.sd # saner find-and-replace than sed ]; + # Resource monitor, themed Catppuccin Mocha to match the rest of the desktop. + # btop does not bundle the theme, so vendor it from catppuccin/btop (pinned). + programs.btop = { + enable = true; + settings.color_theme = "catppuccin_mocha"; + }; + xdg.configFile."btop/themes/catppuccin_mocha.theme".source = pkgs.fetchurl { + url = "https://raw.githubusercontent.com/catppuccin/btop/f437574b600f1c6d932627050b15ff5153b58fa3/themes/catppuccin_mocha.theme"; + hash = "sha256-THRpq5vaKCwf9gaso3ycC4TNDLZtBB5Ofh/tOXkfRkQ="; + }; + programs.zsh = { enable = true; # Keep zsh dotfiles under XDG (~/.config/zsh) rather than the legacy $HOME @@ -218,6 +230,7 @@ in sensible vim-tmux-navigator # Ctrl-h/j/k/l across vim splits and tmux panes yank + extrakto # prefix+Tab: fzf-grab paths/URLs/text from the pane into the prompt { # Catppuccin Mocha statusline (v2 API: flavour + window options must be # set before the plugin loads, which home-manager does for plugin @@ -283,7 +296,7 @@ in # Add the key to the agent on first use, so the passphrase is typed once per # login session rather than per commit/push (commit signing uses this agent). - # The work box keeps its own ssh config (see work/default.nix), so this only + # The work box keeps its own ssh config (see work.nix), so this only # manages ~/.ssh/config on the personal hosts. programs.ssh = { enable = true; diff --git a/lyrathorpe/home/sway.nix b/lyrathorpe/home/sway.nix index b5d2623..f250716 100644 --- a/lyrathorpe/home/sway.nix +++ b/lyrathorpe/home/sway.nix @@ -1,11 +1,13 @@ # Declarative Sway window manager, status bar, lock, idle and notifications. # Imported via ./desktop.nix, so only graphical hosts get it. # -# The compositor binary, PAM and polkit integration come from the system-level +# The compositor binary, PAM and the polkit *daemon* come from the system-level # programs.sway (see ../swaywm.nix); package = null below reuses it instead of -# pulling a second Sway. home-manager owns the user config (~/.config/sway) and -# wires the systemd user session (sway-session.target), which is what lets the -# swayidle/dunst user services start with the desktop. +# pulling a second Sway. The polkit authentication *agent* (the thing that draws +# the GUI auth dialog) is a user service started here. home-manager owns the user +# config (~/.config/sway) and wires the systemd user session (sway-session.target), +# which is what lets the agent/swayidle/dunst/kanshi user services start with the +# desktop. { pkgs, lib, @@ -99,6 +101,16 @@ in criteria.app_id = "launcher"; command = "floating enable, resize set 800 500"; } + # Don't let swayidle blank/lock during fullscreen video. Two rules cover + # native Wayland (app_id) and XWayland (class) clients. + { + criteria.app_id = ".*"; + command = "inhibit_idle fullscreen"; + } + { + criteria.class = ".*"; + command = "inhibit_idle fullscreen"; + } ]; # Binding modes (submenus). Entered from keybindings below; each action @@ -277,6 +289,64 @@ in # an old entry through fuzzel. services.clipman.enable = true; + # Polkit authentication agent. programs.sway (system) enables the polkit + # daemon but no agent, so GUI privilege prompts (nemo mounting a disk, + # NetworkManager/blueman editing a system resource) would otherwise fail + # silently. lxqt-policykit is a small, toolkit-light agent; bind it to the + # Sway session so it starts and stops with the desktop. + systemd.user.services.polkit-lxqt = { + Unit = { + Description = "lxqt-policykit polkit authentication agent"; + PartOf = [ "graphical-session.target" ]; + After = [ "graphical-session.target" ]; + }; + Service = { + ExecStart = "${pkgs.lxqt.lxqt-policykit}/bin/lxqt-policykit-agent"; + Restart = "on-failure"; + }; + Install.WantedBy = [ "sway-session.target" ]; + }; + + # Output/display management. Reacts to hotplug and applies per-display + # mode/scale/position. Profiles are hardware-specific: the safe default below + # just enables the internal laptop panel; add docked/desktop profiles with the + # real identifiers from `swaymsg -t get_outputs` (e.g. the Mac Pro's Apple + # Cinema Display with its scale, or a docked laptop + external monitor). + services.kanshi = { + enable = true; + settings = [ + { + profile.name = "undocked"; + profile.outputs = [ + { + criteria = "eDP-1"; + status = "enable"; + } + ]; + } + # Example to copy per host (fill in real criteria/mode/scale/position): + # { + # profile.name = "desktop"; + # profile.outputs = [ + # { criteria = "Apple Computer Inc Cinema HD ..."; mode = "2560x1600"; scale = 1.0; position = "0,0"; status = "enable"; } + # ]; + # } + ]; + }; + + # Night light. Manual location (no geoclue dependency); adjust the coordinates + # to taste. Warmer at night, neutral by day. + services.gammastep = { + enable = true; + provider = "manual"; + latitude = 51.5; + longitude = -0.13; # London-ish; set to your actual location + temperature = { + day = 6500; + night = 3700; + }; + }; + # fuzzel: the dmenu picker used by clipman, themed Catppuccin Mocha to match # (fuzzel colours are RRGGBBAA -- 8 hex digits). programs.fuzzel = { diff --git a/lyrathorpe/home/work.nix b/lyrathorpe/home/work.nix index 27d54ea..d504026 100644 --- a/lyrathorpe/home/work.nix +++ b/lyrathorpe/home/work.nix @@ -39,6 +39,14 @@ pkgs.wget pkgs.claude-code pkgs.google-cloud-sdk + # Day-to-day Kubernetes / Helm / Terraform accelerators for this box. + pkgs.k9s # cluster TUI + pkgs.kubectx # kubectx + kubens (context/namespace switch) + pkgs.stern # multi-pod log tail + pkgs.dyff # semantic YAML/manifest diffs (Helm release drift) + pkgs.tflint # Terraform linter (catches what terraformls won't) + pkgs.terraform-docs # generate Terraform module docs + pkgs.yq-go # jq for YAML ]; services.ssh-agent.enable = true; home.shellAliases = { diff --git a/system/machine/Darwin/configuration.nix b/system/machine/Darwin/configuration.nix index e894e08..bea63d2 100644 --- a/system/machine/Darwin/configuration.nix +++ b/system/machine/Darwin/configuration.nix @@ -151,6 +151,38 @@ }; }; + # Touch ID authorises sudo (and darwin-rebuild's sudo prompt) instead of a + # typed password. sudo_local keeps the change in /etc/pam.d/sudo_local so it + # survives macOS updates. + security.pam.services.sudo_local.touchIdAuth = true; + + # Declarative macOS UI defaults -- the main reason to run nix-darwin beyond + # package management. Applied on activation; all reversible. + system.defaults = { + dock = { + autohide = true; + show-recents = false; + mru-spaces = false; # don't reorder spaces by use + tilesize = 48; + }; + finder = { + AppleShowAllExtensions = true; + ShowPathbar = true; + FXPreferredViewStyle = "Nlsv"; # list view + _FXShowPosixPathInTitle = true; + }; + NSGlobalDomain = { + AppleInterfaceStyle = "Dark"; + ApplePressAndHoldEnabled = false; # key-repeat instead of the accent popup + InitialKeyRepeat = 15; + KeyRepeat = 2; + }; + trackpad = { + Clicking = true; # tap to click + TrackpadThreeFingerDrag = true; + }; + }; + # Used for backwards compatibility; read `darwin-rebuild changelog` before changing. system.stateVersion = 5; } diff --git a/system/machine/EDaaS/configuration.nix b/system/machine/EDaaS/configuration.nix index 13bbc90..17ee67d 100644 --- a/system/machine/EDaaS/configuration.nix +++ b/system/machine/EDaaS/configuration.nix @@ -58,7 +58,7 @@ systemd.services.docker-desktop-proxy.script = lib.mkForce ''${config.wsl.wslConf.automount.root}/wsl/docker-desktop/docker-desktop-user-distro proxy --docker-desktop-root ${config.wsl.wslConf.automount.root}/wsl/docker-desktop "C:\Program Files\Docker\Docker\resources"''; features.swayDesktop.enable = false; - programs.nix-ld.enable = true; + # programs.nix-ld is enabled for all NixOS hosts in common-nixos.nix. # This value determines the NixOS release from which the default # settings for stateful data, like file locations and database versions # on your system were taken. It's perfectly fine and recommended to leave diff --git a/system/machine/MBP-Asahi/configuration.nix b/system/machine/MBP-Asahi/configuration.nix index 0a1d5f7..3aa0d1f 100644 --- a/system/machine/MBP-Asahi/configuration.nix +++ b/system/machine/MBP-Asahi/configuration.nix @@ -12,7 +12,7 @@ boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = false; - networking.hostName = "Emma-Asahi"; + networking.hostName = "Lyra-Asahi"; # Audio (PipeWire) and the swaylock PAM stack are inherited from # workstation.nix. hardware.enableRedistributableFirmware is also set there; diff --git a/system/machine/MacPro31/configuration.nix b/system/machine/MacPro31/configuration.nix index 6b7d3f7..66b0834 100644 --- a/system/machine/MacPro31/configuration.nix +++ b/system/machine/MacPro31/configuration.nix @@ -22,6 +22,10 @@ networking.hostName = "MacPro31-NixOS"; + # Elderly host: a compressed RAM swap softens memory pressure (earlyoom in + # workstation.nix is the backstop). + zramSwap.enable = true; + # This host accepts SSH, so open 22 (the firewall itself is enabled in # workstation.nix with a default-deny policy). services.openssh.enable = true; diff --git a/system/machine/T400/configuration.nix b/system/machine/T400/configuration.nix index 361130c..52c8931 100644 --- a/system/machine/T400/configuration.nix +++ b/system/machine/T400/configuration.nix @@ -1,7 +1,7 @@ # ThinkPad T400 (NixOS). Shared laptop options live in ../../modules/laptop.nix; # only host-specific settings are here. Install notes (boot variants, GPU, # partitions): see ./README.md. -{ ... }: +{ config, ... }: { imports = [ @@ -31,6 +31,16 @@ # the radeon firmware needed by the discrete GPU below. hardware.cpu.intel.updateMicrocode = true; + # Battery longevity: cap charging to 75-80%. tlp itself comes from the + # nixos-hardware lenovo-thinkpad profile; tp_smapi supplies the threshold + # sysfs on this 2008-era ThinkPad (kernel-native natacpi is too new for it). + boot.kernelModules = [ "tp_smapi" ]; + boot.extraModulePackages = [ config.boot.kernelPackages.tp_smapi ]; + services.tlp.settings = { + START_CHARGE_THRESH_BAT0 = 75; + STOP_CHARGE_THRESH_BAT0 = 80; + }; + # This T400 has the optional discrete GPU fitted: an ATI Mobility Radeon HD # 3470 (RV620), driven by the open `radeon` KMS driver. Load it in the initrd # for early modesetting (clean Sway/Wayland start); firmware comes from diff --git a/system/modules/common-nixos.nix b/system/modules/common-nixos.nix index fe87953..e4b0f00 100644 --- a/system/modules/common-nixos.nix +++ b/system/modules/common-nixos.nix @@ -13,6 +13,18 @@ nix.settings.auto-optimise-store = true; nix.settings.download-buffer-size = 134217728; # 128 MiB + # Extra binary cache for the nix-community toolchain (home-manager, nixvim, + # treefmt, ...). Merges with any host-specific caches (e.g. the Asahi cache on + # the MBP) rather than replacing them. + nix.settings.substituters = [ "https://nix-community.cachix.org" ]; + nix.settings.trusted-public-keys = [ + "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" + ]; + + # Run dynamically-linked foreign binaries (VS Code remote server, prebuilt + # toolchains, language-server downloads) on every NixOS host, not just WSL. + programs.nix-ld.enable = true; + # Minimal system-level CLI available before the home-manager profile loads # (e.g. early boot / rescue). User-level tooling lives in home-manager. environment.systemPackages = with pkgs; [ @@ -20,9 +32,31 @@ fastfetch ]; - # Terminal font with powerline/Nerd glyphs. Installed on every host because - # the tmux statusline (which uses these glyphs) runs everywhere, not just on - # the Sway/graphical hosts. foot names it explicitly (home/sway.nix); the Mac - # installs it via the Darwin config. - fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ]; + # Fonts on every host. The Nerd Font carries the powerline/Nerd glyphs the + # tmux statusline uses (foot names it explicitly in home/sway.nix); Noto sans + + # colour emoji prevent tofu in terminals/TUIs/Firefox -- important on the WSL + # box, which does not pull the graphical hosts' default Noto stack. The Mac + # installs the Nerd Font via the Darwin config. + fonts.packages = with pkgs; [ + nerd-fonts.jetbrains-mono + noto-fonts + noto-fonts-color-emoji + ]; + # Map the generic fontconfig families so anything asking for "monospace" gets + # the Nerd Font (with emoji fallback), not DejaVu. + fonts.fontconfig.defaultFonts = { + monospace = [ + "JetBrainsMono Nerd Font" + "Noto Color Emoji" + ]; + sansSerif = [ + "Noto Sans" + "Noto Color Emoji" + ]; + serif = [ + "Noto Serif" + "Noto Color Emoji" + ]; + emoji = [ "Noto Color Emoji" ]; + }; } diff --git a/system/modules/laptop.nix b/system/modules/laptop.nix index 211beab..127dd95 100644 --- a/system/modules/laptop.nix +++ b/system/modules/laptop.nix @@ -12,4 +12,20 @@ enable = true; settings.General.EnableNetworkConfiguration = true; }; + + # Lid behaviour: suspend on battery, lock on external power (swayidle's + # before-sleep hook locks before the suspend completes either way). + services.logind.settings.Login = { + HandleLidSwitch = "suspend"; + HandleLidSwitchExternalPower = "lock"; + }; + + # Bluetooth. The Asahi MBP loads Apple's BT firmware (see its host config) and + # the T400 has an optional BT module; enable bluez on both, with blueman as the + # GUI/tray manager for the Sway session. + hardware.bluetooth = { + enable = true; + powerOnBoot = true; + }; + services.blueman.enable = true; } diff --git a/system/modules/ssh.nix b/system/modules/ssh.nix new file mode 100644 index 0000000..ee2423a --- /dev/null +++ b/system/modules/ssh.nix @@ -0,0 +1,19 @@ +# Key-only SSH hardening, imported by the hosts that run sshd (T400, Mac Pro). +# The host config still does `services.openssh.enable = true` and opens port 22 +# next to where it documents the listening service; this module only tightens +# the policy and installs the authorized key, so a host opting into sshd cannot +# accidentally ship password/root login. +{ username, ... }: +{ + services.openssh.settings = { + PasswordAuthentication = false; # keys only + KbdInteractiveAuthentication = false; # no keyboard-interactive fallback + PermitRootLogin = "no"; + }; + + # The key permitted to log in as the primary user. Add more entries here as + # new client machines are provisioned. + users.users.${username}.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDxHvdMTOzpFWUFMtCP7C/4tIOUO3GIO2QPvaifSnWH lyrathorpe@Lyra-MBA" + ]; +} diff --git a/system/modules/workstation.nix b/system/modules/workstation.nix index a8e52b1..fcd58ea 100644 --- a/system/modules/workstation.nix +++ b/system/modules/workstation.nix @@ -5,12 +5,16 @@ # The bootloader is NOT set here -- it is firmware-specific, not form-factor: # UEFI hosts (MBP, Mac Pro 3,1) use systemd-boot, the BIOS-only T400 uses GRUB. # Each machine config declares its own. -{ ... }: +{ lib, pkgs, ... }: { features.swayDesktop.enable = true; console.keyMap = "dvorak"; + # Intel thermal management. x86 only -- the Asahi MBP governs its own SoC + # thermals, and thermald is an Intel-platform daemon. + services.thermald.enable = lib.mkIf pkgs.stdenv.hostPlatform.isx86_64 true; + # Default-deny inbound. Hosts that run a listening service open their own # ports next to where the service is enabled (e.g. sshd -> 22 on X1). networking.firewall.enable = true; @@ -20,6 +24,14 @@ services.fstrim.enable = true; boot.tmp.cleanOnBoot = true; + # Userspace OOM killer: act on memory pressure early instead of letting the + # kernel OOM-thrash. Matters on the 4 GiB T400 and the elderly Mac Pro. + services.earlyoom.enable = true; + + # Firmware updates via LVFS. No-op on the Asahi MBP (Apple-managed firmware), + # useful for UEFI/SSD updates on the x86 hosts. + services.fwupd.enable = true; + # Audio. PipeWire with the PulseAudio shim covers every graphical host; no # per-machine audio config is needed. services.pipewire = {