Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/actions/setup-nix/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Setup Nix
description: >-
Install Nix and restore the devShell closure from the Actions cache.

inputs:
cache:
description: >-
Restore and save the Nix store through the Actions cache. On Depot
runners the Actions cache is Depot Cache.
default: 'true'
primary-key:
description: >-
Cache key base. Empty derives one from the runner OS and architecture
and the digest of flake.lock.
default: ''
github-token:
description: >-
Token the Nix daemon uses for `github:` flake inputs (access-tokens in
nix.conf). The workflow token cannot read private sibling repositories,
so pass a bot token when the flake does.
default: ${{ github.token }}
git-auth-token:
description: >-
Token written into a global git `insteadOf` rewrite, for `git+https`
flake inputs from private repositories. Empty disables the rewrite.
default: ''

outputs:
cache-hit:
description: >-
'true' when the Nix store came from any cache entry, including a
partial match restored through the prefix.
value: ${{ steps.cache.outputs.hit }}
cache-key:
description: Cache key the store was saved under.
value: ${{ steps.key.outputs.primary }}

runs:
using: composite
steps:
- name: Install Nix
uses: cachix/install-nix-action@75c75c31ebece974e48ebc36b4ad125bd97bd97b # 31.11.3
with:
github_access_token: ${{ inputs.github-token }}
# Pinned so every repository runs the same Nix. Bump this line
# deliberately; pinact and Renovate do not track it.
install_url: https://releases.nixos.org/nix/nix-2.35.4/install

# Nix's git fetcher shells out to git, so it honours this rewrite. The
# daemon's `access-tokens` from the installer covers `github:` inputs and
# does not cover `git+https` inputs.
- name: Authenticate git for private flake inputs
if: ${{ inputs.git-auth-token != '' }}
shell: bash
env:
GIT_AUTH_TOKEN: ${{ inputs.git-auth-token }}
run: >-
git config --global
url."https://${GIT_AUTH_TOKEN}@github.com/".insteadOf
"https://github.com/"

- name: Compute cache key
id: key
if: ${{ inputs.cache == 'true' }}
shell: bash
env:
PRIMARY_KEY: ${{ inputs.primary-key }}
PREFIX: ${{ format('nix-v3-{0}-{1}-', runner.os, runner.arch) }}
LOCK: ${{ hashFiles('**/flake.lock') }}
run: |
set -euo pipefail
primary="${PRIMARY_KEY:-${PREFIX}${LOCK}}"
printf 'primary=%s\nprefix=%s\n' "$primary" "$PREFIX" >> "$GITHUB_OUTPUT"

- name: Restore Nix store
id: cache
if: ${{ inputs.cache == 'true' }}
uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2
with:
primary-key: ${{ steps.key.outputs.primary }}
# A cache miss after a flake.lock change still restores the previous
# store, so only the changed paths download. The entry is then saved
# under the new primary key.
restore-prefixes-first-match: ${{ steps.key.outputs.prefix }}
paths: |
~/.cache/nix
/nix
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions: {}

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
setup-nix:
name: setup-nix
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Nix
id: nix
uses: $/.github/actions/setup-nix

- name: Check the devshell evaluates
run: nix develop .#dev --command nix --version

- name: Report cache
env:
CACHE_HIT: ${{ steps.nix.outputs.cache-hit }}
CACHE_KEY: ${{ steps.nix.outputs.cache-key }}
run: echo "cache-hit=$CACHE_HIT key=$CACHE_KEY"

actions-lint:
name: zizmor and pinact
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Nix
uses: $/.github/actions/setup-nix

- name: zizmor
run: nix develop .#dev --command zizmor --format=plain --min-confidence=medium .

# --verify resolves every SHA back to its tag, and --min-age holds back
# releases younger than three days. Both read the GitHub API, so pass a
# token to stay clear of the anonymous rate limit.
- name: pinact
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
mapfile -t files < <(find .github -name '*.yml' -o -name '*.yaml')
nix develop .#dev --command pinact run --check --verify --min-age 3 "${files[@]}"
5 changes: 4 additions & 1 deletion .github/workflows/update-flake-lock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ jobs:
- name: Install Nix
uses: DeterminateSystems/determinate-nix-action@bafaa638b9d5ec0e7e3ac1a7fc80453ef1fd265f # v3.20.0

# nixpkgs stays put: the locked revision is the last one whose zizmor
# builds on both architectures. Update it by hand, with `nix flake lock
# --update-input nixpkgs`, after checking that. See AGENTS.md.
- name: Update flake.lock
run: nix flake update
run: nix flake update flake-parts mattware treefmt-nix

- name: Validate flake
run: nix flake check
Expand Down
36 changes: 35 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This repo (`planetscale/nix-devshell`) exports shared flake-parts modules consum

