diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..c6fe573 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,57 @@ +name: check + +# Evaluates every host configuration on every push and pull request. +# +# This is deliberately eval-only: it never builds a system closure, so it runs +# in a couple of minutes on a free runner. That is enough to catch the class of +# breakage this repo has actually suffered — a module edited into a syntax +# error, or a host that only fails on an architecture nobody rebuilds by hand. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: check-${{ github.ref }} + cancel-in-progress: true + +jobs: + eval: + name: evaluate host configurations + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v31 + with: + extra_nix_config: | + experimental-features = nix-command flakes + accept-flake-config = true + + # Evaluating a nix-darwin configuration does not require macOS — only + # building it does — so the Mac host is covered from this Linux runner. + - name: nix flake check (all systems, eval only) + run: nix flake check --all-systems --no-build + + - name: flake.lock is in sync with flake.nix + run: | + nix flake lock --no-update-lock-file + git diff --exit-code flake.lock + + format: + name: nixfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v31 + with: + extra_nix_config: | + experimental-features = nix-command flakes + + - name: check formatting + run: | + find . -name '*.nix' -not -path './.git/*' -print0 \ + | xargs -0 nix run .#formatter.x86_64-linux -- --check diff --git a/CLAUDE.md b/CLAUDE.md index 86d1ef4..069c44a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,20 +107,35 @@ See `docs/secrets.md` for full details. ## Flake Inputs -| Input | Pinned version | -| --------------------- | ---------------- | -| nixpkgs | nixos-25.11 | -| home-manager | release-25.11 | -| nix-darwin | nix-darwin-25.11 | -| sops-nix | latest | -| nix-topology | latest | -| plasma-manager | latest | -| catppuccin | latest | -| nix-vscode-extensions | latest | -| mac-app-util | latest | +| Input | Pinned version | Notes | +| --------------------- | ---------------- | ---------------------------------------------- | +| nixpkgs | nixos-25.11 | | +| nixpkgs-unstable | nixos-unstable | passed to the server as `pkgs-unstable` | +| home-manager | release-25.11 | | +| nix-darwin | nix-darwin-25.11 | | +| sops-nix | latest | | +| nix-topology | latest | | +| plasma-manager | latest | | +| nix-vscode-extensions | latest | | +| mac-app-util | latest | | +| pkgs-monorepo | latest | `ewanc26/pkgs` — maintenance tools | +| tgirlpkgs | latest | `tgirlcloud/pkgs` — provides `pds-gatekeeper` | Run `nix flake update` to update all inputs, or `flake-bump --update ` to bump selectively. +## Checks + +`nix flake check --all-systems` evaluates every host — `laptop`, `server`, +`server-arm` and `macmini` — without building any of them. Evaluating the +nix-darwin host does **not** require macOS, so this runs anywhere. + +CI (`.github/workflows/check.yml`) runs the same command on every push and pull +request, plus a `nixfmt` check. Run it locally before pushing: + +```bash +nix flake check --all-systems --no-build +``` + ## Running Tools Unless a tool is explicitly listed as a shell alias or known to be installed, diff --git a/flake.nix b/flake.nix index 0e3d998..9b33c27 100644 --- a/flake.nix +++ b/flake.nix @@ -145,6 +145,26 @@ { formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixfmt-rfc-style); + # Every host, exposed so `nix flake check` covers all of them. + # + # With --no-build (what CI runs) this is eval-only: every module is + # imported and every option type-checked, but nothing is built, so it + # finishes in minutes on a free runner. Drop --no-build to actually build + # the closures. Evaluating the nix-darwin host does not require macOS. + # + # This exists because the failure mode that bit this repo was a module + # edited into a syntax error and a host that only breaks on an + # architecture nobody rebuilds by hand — both invisible for a month + # because nothing ever evaluated the configs except a manual rebuild. + checks = { + x86_64-linux = { + laptop = self.nixosConfigurations.laptop.config.system.build.toplevel; + server = self.nixosConfigurations.server.config.system.build.toplevel; + }; + aarch64-linux.server-arm = self.nixosConfigurations.server-arm.config.system.build.toplevel; + aarch64-darwin.macmini = self.darwinConfigurations.macmini.system; + }; + # Render infrastructure diagrams: nix build .#topology.x86_64-linux.config.output topology.x86_64-linux = import nix-topology { pkgs = topologyPkgs; diff --git a/hooks/pre-commit b/hooks/pre-commit index 2415a54..9792aef 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -39,6 +39,29 @@ _staged_files '\.(json|jsonc|yaml|yml|css|js|html)$'; PRETTIER_FILES=("${_FILES[ FAILED=0 +# ── Nix parse check ─────────────────────────────────────────────────────────── +# Runs before nixfmt: a file that does not parse cannot be formatted either, and +# `set -e` would abort the hook mid-run with nixfmt's error rather than a clear +# message naming the file. This is the check that would have caught the +# modules/server/infra/* breakage, where a comment block replaced a module +# header and the server config stopped evaluating entirely. +if [ "${#NIX_FILES[@]}" -gt 0 ]; then + echo "→ nix: parsing staged .nix files..." + PARSE_FAILED=0 + for file in "${NIX_FILES[@]}"; do + if ! nix-instantiate --parse "$file" >/dev/null; then + echo " ✗ $file does not parse" + PARSE_FAILED=1 + fi + done + if [ "$PARSE_FAILED" -ne 0 ]; then + echo "" + echo "✗ syntax error in staged Nix files — commit blocked" + exit 1 + fi + echo " ✓ all .nix files parse" +fi + # ── Nix formatting ──────────────────────────────────────────────────────────── if [ "${#NIX_FILES[@]}" -gt 0 ]; then echo "→ nixfmt: formatting staged .nix files..."