Skip to content
Open
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
170 changes: 170 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Build ISO — builds the installer AND appliance ISOs for every supported
# architecture.
#
# Each arch builds on a native runner; the build itself runs inside the
# official `nixos/nix` container via `docker run`. We deliberately do NOT put
# the whole job in `container:` — the nixos/nix image lacks a standard glibc
# loader, so GitHub's bundled Node can't run there and every JS action
# (checkout, upload-artifact) fails with `exec .../node: no such file or
# directory`. So checkout/upload run on the host runner and only `make` runs in
# the container.
#
# One job per arch builds BOTH kinds in the SAME container (installer first,
# appliance second). `make <kind>/iso` resolves to the runner's native
# `builtins.currentSystem` and produces
# out/<kind>-iso/iso/coder-box-<kind>-<arch>-linux.iso (+ a .sha256 sidecar);
# the container /nix/store is ephemeral, so we dereference the ISOs into a host
# /dist volume and upload from there.
#
# Triggers / what gets built per kind:
# * push to main, workflow_dispatch → always build both full ISOs.
# * pull_request (ready, non-draft) → realise a kind's full ISO only when its
# label (test-installer-iso / test-appliance-iso) is applied; without the
# label that kind is just instantiated (.drv, cheap validation, no image).
# The `labeled` trigger means adding the label kicks off the full build.
# The job title reflects which kinds get a full build. Verification artifacts
# are short-lived (1 day).

name: Build ISO

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

jobs:
iso:
# Title varies with which kinds get a full build (drv-only kinds aren't
# named): "Installer + Appliance", "Installer", "Appliance", or
# "Derivations" when neither is labelled on a PR.
name: >-
${{ (
(github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'test-installer-iso'))
&& (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'test-appliance-iso'))
) && 'Installer + Appliance'
|| (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'test-installer-iso')) && 'Installer'
|| (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'test-appliance-iso')) && 'Appliance'
|| 'Derivations' }} (${{ matrix.system }})
# Skip draft PRs (opened/reopened/labeled fire for drafts too); always run
# for push and manual dispatch.
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ${{ matrix.runner }}
env:
# Per kind: realise the full ISO, or (drv-only) just instantiate. Non-PR
# events (push, manual dispatch) build both full; a PR builds a kind's
# full ISO only when its label is applied. Steps below branch on these.
INSTALLER_FULL: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'test-installer-iso') }}
APPLIANCE_FULL: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'test-appliance-iso') }}
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 }}

- name: Build ISOs
id: build
# INSTALLER_FULL / APPLIANCE_FULL come from the job-level env above.
run: |
# Host /dist volume, bind-mounted into the container, where full builds
# drop the finished ISO + checksum (the container's /nix/store is
# ephemeral, so the in-tree out/ symlink would dangle on the host).
dist="$(mktemp -d)"
echo "dist=$dist" >>"$GITHUB_OUTPUT"
# Both kinds run in ONE container; installer first, appliance later.
docker run --rm \
-v "$PWD":/work -w /work \
-v "$dist":/dist \
-e INSTALLER_FULL -e APPLIANCE_FULL \
nixos/nix:latest \
sh -euc '
# The Makefile builds via "nix build --impure" and needs flakes +
# nix-command, which the nixos/nix image does not enable by default.
export NIX_CONFIG="experimental-features = nix-command flakes"
# Repo is bind-mounted and owned by a different uid; allow git to
# read it so the Makefile can stamp the build rev.
git config --global --add safe.directory /work