```
flake.nix # exports flakeModules, owns the dev shell for this repo
.github/actions/setup-nix/ # composite action consumers use in CI: install Nix + Actions cache
modules/
base.nix # company-wide tools, sets systems via lib.mkDefault
go.nix # Go toolchain, parameterized by version (mattware + version args)
Expand Down Expand Up @@ -106,10 +107,43 @@ A module that only uses nixpkgs (no nix-devshell inputs needed) skips the first
- **`x86_64-darwin` is not supported** — base sets `lib.mkDefault` to the three supported systems; consumers can restrict further for Linux-only projects
- **`inputsFrom` does not propagate env vars or shellHook** — consumers needing shared config across multiple shells must use a `let mkDevShell = ...` pattern

## The setup-nix Action

`.github/actions/setup-nix` installs Nix and restores the Nix store through
`nix-community/cache-nix-action`. Consumers pin it by SHA:

```yaml
- uses: planetscale/nix-devshell/.github/actions/setup-nix@<sha> # vX.Y.Z
```

Constraints that must not regress:

- `magic-nix-cache-action` is no faster than no cache on Depot runners. Measured
with all three installers, a warm `nix develop` took 29 to 32 seconds, the
same as a cold one. With plain Nix every store upload fails with
`405 Method Not Allowed`, once per path. With Determinate Nix the action
reports `Subscribing to Determinate Nixd build events` and logs no uploads at
all. `cache-nix-action` restores in 4 to 9 seconds and leaves `nix develop`
at under a second, so use that instead.

Untested: whether `permissions: actions: write` changes the 405, and why
Determinate Nix logs no uploads. Do not restate either as fact.
- Every `run:` step in the composite takes its inputs through `env:`, never
through `${{ }}` interpolation, so zizmor's template-injection audit stays
quiet.
- All `uses:` in `.github/` stay SHA-pinned with a version comment.
`.github/workflows/ci.yml` enforces this with `pinact run --check --verify`.
- `pinact` and `zizmor` come from `devShells.dev`. zizmor only builds because
the `nixpkgs` input is pinned to a revision: 1.32.0 fails to build on both
architectures (aws-lc-sys wants a system AWS-LC that nixpkgs does not
provide). The pin is a full revision, not `nixos-unstable`, so `nix flake
update` leaves it alone. Move the pin forward only after checking that
zizmor builds and is in cache.nixos.org for both architectures.

## Dev Shell for This Repo

```bash
nix develop # enters the base shell (default = devShells.base)
nix develop .#dev # treefmt wrapper (nixfmt, statix, deadnix)
nix develop .#dev # treefmt wrapper (nixfmt, statix, deadnix) plus pinact and zizmor
nix flake check # runs treefmt check + validates all devShells
```
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,59 @@ leaves that file alone and the shell follows it as-is.
The imported version provides `devShells.zig` (zig, matching zls, ziglint, zigdoc).
- `nix` — Nix linting and formatting (nixfmt, statix, deadnix)
- `queryPath` — Query Path team module

## GitHub Actions

The repository ships a composite action at `.github/actions/setup-nix`. It
installs Nix and restores the devShell closure from the Actions cache. On Depot
runners the Actions cache is Depot Cache.

```yaml
jobs:
test:
runs-on: depot-ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@<sha> # v7.0.1
with:
persist-credentials: false

- uses: planetscale/nix-devshell/.github/actions/setup-nix@<sha> # v0.1.0

- run: nix develop .#dev --command just test
```

Pin the action by SHA and keep the version in a comment. Renovate and pinact
update both. A job that uses the action needs no other Nix setup.

### Inputs

| Input | Default | Description |
| --- | --- | --- |
| `cache` | `true` | Restore and save the Nix store through the Actions cache. |
| `primary-key` | derived | Cache key. The default uses the runner OS and architecture and the digest of `flake.lock`. |
| `github-token` | workflow token | Token for `github:` flake inputs. Pass a bot token when the flake reads private sibling repositories, because the workflow token cannot. |
| `git-auth-token` | empty | Token for a global git `insteadOf` rewrite. This covers `git+https` flake inputs from private repositories. |

### Outputs

| Output | Description |
| --- | --- |
| `cache-hit` | `true` when the Nix store came from a cache entry, including a partial match. |
| `cache-key` | Cache key the store was saved under. |

### Cache behaviour

The key contains the `flake.lock` digest. When the lock file changes, the action
restores the previous store through the key prefix and downloads only the paths
that changed. The job then saves a new entry under the new key.

The action installs one Nix version for every repository: `install_url` is
pinned to a release, and it does not follow the latest Nix. Bump that line when
you mean to move the org.

Measured on Depot runners, restoring one project devShell closure (3.3 GiB over
149 paths) takes 4 to 9 seconds. A cold download from cache.nixos.org takes 24 to
31 seconds. `DeterminateSystems/magic-nix-cache-action` gives no speedup on
Depot runners: the store uploads fail or never happen, so every job stays cold.
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@

devShells.dev = pkgs.mkShell {
inputsFrom = [ config.devShells.nix ];
packages = [ config.treefmt.build.wrapper ];
packages = [
config.treefmt.build.wrapper
# GitHub Actions auditing for this repo's .github/. Consumers do
# not need these, so they stay out of the shared modules. zizmor
# builds because the nixpkgs input is pinned to the last
# revision before 1.32.0.
pkgs.pinact
pkgs.zizmor
];
};

devShells.default = config.devShells.base;
Expand Down
Loading