deploy: add nix-built runner image
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
0bffad9142435847e7c224a343a8843522723b78- Parents
-
cf7b55f - Tree
12e23d2
0bffad9
0bffad9142435847e7c224a343a8843522723b78cf7b55f
12e23d2| Status | File | + | - |
|---|---|---|---|
| A |
.github/workflows/runner-image.yml
|
75 | 0 |
| A |
deploy/runner-images/README.md
|
22 | 0 |
| A |
deploy/runner-images/flake.lock
|
27 | 0 |
| A |
deploy/runner-images/flake.nix
|
93 | 0 |
.github/workflows/runner-image.ymladded@@ -0,0 +1,75 @@ | |||
| 1 | +name: runner image | ||
| 2 | + | ||
| 3 | +on: | ||
| 4 | + workflow_dispatch: | ||
| 5 | + inputs: | ||
| 6 | + image: | ||
| 7 | + description: "Destination image name; blank publishes under this repo's GHCR namespace" | ||
| 8 | + required: false | ||
| 9 | + default: "" | ||
| 10 | + tag: | ||
| 11 | + description: "Destination image tag" | ||
| 12 | + required: true | ||
| 13 | + default: "1.0" | ||
| 14 | + | ||
| 15 | +permissions: | ||
| 16 | + contents: read | ||
| 17 | + id-token: write | ||
| 18 | + packages: write | ||
| 19 | + | ||
| 20 | +env: | ||
| 21 | + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | ||
| 22 | + | ||
| 23 | +jobs: | ||
| 24 | + build: | ||
| 25 | + runs-on: ubuntu-latest | ||
| 26 | + steps: | ||
| 27 | + - uses: actions/checkout@v4 | ||
| 28 | + | ||
| 29 | + - uses: DeterminateSystems/determinate-nix-action@v3 | ||
| 30 | + | ||
| 31 | + - name: Resolve destination image | ||
| 32 | + id: image | ||
| 33 | + env: | ||
| 34 | + INPUT_IMAGE: ${{ inputs.image }} | ||
| 35 | + INPUT_TAG: ${{ inputs.tag }} | ||
| 36 | + REPOSITORY: ${{ github.repository }} | ||
| 37 | + run: | | ||
| 38 | + set -euo pipefail | ||
| 39 | + image="$INPUT_IMAGE" | ||
| 40 | + if [ -z "$image" ]; then | ||
| 41 | + image="ghcr.io/${REPOSITORY,,}/runner-nix" | ||
| 42 | + fi | ||
| 43 | + case "$image" in | ||
| 44 | + *[!a-z0-9/:._-]* | "") | ||
| 45 | + echo "invalid image name: $image" >&2 | ||
| 46 | + exit 2 | ||
| 47 | + ;; | ||
| 48 | + esac | ||
| 49 | + case "$INPUT_TAG" in | ||
| 50 | + *[!A-Za-z0-9_.-]* | "") | ||
| 51 | + echo "invalid image tag: $INPUT_TAG" >&2 | ||
| 52 | + exit 2 | ||
| 53 | + ;; | ||
| 54 | + esac | ||
| 55 | + printf 'image=%s\n' "$image" >> "$GITHUB_OUTPUT" | ||
| 56 | + printf 'tag=%s\n' "$INPUT_TAG" >> "$GITHUB_OUTPUT" | ||
| 57 | + | ||
| 58 | + - name: Build image tarball | ||
| 59 | + run: nix build ./deploy/runner-images#runnerImage --print-build-logs | ||
| 60 | + | ||
| 61 | + - name: Load image | ||
| 62 | + run: docker load < result | ||
| 63 | + | ||
| 64 | + - name: Tag image | ||
| 65 | + run: docker tag ghcr.io/shithub/runner-nix:1.0 "${{ steps.image.outputs.image }}:${{ steps.image.outputs.tag }}" | ||
| 66 | + | ||
| 67 | + - name: Login to GHCR | ||
| 68 | + uses: docker/login-action@v3 | ||
| 69 | + with: | ||
| 70 | + registry: ghcr.io | ||
| 71 | + username: ${{ github.actor }} | ||
| 72 | + password: ${{ secrets.GITHUB_TOKEN }} | ||
| 73 | + | ||
| 74 | + - name: Push image | ||
| 75 | + run: docker push "${{ steps.image.outputs.image }}:${{ steps.image.outputs.tag }}" | ||
deploy/runner-images/README.mdadded@@ -0,0 +1,22 @@ | |||
| 1 | +# shithub runner image | ||
| 2 | + | ||
| 3 | +`flake.nix` builds the default S41d runner container image: | ||
| 4 | + | ||
| 5 | +```sh | ||
| 6 | +nix build ./deploy/runner-images#runnerImage | ||
| 7 | +docker load < result | ||
| 8 | +``` | ||
| 9 | + | ||
| 10 | +The image tag is `ghcr.io/shithub/runner-nix:1.0`, matching | ||
| 11 | +`internal/runner/config`'s default. `flake.lock` pins nixpkgs so the | ||
| 12 | +image input set is reviewable and repeatable. The image intentionally | ||
| 13 | +contains only the baseline tools needed for v1 `run:` steps and checkout | ||
| 14 | +plumbing: | ||
| 15 | +`bash`, coreutils, git, curl, CA certificates, gnupg, gcc, gnumake, | ||
| 16 | +archive tools, OpenSSH, and `shithub-shallow-checkout`. | ||
| 17 | + | ||
| 18 | +Publishing is handled by `.github/workflows/runner-image.yml`. That | ||
| 19 | +workflow is manual because the GHCR namespace may differ between the | ||
| 20 | +upstream project and self-hosted forks. Leave the image input blank to | ||
| 21 | +publish under the current repository's GHCR namespace, or override it | ||
| 22 | +with `ghcr.io/shithub/runner-nix` for the upstream package. | ||
deploy/runner-images/flake.lockadded@@ -0,0 +1,27 @@ | |||
| 1 | +{ | ||
| 2 | + "nodes": { | ||
| 3 | + "nixpkgs": { | ||
| 4 | + "locked": { | ||
| 5 | + "lastModified": 1778003029, | ||
| 6 | + "narHash": "sha256-q/nkKLDtHIyLjZpKhWk3cSK5IYsFqtMd6UtXF3ddjgA=", | ||
| 7 | + "owner": "NixOS", | ||
| 8 | + "repo": "nixpkgs", | ||
| 9 | + "rev": "0c88e1f2bdb93d5999019e99cb0e61e1fe2af4c5", | ||
| 10 | + "type": "github" | ||
| 11 | + }, | ||
| 12 | + "original": { | ||
| 13 | + "owner": "NixOS", | ||
| 14 | + "ref": "nixos-25.11", | ||
| 15 | + "repo": "nixpkgs", | ||
| 16 | + "type": "github" | ||
| 17 | + } | ||
| 18 | + }, | ||
| 19 | + "root": { | ||
| 20 | + "inputs": { | ||
| 21 | + "nixpkgs": "nixpkgs" | ||
| 22 | + } | ||
| 23 | + } | ||
| 24 | + }, | ||
| 25 | + "root": "root", | ||
| 26 | + "version": 7 | ||
| 27 | +} | ||
deploy/runner-images/flake.nixadded@@ -0,0 +1,93 @@ | |||
| 1 | +{ | ||
| 2 | + description = "shithub Actions default runner image"; | ||
| 3 | + | ||
| 4 | + inputs = { | ||
| 5 | + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; | ||
| 6 | + }; | ||
| 7 | + | ||
| 8 | + outputs = { self, nixpkgs }: | ||
| 9 | + let | ||
| 10 | + systems = [ "x86_64-linux" "aarch64-linux" ]; | ||
| 11 | + forAllSystems = nixpkgs.lib.genAttrs systems; | ||
| 12 | + in | ||
| 13 | + { | ||
| 14 | + packages = forAllSystems (system: | ||
| 15 | + let | ||
| 16 | + pkgs = import nixpkgs { inherit system; }; | ||
| 17 | + checkoutHelper = pkgs.writeShellApplication { | ||
| 18 | + name = "shithub-shallow-checkout"; | ||
| 19 | + runtimeInputs = [ | ||
| 20 | + pkgs.git | ||
| 21 | + pkgs.coreutils | ||
| 22 | + ]; | ||
| 23 | + text = '' | ||
| 24 | + set -euo pipefail | ||
| 25 | + | ||
| 26 | + if [ "$#" -ne 3 ]; then | ||
| 27 | + echo "usage: shithub-shallow-checkout <repo-url> <sha> <dest>" >&2 | ||
| 28 | + exit 2 | ||
| 29 | + fi | ||
| 30 | + | ||
| 31 | + repo_url="$1" | ||
| 32 | + sha="$2" | ||
| 33 | + dest="$3" | ||
| 34 | + | ||
| 35 | + mkdir -p "$dest" | ||
| 36 | + cd "$dest" | ||
| 37 | + git init | ||
| 38 | + git remote add origin "$repo_url" | ||
| 39 | + git fetch --depth=1 origin "$sha" | ||
| 40 | + git checkout --detach FETCH_HEAD | ||
| 41 | + ''; | ||
| 42 | + }; | ||
| 43 | + imageRoot = pkgs.buildEnv { | ||
| 44 | + name = "shithub-runner-nix-root"; | ||
| 45 | + paths = [ | ||
| 46 | + pkgs.bashInteractive | ||
| 47 | + pkgs.cacert | ||
| 48 | + pkgs.coreutils | ||
| 49 | + pkgs.curl | ||
| 50 | + pkgs.findutils | ||
| 51 | + pkgs.gcc | ||
| 52 | + pkgs.git | ||
| 53 | + pkgs.gnugrep | ||
| 54 | + pkgs.gnused | ||
| 55 | + pkgs.gnutar | ||
| 56 | + pkgs.gzip | ||
| 57 | + pkgs.gnupg | ||
| 58 | + pkgs.gnumake | ||
| 59 | + pkgs.openssh | ||
| 60 | + pkgs.xz | ||
| 61 | + checkoutHelper | ||
| 62 | + ]; | ||
| 63 | + pathsToLink = [ "/bin" "/etc" ]; | ||
| 64 | + }; | ||
| 65 | + in | ||
| 66 | + { | ||
| 67 | + runnerImage = pkgs.dockerTools.buildLayeredImage { | ||
| 68 | + name = "ghcr.io/shithub/runner-nix"; | ||
| 69 | + tag = "1.0"; | ||
| 70 | + contents = [ imageRoot ]; | ||
| 71 | + maxLayers = 80; | ||
| 72 | + config = { | ||
| 73 | + Cmd = [ "${pkgs.bashInteractive}/bin/bash" ]; | ||
| 74 | + Env = [ | ||
| 75 | + "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" | ||
| 76 | + "GIT_SSL_CAINFO=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" | ||
| 77 | + "PATH=/bin:${imageRoot}/bin" | ||
| 78 | + ]; | ||
| 79 | + WorkingDir = "/workspace"; | ||
| 80 | + Labels = { | ||
| 81 | + "org.opencontainers.image.title" = "shithub runner-nix"; | ||
| 82 | + "org.opencontainers.image.description" = "Default container image for shithub Actions run steps."; | ||
| 83 | + "org.opencontainers.image.source" = "https://github.com/tenseleyFlow/shithub"; | ||
| 84 | + "org.opencontainers.image.version" = "1.0"; | ||
| 85 | + "org.opencontainers.image.licenses" = "AGPL-3.0-or-later"; | ||
| 86 | + }; | ||
| 87 | + }; | ||
| 88 | + }; | ||
| 89 | + | ||
| 90 | + default = self.packages.${system}.runnerImage; | ||
| 91 | + }); | ||
| 92 | + }; | ||
| 93 | +} | ||