Skip to content

feat(cli): adds init/add-host scaffold with sops-nix safety gates #105

feat(cli): adds init/add-host scaffold with sops-nix safety gates

feat(cli): adds init/add-host scaffold with sops-nix safety gates #105

Workflow file for this run

name: PR Checks
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
lint-and-test:
runs-on: macos-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.13"
- name: Install dependencies
run: make install
- name: Lint
run: make lint
- name: Type check
run: make typecheck
- name: Test
run: make test
# Runs on every pull_request, unconditional (not gated like the
# `nix-darwin-switch` job below) — this plan's Key Decisions require every
# deliverable validated before merge, not after. No `needs:` on
# lint-and-test — runs in parallel. Inherits the workflow's default
# read-only GITHUB_TOKEN (no elevated permissions) since this job runs
# `nix build` against PR-supplied template content, including from forks.
#
# NOTE FOR WHOEVER MERGES THIS PR: this job needs to be added to the
# repository's branch-protection required-status-checks list manually via
# GitHub repo settings — a one-time change outside this workflow file's scope.
nix-integration:
if: github.event_name == 'pull_request'
runs-on: macos-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.13"
- name: Install dependencies
run: make install
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
- name: Install sops and age
run: brew install sops age
# Authenticates nix's own github: flake-input fetches (see
# tests/_scaffold_helpers.py's _nix_extra_access_tokens_args() for why
# this matters on a shared, unauthenticated-rate-limited runner IP
# pool). Default read-only GITHUB_TOKEN — no elevated permissions,
# matches this job's existing security posture against fork PRs.
- name: Nix build integration tests
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: make test-nix
# Job-level (not workflow-level `on.pull_request.paths`) scoping: a workflow-level
# path filter leaves a required status check permanently "Waiting for status to be
# reported" on any PR that doesn't touch the filtered paths. A job-level skip
# reports a real, non-blocking "success" status instead.
detect-changes:
if: github.event_name == 'pull_request'
runs-on: macos-latest
outputs:
vm-relevant: ${{ steps.check.outputs.vm-relevant }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Detect VM-relevant changes
id: check
run: |
git fetch origin "${{ github.base_ref }}" --depth=1
changed=$(git diff --name-only "origin/${{ github.base_ref }}" HEAD)
echo "$changed"
if echo "$changed" | grep -qE '^(src/mac2nix/generators/|src/mac2nix/templates/|src/mac2nix/vm/|tests/generators/|tests/vm/|tests/vm_fixtures\.py)'; then
echo "vm-relevant=true" >> "$GITHUB_OUTPUT"
else
echo "vm-relevant=false" >> "$GITHUB_OUTPUT"
fi
# Real, invasive (applies a genuine nix-darwin switch to the runner itself) and
# gated: only runs when detect-changes says the PR touches VM/generator-relevant
# paths, and even then waits for a maintainer's manual approval via the
# vm-validated environment's required reviewer.
#
# Nested macOS virtualization is categorically unsupported on GitHub-hosted
# runners (confirmed via GitHub's own docs — an Apple Virtualization Framework
# limitation, not a Tart-specific one: `tart run` exits immediately with no
# boot at all), so a Tart-VM-based apply-and-verify check — what this job
# originally did, and what `tests/vm/test_scaffold_vm.py` still does for local
# development on real Apple Silicon hardware — cannot run here. Instead this
# job applies the switch directly to the runner itself, which is safe only
# because the runner is already fully disposable (destroyed after the job).
# See tests/generators/test_scaffold_switch_native.py's own docstring for the
# full safety gating (skips unless GITHUB_ACTIONS=true, fails loudly rather
# than reusing a real key that shouldn't exist on a fresh runner).
#
# test_integration.py's TartVMManager-lifecycle/FileSystemComparator tests are
# deliberately NOT run here — they test the VM-control layer itself, which
# needs an actual VM boot regardless of what's being tested, so they can never
# run on any GitHub-hosted runner. They remain a local-only check
# (`make test-integration`, requires real Tart-capable hardware).
#
# NOTE FOR WHOEVER MERGES THIS PR: the `vm-validated` GitHub Environment and its
# required reviewer need to be configured once via repo settings — this is outside
# this workflow file's scope.
nix-darwin-switch:
needs: detect-changes
if: needs.detect-changes.outputs.vm-relevant == 'true'
runs-on: macos-latest
environment: vm-validated
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.13"
- name: Install dependencies
run: make install
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
# Installed via Nix, not Homebrew, so they survive the Homebrew removal
# below — add_host() needs sops/age-keygen on PATH independent of
# whatever happens to Homebrew during the switch.
- name: Install sops and age via nix
run: nix profile install nixpkgs#sops nixpkgs#age
# nix-darwin refuses to overwrite any /etc file it doesn't already manage
# and finds with unrecognized content — a safety guard against silently
# clobbering pre-existing system config, not a mac2nix-specific problem.
# DeterminateSystems/nix-installer-action itself writes nix.custom.conf,
# which nix-darwin's own Nix management then wants to own — exactly the
# scenario nix-darwin's own error message describes, with its own
# suggested fix (rename it out of the way first). A real user migrating a
# Mac that already has Nix installed independently of mac2nix would hit
# this identically and need the same one-time manual step.
- name: Move pre-existing /etc/nix/nix.custom.conf out of nix-darwin's way
run: |
if [ -f /etc/nix/nix.custom.conf ]; then
sudo mv /etc/nix/nix.custom.conf /etc/nix/nix.custom.conf.before-nix-darwin
fi
# nix-homebrew's autoMigrate adopts an existing Homebrew install, but
# this runner's pre-provisioned Homebrew has real content from dozens of
# formulae/casks across several non-default taps (Azure, AWS, etc.) —
# migration kept cascading through one conflict after another (an
# existing Library/Taps directory, then a formula whose originating tap
# had just been removed). Rather than keep chasing individual
# conflicts on an image this heavily provisioned, remove Homebrew
# entirely first: nix-homebrew then does a normal fresh install against
# an empty prefix, sidestepping autoMigrate's adoption path altogether.
# autoMigrate = true stays set in the scaffold template regardless —
# it's still the objectively correct setting for a real user's actual
# migration target, which (unlike this disposable runner) genuinely
# needs its existing Homebrew adopted rather than wiped.
- name: Remove pre-existing Homebrew installation
run: sudo rm -rf /opt/homebrew
# See the nix-integration job's own comment on GITHUB_TOKEN — same
# reasoning applies here, for this job's own github: flake-input
# fetches during the real switch (which sudo's env-stripping means
# can't just be set via NIX_CONFIG at the job level; the test itself
# threads this through as an explicit nix CLI argument instead).
- name: Real nix-darwin switch (applied to this disposable runner)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: make test-nix-darwin-switch