Separate user identity (data) from the reusable modules, and let a host declare any number of users instead of exactly one. - users/registry.nix: per-user identity (name, email, groups, authorized and signing keys) as the single source of identity; no user data is hardcoded in the modules. - mkHost takes a `users` set keyed by username; per-user identity is injected into each home config via the `identity` module arg (extraSpecialArgs is per-host, so it cannot carry per-user data). - modules/users.nix builds accounts from the registry; modules/ssh.nix no longer defines authorized keys (the registry owns them); home/git.nix and home/desktop.nix read `identity`; users/emmathorpe/work.nix drops its now-redundant git identity override. - Restructure the tree: users/, home/, modules/, hosts/, lib/ replace the former lyrathorpe/ and system/ layout. - Add standalone homeConfigurations (the portable subset: shell, git, editor, claude) and an exported homeModules output for use on machines not managed by this flake, or as an input to other flakes. Behaviour-preserving for existing hosts: lyrathorpe-mbp and emmathorpe-edaas evaluate to identical derivations; lyrathorpe-t400, lyrathorpe-macpro31 and lyrathorpe-rpi5 differ only by de-duplicating a repeated authorized_keys entry. Fixes the SSH authorized-key leak (one user's key was applied to every account), the hardcoded default git identity, and the hardcoded EDaaS linger setting.
487 lines
19 KiB
Nix
487 lines
19 KiB
Nix
{
|
|
description = "NixOS configuration";
|
|
|
|
inputs = {
|
|
# Pinned stable channel; the single source of truth for every host.
|
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
|
|
# Bleeding-edge channel, used only to pull individual packages via overlay.
|
|
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
|
|
# Home-manager release matched to the stable nixpkgs; `follows` keeps a single nixpkgs eval.
|
|
home-manager.url = "github:nix-community/home-manager/release-26.05";
|
|
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
|
# WSL module for the EDaaS host; flake input avoids the impure <nixos-wsl> NIX_PATH lookup.
|
|
nixos-wsl.url = "github:nix-community/NixOS-WSL";
|
|
nixos-wsl.inputs.nixpkgs.follows = "nixpkgs";
|
|
# Apple Silicon (Asahi) support for the MacBook host.
|
|
nixos-apple-silicon.url = "github:nix-community/nixos-apple-silicon";
|
|
nixos-apple-silicon.inputs.nixpkgs.follows = "nixpkgs";
|
|
# nix-darwin: manage macOS hosts from this same flake.
|
|
nix-darwin.url = "github:nix-darwin/nix-darwin/nix-darwin-26.05";
|
|
nix-darwin.inputs.nixpkgs.follows = "nixpkgs";
|
|
# nix-homebrew: declaratively own and install the Homebrew prefix on macOS.
|
|
nix-homebrew.url = "github:zhaofengli/nix-homebrew";
|
|
# Provides mkFlake: the systems/perSystem scaffolding used below.
|
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
|
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
|
|
# Declarative Firefox add-ons (e.g. the Catppuccin theme); see modules/users.nix.
|
|
firefox-addons = {
|
|
url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
# Prebuilt nix-index database so "command not found -> which package
|
|
# provides it" works immediately (no manual `nix-index` run). See shell.nix.
|
|
nix-index-database = {
|
|
url = "github:nix-community/nix-index-database";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
# treefmt-nix: one multi-language formatter driving `nix fmt` and the
|
|
# formatting flake check (nixfmt + shfmt + prettier).
|
|
treefmt-nix = {
|
|
url = "github:numtide/treefmt-nix";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
# git-hooks.nix: declarative pre-commit hooks (nixfmt/deadnix/statix),
|
|
# installed into the repo via the devShell.
|
|
git-hooks = {
|
|
url = "github:cachix/git-hooks.nix";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
# Declarative Neovim (the editor; see home/editor.nix). Release
|
|
# branch matched to the pinned nixpkgs (26.05); follows our nixpkgs to keep a
|
|
# single nixpkgs in the closure. editor.nix sets programs.nixvim.nixpkgs.source
|
|
# to this same input so the home module doesn't warn about the pin.
|
|
nixvim = {
|
|
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 =
|
|
inputs@{
|
|
flake-parts,
|
|
nixpkgs,
|
|
nixpkgs-unstable,
|
|
home-manager,
|
|
nixos-wsl,
|
|
nixos-apple-silicon,
|
|
nix-darwin,
|
|
nix-homebrew,
|
|
...
|
|
}:
|
|
flake-parts.lib.mkFlake { inherit inputs; } (
|
|
{ lib, ... }:
|
|
let
|
|
# claude-code tracks nixpkgs-unstable regardless of the pinned nixpkgs.
|
|
overlays = [
|
|
(_final: prev: {
|
|
inherit
|
|
(import nixpkgs-unstable {
|
|
inherit (prev.stdenv.hostPlatform) system;
|
|
config.allowUnfree = true;
|
|
})
|
|
claude-code
|
|
;
|
|
})
|
|
];
|
|
|
|
# Unfree packages permitted to be built (replaces blanket allowUnfree).
|
|
unfreePackages = [
|
|
"claude-code"
|
|
"lens"
|
|
"lens-desktop"
|
|
];
|
|
|
|
# Identity registry: who each user is (name, email, keys, groups), keyed
|
|
# by username. Threaded into the system layer as the `userRegistry`
|
|
# specialArg and into each user's home config as the `identity` module
|
|
# arg. See users/registry.nix, modules/users.nix, home/git.nix.
|
|
userRegistry = import ./users/registry.nix;
|
|
|
|
# nixpkgs + nix-daemon settings shared by NixOS and Darwin hosts.
|
|
commonModule = {
|
|
nixpkgs.overlays = overlays;
|
|
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) unfreePackages;
|
|
nix.settings.experimental-features = [
|
|
"nix-command"
|
|
"flakes"
|
|
];
|
|
# Make `nix shell nixpkgs#...` and <nixpkgs> use the pinned nixpkgs.
|
|
nix.registry.nixpkgs.flake = nixpkgs;
|
|
nix.nixPath = [ "nixpkgs=${nixpkgs}" ];
|
|
};
|
|
|
|
# Shared scaffolding for every NixOS host: common user, settings, home-manager.
|
|
baseModules = [
|
|
./modules/users.nix
|
|
./modules/common-nixos.nix
|
|
./modules/features.nix
|
|
commonModule
|
|
home-manager.nixosModules.home-manager
|
|
{
|
|
home-manager.useGlobalPkgs = true;
|
|
home-manager.useUserPackages = true;
|
|
# Back up pre-existing dotfiles (e.g. .zshrc) instead of aborting
|
|
# activation when home-manager would overwrite them.
|
|
home-manager.backupFileExtension = "backup";
|
|
}
|
|
];
|
|
|
|
# mkHost :: { system, modules, users, portable } -> nixosSystem
|
|
# Builds one machine by appending its host-specific modules to the shared
|
|
# baseModules. `users` is an attrset keyed by username; each value carries
|
|
# that user's home-module list and optional per-host-user system bits
|
|
# (e.g. linger). Per-user identity is injected into each home config via
|
|
# the `identity` module arg (extraSpecialArgs is per-host, so it cannot
|
|
# carry per-user data); the system layer reads the global `userRegistry`
|
|
# restricted to this host's `hostUsers` set.
|
|
mkHost =
|
|
{
|
|
system,
|
|
modules,
|
|
users,
|
|
# Host form factor. Laptops inherit the default; a desktop host sets
|
|
# `portable = false` to drop mobile components (battery block,
|
|
# brightness keys) from the home-manager Sway config.
|
|
portable ? true,
|
|
}:
|
|
nixpkgs.lib.nixosSystem {
|
|
inherit system;
|
|
specialArgs = {
|
|
inherit
|
|
inputs
|
|
userRegistry
|
|
portable
|
|
;
|
|
};
|
|
modules =
|
|
baseModules
|
|
++ modules
|
|
++ [
|
|
{ _module.args.hostUsers = users; }
|
|
{
|
|
home-manager.extraSpecialArgs = { inherit inputs portable; };
|
|
home-manager.users = lib.mapAttrs (name: spec: {
|
|
imports = spec.homeModules;
|
|
_module.args.identity = userRegistry.${name} // {
|
|
username = name;
|
|
};
|
|
}) users;
|
|
}
|
|
];
|
|
};
|
|
|
|
# Shared scaffolding for every Darwin (macOS) host.
|
|
darwinBaseModules = [
|
|
commonModule
|
|
nix-homebrew.darwinModules.nix-homebrew
|
|
home-manager.darwinModules.home-manager
|
|
{
|
|
home-manager.useGlobalPkgs = true;
|
|
home-manager.useUserPackages = true;
|
|
# Back up pre-existing dotfiles (e.g. .zshrc) instead of aborting
|
|
# activation when home-manager would overwrite them.
|
|
home-manager.backupFileExtension = "backup";
|
|
}
|
|
];
|
|
|
|
# mkDarwinHost :: { system, username, modules, homeModules } -> darwinSystem
|
|
# Darwin counterpart of mkHost. macOS already owns the login user, so we
|
|
# only attach the platform and home-manager; no NixOS user module here.
|
|
# Stays single-user (macOS owns the account); identity is still sourced
|
|
# from the registry so the shared home modules behave as on NixOS.
|
|
mkDarwinHost =
|
|
{
|
|
system,
|
|
username,
|
|
modules,
|
|
homeModules,
|
|
}:
|
|
nix-darwin.lib.darwinSystem {
|
|
specialArgs = { inherit inputs username; };
|
|
modules =
|
|
darwinBaseModules
|
|
++ modules
|
|
++ [
|
|
{
|
|
nixpkgs.hostPlatform = system;
|
|
# macOS owns the account; point home-manager at its home dir.
|
|
users.users.${username}.home = "/Users/${username}";
|
|
home-manager.extraSpecialArgs = { inherit inputs; };
|
|
home-manager.users.${username} = {
|
|
imports = homeModules;
|
|
_module.args.identity = userRegistry.${username} // {
|
|
inherit username;
|
|
};
|
|
};
|
|
}
|
|
];
|
|
};
|
|
|
|
# Host table — declarative registry of every machine. To add a host:
|
|
# give it a name, its `system`, its `users` set (each user's home-module
|
|
# list, plus optional per-host-user bits like linger), and the system
|
|
# `modules`. mapAttrs below turns each entry into a nixosConfiguration of
|
|
# the same name. Per-user home configs compose ./home (the shared bundle)
|
|
# with any per-user modules (e.g. ./users/emmathorpe/work.nix).
|
|
hosts = {
|
|
lyrathorpe-mbp = {
|
|
system = "aarch64-linux";
|
|
modules = [
|
|
./hosts/MBP-Asahi/configuration.nix
|
|
./modules/laptop.nix
|
|
nixos-apple-silicon.nixosModules.default
|
|
./modules/sway.nix
|
|
];
|
|
users.lyrathorpe.homeModules = [
|
|
./home
|
|
./home/desktop.nix
|
|
];
|
|
};
|
|
|
|
lyrathorpe-t400 = {
|
|
system = "x86_64-linux";
|
|
modules = [
|
|
./hosts/T400/configuration.nix
|
|
./modules/laptop.nix
|
|
./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
|
|
./modules/sway.nix
|
|
];
|
|
users.lyrathorpe.homeModules = [
|
|
./home
|
|
./home/desktop.nix
|
|
];
|
|
};
|
|
|
|
lyrathorpe-macpro31 = {
|
|
system = "x86_64-linux";
|
|
portable = false;
|
|
modules = [
|
|
./hosts/MacPro31/configuration.nix
|
|
./modules/desktop.nix
|
|
./modules/ssh.nix
|
|
inputs.nixos-hardware.nixosModules.common-pc-ssd
|
|
inputs.nixos-hardware.nixosModules.common-cpu-intel
|
|
./modules/sway.nix
|
|
];
|
|
users.lyrathorpe.homeModules = [
|
|
./home
|
|
./home/desktop.nix
|
|
];
|
|
};
|
|
|
|
emmathorpe-edaas = {
|
|
system = "x86_64-linux";
|
|
modules = [
|
|
./hosts/EDaaS/configuration.nix
|
|
nixos-wsl.nixosModules.default
|
|
./modules/sway.nix
|
|
];
|
|
users.emmathorpe = {
|
|
homeModules = [
|
|
./home
|
|
./users/emmathorpe/work.nix
|
|
];
|
|
# Keep the systemd --user instance alive without a login session so
|
|
# the renovate-review home timer fires on schedule.
|
|
linger = true;
|
|
};
|
|
};
|
|
|
|
lyrathorpe-rpi5 = {
|
|
system = "aarch64-linux";
|
|
portable = false;
|
|
# Headless server: Docker host + nginx reverse proxy. No sway.nix
|
|
# (no desktop); the raspberry-pi-5 profile supplies kernel/firmware,
|
|
# ssh.nix adds key-only sshd.
|
|
modules = [
|
|
./hosts/RPi5/configuration.nix
|
|
inputs.nixos-hardware.nixosModules.raspberry-pi-5
|
|
./modules/ssh.nix
|
|
];
|
|
users.lyrathorpe.homeModules = [ ./home ];
|
|
};
|
|
};
|
|
|
|
# Darwin host table — macOS machines built via mkDarwinHost. The shared
|
|
# ./home bundle (shell, git, editor) is reused directly; the Linux-only
|
|
# desktop/sway modules are intentionally left out.
|
|
darwinHosts = {
|
|
lyrathorpe-mac = {
|
|
system = "aarch64-darwin";
|
|
username = "lyrathorpe";
|
|
modules = [
|
|
./hosts/Darwin/configuration.nix
|
|
];
|
|
homeModules = [
|
|
./home
|
|
];
|
|
};
|
|
};
|
|
in
|
|
{
|
|
# flake-parts modules: treefmt-nix wires `nix fmt` + a formatting check;
|
|
# git-hooks.nix wires the pre-commit check + devShell installation script.
|
|
imports = [
|
|
inputs.treefmt-nix.flakeModule
|
|
inputs.git-hooks.flakeModule
|
|
];
|
|
|
|
systems = [
|
|
"x86_64-linux"
|
|
"aarch64-linux"
|
|
"aarch64-darwin"
|
|
"x86_64-darwin"
|
|
];
|
|
|
|
# perSystem is evaluated once per entry in `systems`; `pkgs` is the
|
|
# nixpkgs instance for that system. Outputs here become per-system
|
|
# attrsets automatically (e.g. devShells.<system>.default).
|
|
perSystem =
|
|
{ config, pkgs, ... }:
|
|
{
|
|
# treefmt drives `nix fmt` and the formatting check below. nixfmt
|
|
# stays the .nix formatter (the tree is already nixfmt-formatted);
|
|
# shfmt covers shell and prettier covers markdown/yaml/json.
|
|
treefmt = {
|
|
projectRootFile = "flake.nix";
|
|
programs.nixfmt.enable = true;
|
|
programs.shfmt.enable = true;
|
|
programs.prettier.enable = true;
|
|
# Generated hardware-configuration.nix files are not hand-edited.
|
|
settings.global.excludes = [
|
|
"*/hardware-configuration.nix" # generated by nixos-generate-config
|
|
"flake.lock" # generated by `nix flake lock`
|
|
];
|
|
};
|
|
|
|
# Pre-commit hooks: format + lint gate run on commit. The same hooks
|
|
# are exposed as a flake check (pre-commit.check.enable defaults true).
|
|
pre-commit.settings = {
|
|
# Generated by nixos-generate-config; don't lint/reformat (treefmt
|
|
# excludes them too).
|
|
excludes = [ "hardware-configuration\\.nix$" ];
|
|
hooks = {
|
|
nixfmt-rfc-style.enable = true;
|
|
deadnix = {
|
|
enable = true;
|
|
# Unused module args ({config,lib,pkgs,...}) are normal; only
|
|
# flag genuinely dead bindings.
|
|
settings.noLambdaPatternNames = true;
|
|
};
|
|
statix.enable = true; # reads statix.toml (repeated_keys/empty_pattern disabled)
|
|
};
|
|
};
|
|
|
|
# treefmt-nix exposes its own `checks.treefmt`; alias it to
|
|
# `formatting` so the existing CI gate (.#checks.*.formatting) keeps
|
|
# working without churn.
|
|
checks.formatting = config.treefmt.build.check inputs.self;
|
|
|
|
# deadnix / statix lints as standalone flake checks so `nix flake
|
|
# check` flags dead code and antipatterns independently of pre-commit.
|
|
checks.deadnix = pkgs.runCommandLocal "check-deadnix" { nativeBuildInputs = [ pkgs.deadnix ]; } ''
|
|
deadnix --fail --no-lambda-pattern-names ${./.} && touch $out
|
|
'';
|
|
checks.statix = pkgs.runCommandLocal "check-statix" { nativeBuildInputs = [ pkgs.statix ]; } ''
|
|
statix check -c ${./.} ${./.} && touch $out
|
|
'';
|
|
|
|
# `nix develop` shell with the tooling needed to hack on this flake.
|
|
# shellHook installs the git pre-commit hooks into the working tree.
|
|
devShells.default = pkgs.mkShellNoCC {
|
|
packages = with pkgs; [
|
|
nixfmt
|
|
nil
|
|
git
|
|
deadnix
|
|
statix
|
|
treefmt
|
|
];
|
|
shellHook = config.pre-commit.installationScript;
|
|
};
|
|
};
|
|
|
|
# Realise the host tables: each entry becomes a {nixos,darwin}Configuration.
|
|
flake.nixosConfigurations = lib.mapAttrs (_name: mkHost) hosts;
|
|
flake.darwinConfigurations = lib.mapAttrs (_name: mkDarwinHost) darwinHosts;
|
|
|
|
# Reusable home modules, exported so this config can be consumed off these
|
|
# hosts -- by a standalone home-manager on a non-NixOS machine, or as an
|
|
# input to someone else's flake. `default` is the portable bundle
|
|
# (shell + git + editor + claude). Consumers must supply the module args
|
|
# these expect: `inputs` always; `identity` (see users/registry.nix) for
|
|
# git/desktop; `portable` for sway. `desktop`/`sway` additionally require
|
|
# a NixOS host that provides the Sway/Firefox binary -- they are not
|
|
# standalone-portable.
|
|
flake.homeModules = {
|
|
default = ./home;
|
|
shell = ./home/shell.nix;
|
|
git = ./home/git.nix;
|
|
editor = ./home/editor.nix;
|
|
claude = ./home/claude.nix;
|
|
desktop = ./home/desktop.nix;
|
|
sway = ./home/sway.nix;
|
|
};
|
|
|
|
# Standalone home-manager configurations: the portable bundle built for a
|
|
# machine NOT managed by this flake (`home-manager switch --flake
|
|
# .#"<user>@<system>"`). Only the portable subset is exposed; the desktop
|
|
# suite stays NixOS-only. homeConfigurations are not per-system, so the
|
|
# system is encoded in the attribute name, and home.username/homeDirectory
|
|
# are set explicitly (the NixOS module sets them automatically; standalone
|
|
# does not).
|
|
flake.homeConfigurations =
|
|
let
|
|
mkHome =
|
|
{
|
|
system,
|
|
name,
|
|
}:
|
|
home-manager.lib.homeManagerConfiguration {
|
|
pkgs = import nixpkgs {
|
|
inherit system overlays;
|
|
config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) unfreePackages;
|
|
};
|
|
extraSpecialArgs = {
|
|
inherit inputs;
|
|
portable = true;
|
|
identity = userRegistry.${name} // {
|
|
username = name;
|
|
};
|
|
};
|
|
modules = [
|
|
./home
|
|
{
|
|
home.username = name;
|
|
home.homeDirectory = "/home/${name}";
|
|
}
|
|
];
|
|
};
|
|
in
|
|
{
|
|
"lyrathorpe@x86_64-linux" = mkHome {
|
|
system = "x86_64-linux";
|
|
name = "lyrathorpe";
|
|
};
|
|
"lyrathorpe@aarch64-linux" = mkHome {
|
|
system = "aarch64-linux";
|
|
name = "lyrathorpe";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
}
|