# full build → realise the ISO and copy it (+ its .sha256 sidecar,
# written next to out/) into /dist; otherwise just instantiate the
# derivation. Bare target → native currentSystem (matrix pins the
# runner arch). gnumake provides "make"; git stamps the build rev.
build_kind() {
kind="$1"; full="$2"
if [ "$full" = "true" ]; then
nix-shell -p gnumake git --run "make $kind/iso"
cp -L "out/$kind-iso/iso"/*.iso /dist/
cp "out/coder-box-$kind"-*.iso.sha256 /dist/
else
nix-shell -p gnumake git --run "make $kind/drv"
fi
}

build_kind installer "$INSTALLER_FULL"
build_kind appliance "$APPLIANCE_FULL"
'
ls -lh "$dist"

- name: Upload installer ISO artifact
if: env.INSTALLER_FULL == 'true'
uses: actions/upload-artifact@v5
with:
name: coder-box-installer-${{ matrix.system }}
path: ${{ steps.build.outputs.dist }}/coder-box-installer-*.iso
# Verification build; keep storage cost minimal.
retention-days: 1
if-no-files-found: error

- name: Upload installer checksum artifact
if: env.INSTALLER_FULL == 'true'
uses: actions/upload-artifact@v5
with:
name: coder-box-installer-${{ matrix.system }}-sha256
path: ${{ steps.build.outputs.dist }}/coder-box-installer-*.iso.sha256
retention-days: 1
if-no-files-found: error

- name: Upload appliance ISO artifact
if: env.APPLIANCE_FULL == 'true'
uses: actions/upload-artifact@v5
with:
name: coder-box-appliance-${{ matrix.system }}
path: ${{ steps.build.outputs.dist }}/coder-box-appliance-*.iso
retention-days: 1
if-no-files-found: error

- name: Upload appliance checksum artifact
if: env.APPLIANCE_FULL == 'true'
uses: actions/upload-artifact@v5
with:
name: coder-box-appliance-${{ matrix.system }}-sha256
path: ${{ steps.build.outputs.dist }}/coder-box-appliance-*.iso.sha256
retention-days: 1
if-no-files-found: error
66 changes: 66 additions & 0 deletions .github/workflows/eval.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Eval Nix — fast validation that the flake still evaluates and the ISO
# derivations instantiate.
#
# Unlike Build ISO / Build and release ISO (which realise the full multi-GB
# images and only run for ready PRs / tags), this is cheap and runs on EVERY
# commit and pull request. Two checks, neither builds anything:
# 1. `nix flake check --no-build --all-systems` — evaluates every flake
# output (nixosConfigurations, packages, …) for all declared systems.
# 2. The Makefile's `*/drv` targets instantiate the installer + appliance ISO
# derivations (evaluate .drvPath, write the .drv, no realisation).
# A typo, bad reference, or type error in the Nix surfaces here in seconds
# instead of after a long image build.
#
# Both architectures are instantiated on a single x86_64 runner: evaluating an
# aarch64-linux derivation needs no aarch64 builder (only realisation would).
# The eval runs inside the nixos/nix container via `docker run` for the same
# reason as the other workflows — that image lacks a glibc loader for GitHub's
# bundled Node, so JS actions (checkout) stay on the host runner.

name: Eval Nix

on:
push:
branches: [main]
pull_request:
# Re-eval on every PR commit too (synchronize), plus open/reopen. This is
# the cheap per-commit gate, so unlike Build ISO we DO want synchronize.
types: [opened, reopened, synchronize]
workflow_dispatch:

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

jobs:
eval:
name: instantiate ISO derivations
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Eval flake + instantiate ISO derivations (in nixos/nix container)
run: |
docker run --rm \
-v "$PWD":/work -w /work \
nixos/nix:latest \
sh -euc '
export NIX_CONFIG="experimental-features = nix-command flakes"
git config --global --add safe.directory /work

# 1) Evaluate every flake output (nixosConfigurations, packages,
# etc.) without realising anything: --no-build skips building
# derivations, --all-systems checks outputs for every system
# the flake declares (x86_64 + aarch64). Catches eval errors in
# flake outputs not covered by the ISO drv targets below.
nix flake check --impure --no-build --all-systems

# 2) Instantiate the ISO derivations via the `*/drv` make targets
# (evaluate .drvPath, no build). Both arches per image; aarch64
# eval needs no aarch64 builder.
nix-shell -p gnumake git --run "
make installer/drv/x86_64-linux installer/drv/aarch64-linux \
appliance/drv/x86_64-linux appliance/drv/aarch64-linux
"
'
158 changes: 158 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Build and release ISO — builds the installer AND appliance ISOs for every
# supported architecture and publishes them as assets on a GitHub Release.
#
# Trigger either way:
# * push a tag matching v* (e.g. `git tag v1.2.0 && git push origin v1.2.0`)
# * run manually from the Actions tab (workflow_dispatch), supplying a tag
# and optionally a ref/commit to build.
#
# Like build.yml, each arch builds natively and the build runs inside the
# `nixos/nix` container via `docker run` (the whole job can't use `container:`
# because that image lacks a glibc loader for GitHub's bundled Node, which
# breaks every JS action). Bare `make <target>` resolves to the runner's native
# currentSystem; the resulting ISO is copied out of the ephemeral container
# store into a host mktemp dir. The release job gathers all ISOs and attaches
# them (with sha256 checksums) to the release.

name: Build and release ISO

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Release tag to create/publish (e.g. v1.2.0)"
required: true
type: string
ref:
description: "Git ref/commit to build (defaults to the tag/branch)"
required: false
type: string
prerelease:
description: "Mark the release as a pre-release"
required: false
default: false
type: boolean

# Only one release run per tag at a time.
concurrency:
group: release-${{ github.event.inputs.tag || github.ref }}
cancel-in-progress: false

jobs:
build:
name: ${{ matrix.kind }} (${{ matrix.system }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: true
matrix:
include:
- kind: Installer
target: installer
system: x86_64-linux
runner: ubuntu-24.04
- kind: Installer
target: installer
system: aarch64-linux
runner: ubuntu-24.04-arm
- kind: Appliance
target: appliance
system: x86_64-linux
runner: ubuntu-24.04
- kind: Appliance
target: appliance
system: aarch64-linux
runner: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v5
with:
# Empty for tag push (checks out the tag); honored for manual dispatch
# to release an arbitrary commit.
ref: ${{ github.event.inputs.ref }}

- name: Build ISO
id: build
env:
TARGET: ${{ matrix.target }}
run: |
# Host /dist volume, bind-mounted into the container, where the build
# drops the finished ISO (the container's /nix/store is ephemeral, so
# the in-tree out/ symlink would dangle on the host — we dereference
# the built ISO into /dist instead).
dist="$(mktemp -d)"
echo "dist=$dist" >>"$GITHUB_OUTPUT"
outlink="out/$TARGET-iso/iso"
docker run --rm \
-v "$PWD":/work -w /work \
-v "$dist":/dist \
-e TARGET -e outlink="$outlink" \
nixos/nix:latest \
sh -euc '
export NIX_CONFIG="experimental-features = nix-command flakes"
git config --global --add safe.directory /work
# gnumake provides "make"; git lets the Makefile stamp the rev. The
# ISO targets also emit out/<name>.iso.sha256 sidecar checksums so
# release consumers can verify the download. Bare target → native
# currentSystem (matrix pins the matching native runner).
nix-shell -p gnumake git --run "make $TARGET/iso"
# Dereference the build store symlink into the /dist volume,
# alongside the make-generated checksum sidecar.
cp -L "$outlink"/*.iso /dist/
cp out/*.iso.sha256 /dist/
'
ls -lh "$dist"

- name: Upload ISO artifact
uses: actions/upload-artifact@v5
with:
name: coder-box-${{ matrix.target }}-${{ matrix.system }}
path: ${{ steps.build.outputs.dist }}/*.iso
if-no-files-found: error

- name: Upload ISO checksum artifact
uses: actions/upload-artifact@v5
with:
name: coder-box-${{ matrix.target }}-${{ matrix.system }}-sha256
path: ${{ steps.build.outputs.dist }}/*.iso.sha256
if-no-files-found: error

release:
name: Publish GitHub Release
needs: build
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Determine release tag
id: tag
run: |
if [ -n "${{ github.event.inputs.tag }}" ]; then
tag="${{ github.event.inputs.tag }}"
else
tag="${GITHUB_REF#refs/tags/}"
fi
echo "tag=$tag" >>"$GITHUB_OUTPUT"
echo "Releasing tag: $tag"

- name: Download built ISOs
uses: actions/download-artifact@v5
with:
path: dist
merge-multiple: true

- name: List release assets
run: ls -lhR dist/

- name: Create / update GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.tag }}
generate_release_notes: true
prerelease: ${{ github.event.inputs.prerelease || false }}
files: |
dist/*.iso
dist/*.iso.sha256
fail_on_unmatched_files: true
Loading
Loading