refactor(installer): path: flake so the installed host stays gitignored + writable repo #132
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Test Nix — evaluates the flake and builds the installer AND appliance ISOs | |
| # for every supported architecture. | |
| # | |
| # A `flake` job runs `nix flake check` (cheap, builds nothing) so Nix typos / | |
| # bad references / type errors surface fast; the matrix build jobs then realise | |
| # (or, drv-only, just instantiate) the images. | |
| # | |
| # Nix is installed natively on each runner (DeterminateSystems/nix-installer- | |
| # action) rather than run from the `nixos/nix` container — that container lacks | |
| # a standard glibc loader, so GitHub's bundled Node couldn't run there and JS | |
| # actions failed; installing on the host avoids that entirely and, crucially, | |
| # lets us cache the /nix/store across runs (nix-community/cache-nix-action, | |
| # backed by the GitHub Actions cache). Each arch builds on its own native | |
| # runner, so `make <kind>/iso` resolves to the runner's native | |
| # `builtins.currentSystem`. | |
| # | |
| # Triggers / what gets built per kind: | |
| # * push to main → drv-only: just instantiate each kind's | |
| # derivation (cheap validation, no image). A full ISO build on every main | |
| # commit is expensive and unnecessary; releases (tags) and manual runs | |
| # still produce real images. | |
| # * workflow_dispatch → always build both full ISOs (manual, | |
| # on-demand image build). | |
| # * pull_request → realise a kind's full ISO only when the | |
| # PR is ready-for-review (non-draft) AND its label (test-installer-iso / | |
| # test-appliance-iso) is applied; otherwise (draft, or no label) that kind | |
| # is just instantiated (.drv, cheap validation, no image). The `labeled` | |
| # trigger means adding the label kicks off the full build. | |
| # A tiny `plan` job computes the per-kind plan once and a short title fragment | |
| # so the build job's name stays readable. The build job always runs (drafts just | |
| # do derivations). Verification artifacts are short-lived (1 day). | |
| name: Test Nix | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| # `opened`/`reopened` cover a PR created/reopened already non-draft, | |
| # `ready_for_review` a draft promoted to ready, `labeled` so applying a | |
| # test-*-iso label starts the full build, and `synchronize` so pushing new | |
| # commits re-runs the build — re-evaluating the labels so a labelled kind | |
| # is re-built (not just its derivation) on every commit. | |
| types: [opened, reopened, ready_for_review, labeled, synchronize] | |
| workflow_dispatch: | |
| inputs: | |
| ref: | |
| description: "Git ref/commit to build (defaults to the selected branch)" | |
| required: false | |
| type: string | |
| # Cancel superseded runs on the same ref; a full ISO build is expensive so | |
| # don't waste runners on stale commits. | |
| concurrency: | |
| group: build-${{ github.ref }}-${{ github.event.inputs.ref }} | |
| cancel-in-progress: true | |
| env: | |
| # Force plain, greppable Nix output in CI logs. Nix's default animated | |
| # multi-line progress bar renders as unreadable ANSI redraw noise in the | |
| # GitHub Actions log viewer; `--log-format raw` prints one line per event and | |
| # `--print-build-logs` streams the actual builder output. The Makefile passes | |
| # $(NIX_OUTPUT_FLAGS) to every nix invocation (build / eval / flake check). | |
| NIX_OUTPUT_FLAGS: --log-format raw --print-build-logs | |
| jobs: | |
| # Flake evaluation — cheap, builds nothing. `nix flake check --no-build | |
| # --all-systems` evaluates every flake output (nixosConfigurations, packages, | |
| # …) for all declared systems (x86_64 + aarch64), catching typos / bad | |
| # references / type errors in seconds. The per-kind ISO derivations are | |
| # instantiated separately by the `Images` job below (its drv-only path), so | |
| # this covers the flake outputs that path doesn't touch. | |
| flake: | |
| name: Flake eval | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.event.inputs.ref }} | |
| # Install Nix natively + cache the /nix/store. The flake check builds | |
| # nothing, so it gets its own smaller cache (distinct prefix + gc cap) so | |
| # it doesn't share an entry with the large image builds. | |
| - name: Set up Nix | |
| uses: ./.github/actions/setup-nix | |
| with: | |
| cache-key-prefix: nix-flake | |
| gc-max-store-size: 5G | |
| - name: Flake check | |
| run: make check | |
| # Tiny pre-job that decides, per kind, whether to build the full ISO or just | |
| # instantiate the derivation, and assembles a short human title for the build | |
| # job. Doing this here (rather than inline in the build job's `name:`) keeps | |
| # that name a SHORT expression — `Build ${{ needs.plan.outputs.kinds }} | |
| # (${{ matrix.system }})` — so the raw "Matrix:" preview / a skipped job shows | |
| # something readable instead of a wall of inlined label checks. | |
| plan: | |
| name: Plan image targets | |
| runs-on: ubuntu-latest | |
| outputs: | |
| # "true"/"false" per kind: realise the full ISO, or (drv-only) instantiate. | |
| installer_full: ${{ steps.plan.outputs.installer_full }} | |
| appliance_full: ${{ steps.plan.outputs.appliance_full }} | |
| # Human title fragment, e.g. "installer & appliance ISO" or | |
| # "installer ISO & appliance DRV". | |
| kinds: ${{ steps.plan.outputs.kinds }} | |
| steps: | |
| - id: plan | |
| # Manual dispatch builds both full; push to main is drv-only; a PR | |
| # builds a kind's full ISO only when it is ready-for-review (non-draft) | |
| # AND its label is applied. push / draft PRs / unlabelled kinds → | |
| # drv-only. | |
| env: | |
| INSTALLER_FULL: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'test-installer-iso')) }} | |
| APPLIANCE_FULL: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'test-appliance-iso')) }} | |
| run: | | |
| isuf=$([ "$INSTALLER_FULL" = "true" ] && echo ISO || echo DRV) | |
| asuf=$([ "$APPLIANCE_FULL" = "true" ] && echo ISO || echo DRV) | |
| # Always join the two kinds with "&". Collapse to a shared suffix when | |
| # they match, otherwise spell each out. | |
| if [ "$isuf" = "$asuf" ]; then | |
| kinds="installer & appliance $isuf" | |
| else | |
| kinds="installer $isuf & appliance $asuf" | |
| fi | |
| { | |
| echo "installer_full=$INSTALLER_FULL" | |
| echo "appliance_full=$APPLIANCE_FULL" | |
| echo "kinds=$kinds" | |
| } >>"$GITHUB_OUTPUT" | |
| # Job key is "Images" so the matrix shows as "Matrix: Images". `needs: plan` | |
| # also means these matrix jobs are skipped if the plan job fails. | |
| Images: | |
| needs: plan | |
| # Short, readable name — the per-kind plan is computed by the `plan` job | |
| # above. e.g. "Build installer & appliance ISO (x86_64-linux)" or | |
| # "Build installer ISO & appliance DRV (aarch64-linux)". | |
| name: Build ${{ needs.plan.outputs.kinds }} (${{ matrix.system }}) | |
| runs-on: ${{ matrix.runner }} | |
| env: | |
| # Resolved per-kind plan from the `plan` job. Steps below (the build-plan | |
| # summary + the per-kind build/upload gating) branch on these. The | |
| # build-iso action instead receives the plan per kind via its `full` input. | |
| # | |
| # PR title/number + branch are passed straight to the build-iso action's | |
| # inputs (pr-title / pr-number / branch): the title + number are woven into | |
| # the image's pretty version name (coderBox.prTitle / coderBox.prNumber), | |
| # and the branch feeds the boot-screen "<short-sha>@<branch>" stamp | |
| # (github.head_ref is the real source branch on PRs — a PR checkout is a | |
| # detached HEAD — and empty otherwise so the Makefile falls back to the | |
| # local branch name; tag/main builds keep their plain names). | |
| INSTALLER_FULL: ${{ needs.plan.outputs.installer_full }} | |
| APPLIANCE_FULL: ${{ needs.plan.outputs.appliance_full }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - system: x86_64-linux | |
| runner: ubuntu-24.04 | |
| - system: aarch64-linux | |
| runner: ubuntu-24.04-arm | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| # Empty for push/PR (checks out the event ref); honored for manual | |
| # dispatch to build an arbitrary commit. | |
| ref: ${{ github.event.inputs.ref }} | |
| # This working tree is baked into the image at /etc/nixos-repo (via | |
| # box-turnkey.nix's self.outPath). actions/checkout defaults to | |
| # persisting the job's short-lived token as an http.*.extraheader in | |
| # .git/config; baked in, that dead token makes `git pull` on installed | |
| # boxes prompt for credentials. Don't persist it (the repo is public; | |
| # the initial fetch still authenticates, and `git pull` on the box | |
| # works anonymously). | |
| persist-credentials: false | |
| # Install Nix natively + cache the /nix/store (shared with the release | |
| # workflow so both build images the exact same way). | |
| - name: Set up Nix | |
| uses: ./.github/actions/setup-nix | |
| # Record the per-kind plan (full ISO vs drv only, in the job name too) in | |
| # the run summary, and stage one dist dir that both kinds' builds collect | |
| # into so the size/upload steps below have a single place to look. | |
| # INSTALLER_FULL / APPLIANCE_FULL come from the job-level env above. | |
| - name: Build plan | |
| id: build | |
| run: | | |
| plan() { [ "$1" = "true" ] && echo "full ISO" || echo "derivation only"; } | |
| { | |
| echo "### Build plan (${{ matrix.system }})" | |
| echo "- installer: $(plan "$INSTALLER_FULL")" | |
| echo "- appliance: $(plan "$APPLIANCE_FULL")" | |
| } >>"$GITHUB_STEP_SUMMARY" | |
| echo "dist=$(mktemp -d)" >>"$GITHUB_OUTPUT" | |
| # Build each kind through the shared action (same as the release workflow). | |
| # These are verification images, not shipped artifacts, so trade ISO size | |
| # for build speed via ISO_COMPRESSION: a low squashfs compression level is | |
| # far faster than the nixpkgs default (zstd level 19), which otherwise | |
| # dominates the build — and the rev baked into /etc forces that recompress | |
| # on every commit regardless of caching. Releases keep the slow default. | |
| # Both kinds collect into the one staged dist dir; a drv-only kind | |
| # (full=false) instantiates without adding an ISO. | |
| - name: Build installer | |
| uses: ./.github/actions/build-iso | |
| with: | |
| target: installer | |
| full: ${{ env.INSTALLER_FULL }} | |
| dist: ${{ steps.build.outputs.dist }} | |
| system: ${{ matrix.system }} | |
| iso-compression: zstd -Xcompression-level 3 | |
| pr-title: ${{ github.event.pull_request.title }} | |
| pr-number: ${{ github.event.pull_request.number }} | |
| branch: ${{ github.head_ref }} | |
| - name: Build appliance | |
| uses: ./.github/actions/build-iso | |
| with: | |
| target: appliance | |
| full: ${{ env.APPLIANCE_FULL }} | |
| dist: ${{ steps.build.outputs.dist }} | |
| system: ${{ matrix.system }} | |
| iso-compression: zstd -Xcompression-level 3 | |
| pr-title: ${{ github.event.pull_request.title }} | |
| pr-number: ${{ github.event.pull_request.number }} | |
| branch: ${{ github.head_ref }} | |
| # Per-arch ISO size metadata (written by the build-iso action, one | |
| # iso-sizes-<target>-<system>.tsv per built kind), kept tiny + short-lived. | |
| # The iso-table job merges every arch's files into one dir; the per-target | |
| # filenames keep them from clashing. | |
| - name: Upload ISO size metadata | |
| if: env.INSTALLER_FULL == 'true' || env.APPLIANCE_FULL == 'true' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: iso-meta-${{ matrix.system }} | |
| path: ${{ steps.build.outputs.dist }}/iso-sizes-*.tsv | |
| retention-days: 1 | |
| if-no-files-found: ignore | |
| # One artifact per kind, each bundling that kind's ISO with its .sha256 | |
| # sidecar (a single upload-artifact step uploads its matched files | |
| # CONCURRENTLY, so the multi-GB ISO and its checksum go up together). | |
| # Installer and appliance stay in SEPARATE artifacts so each kind can be | |
| # downloaded on its own. Each step runs only when its kind built full. | |
| - name: Upload installer ISO artifact | |
| if: env.INSTALLER_FULL == 'true' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: coder-box-installer-${{ matrix.system }} | |
| path: | | |
| ${{ steps.build.outputs.dist }}/coder-box-installer-*.iso | |
| ${{ steps.build.outputs.dist }}/coder-box-installer-*.iso.sha256 | |
| # Verification build; keep storage cost minimal. | |
| retention-days: 1 | |
| if-no-files-found: error | |
| - name: Upload appliance ISO artifact | |
| if: env.APPLIANCE_FULL == 'true' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: coder-box-appliance-${{ matrix.system }} | |
| path: | | |
| ${{ steps.build.outputs.dist }}/coder-box-appliance-*.iso | |
| ${{ steps.build.outputs.dist }}/coder-box-appliance-*.iso.sha256 | |
| retention-days: 1 | |
| if-no-files-found: error | |
| # Render / refresh a single sticky PR comment with a table of the ISO build | |
| # artifacts (kind × arch): exact size + a download link. Runs after the matrix | |
| # builds, only on pull_request and only when at least one kind built a full | |
| # ISO (drv-only validation runs produce no artifacts to list). Every rebuild | |
| # updates the SAME comment — matched by the hidden marker — instead of posting | |
| # a new one each time. | |
| iso-table: | |
| name: ISO artifact table | |
| needs: [plan, Images] | |
| if: >- | |
| github.event_name == 'pull_request' && | |
| (needs.plan.outputs.installer_full == 'true' || needs.plan.outputs.appliance_full == 'true') | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| pull-requests: write | |
| # listWorkflowRunArtifacts (download links) reads the run's artifacts. | |
| actions: read | |
| steps: | |
| # Checkout so the local iso-table composite action is available. | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| # Pull every arch's per-target size TSVs into one dir (filenames are | |
| # target+system-suffixed, so merge-multiple can't clash). | |
| - name: Download ISO size metadata | |
| uses: actions/download-artifact@v7 | |
| with: | |
| path: meta | |
| pattern: iso-meta-* | |
| merge-multiple: true | |
| # Render + upsert the sticky PR comment through the shared action (the | |
| # release workflow reuses the same renderer for its release body). | |
| - name: Upsert ISO artifact table comment | |
| uses: ./.github/actions/iso-table | |
| with: | |
| meta-dir: meta | |
| expiry-days: "1" | |
| sticky-comment: "true" |