Commit 37fea96
Changed files (2)
.forgejo
workflows
scripts
.forgejo/workflows/publish.yml
@@ -0,0 +1,53 @@
+name: Build and publish images
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - flake.nix
+ - flake.lock
+ - package.nix
+ - scripts/**
+ - .forgejo/workflows/publish.yml
+ workflow_dispatch:
+
+concurrency:
+ group: publish
+ cancel-in-progress: false
+
+jobs:
+ publish:
+ runs-on: nixos-latest-high
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ - name: Setup Nix
+ run: |
+ set -euo pipefail
+ echo 'extra-platforms = aarch64-linux' >> /etc/nix/nix.conf
+ echo "substituters = https://mirrors.ustc.edu.cn/nix-channels/store?priority=39 \
+ https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store?priority=39 \
+ https://cache.nixos.org?priority=40" >> /etc/nix/nix.conf
+ - name: Log in to the registries
+ env:
+ DOCKERHUB_USER: ${{ vars.DOCKERHUB_USER }}
+ DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
+ GOI_USER: ${{ vars.GOI_USER }}
+ GOI_TOKEN: ${{ secrets.GOI_TOKEN }}
+ run: |
+ set -euo pipefail
+ install -d -m 700 /run/ci
+ echo "Login to Docker Hub..."
+ echo "$DOCKERHUB_TOKEN" |
+ podman login --compat-auth-file /run/ci/config.json \
+ --username "$DOCKERHUB_USER" --password-stdin docker.io
+ echo "Login to Gate Of Infinity..."
+ echo "$GOI_TOKEN" |
+ podman login --compat-auth-file /run/ci/config.json \
+ --username "$GOI_USER" --password-stdin git.trin.one
+ - name: Build and publish
+ env:
+ REGISTRY_AUTH_FILE: /run/ci/config.json
+ DOCKER_CONFIG: /run/ci
+ run: ./scripts/publish.sh
scripts/publish.sh
@@ -0,0 +1,113 @@
+#!/usr/bin/env bash
+#
+# Build and publish one multi-arch index per registry, plus per-arch tags.
+# The caller must point REGISTRY_AUTH_FILE and DOCKER_CONFIG at the same file.
+#
+# Environment:
+# REGISTRIES space separated "registry/repository" list
+# DRY_RUN 1 to print commands instead of running them
+set -euo pipefail
+
+log() { printf 'publish: %s\n' "$*" >&2; }
+die() { printf 'publish: error: %s\n' "$*" >&2; exit 1; }
+
+run() {
+ if [ "${DRY_RUN:-0}" = 1 ]; then
+ printf 'publish: would run:' >&2
+ printf ' %s' "$@" >&2
+ printf '\n' >&2
+ else
+ "$@"
+ fi
+}
+
+repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
+flake=$repo_root
+
+registries=${REGISTRIES:-"docker.io/hpcesia/nix-act-image git.trin.one/goi/nix-act-image"}
+read -r -a registries <<<"$registries"
+[ "${#registries[@]}" -gt 0 ] || die "no registries to publish to"
+
+systems=(x86_64-linux aarch64-linux)
+channels=(version latest)
+declare -A architecture=([x86_64-linux]=amd64 [aarch64-linux]=arm64)
+declare -A package_of=([version]=default [latest]=latest)
+
+declare -A image_tag index_tag image_json
+for channel in "${channels[@]}"; do
+ for system in "${systems[@]}"; do
+ image_tag[$channel/$system]=$(nix eval --raw "$flake#packages.$system.${package_of[$channel]}.imageTag")
+ done
+done
+for channel in "${channels[@]}"; do
+ per_arch=${image_tag["$channel/${systems[0]}"]}
+ index_tag[$channel]=${per_arch%-"${systems[0]}"}
+ [ "${index_tag[$channel]}" != "$per_arch" ] ||
+ die "$channel tag $per_arch does not end in -${systems[0]}, cannot derive the index tag"
+ for system in "${systems[@]}"; do
+ per_arch=${image_tag["$channel/$system"]}
+ [ "${per_arch%-"$system"}" = "${index_tag[$channel]}" ] ||
+ die "$channel tag $per_arch does not match index tag ${index_tag[$channel]}"
+ done
+done
+
+log "resolving skopeo and crane from the flake inputs"
+tool() {
+ # crane installs into an output called "crane".
+ nix build --no-link --json --inputs-from "$flake" "$1" |
+ jq -r '.[0].outputs | .out // (to_entries[0].value)'
+}
+skopeo=$(tool 'nix2container#packages.x86_64-linux.skopeo-nix2container')/bin/skopeo
+crane=$(tool 'nixpkgs#crane')/bin/crane
+
+log "building images for: ${systems[*]}"
+installables=()
+for system in "${systems[@]}"; do
+ installables+=("$flake#packages.$system.default")
+done
+nix build --no-link "${installables[@]}"
+for system in "${systems[@]}"; do
+ image_json[$system]=$(nix eval --raw "$flake#packages.$system.default.outPath")
+done
+
+# Check architecture field before reaches registry.
+for system in "${systems[@]}"; do
+ actual=$("$skopeo" --insecure-policy inspect "nix:${image_json[$system]}" | jq -r '.Architecture')
+ [ "$actual" = "${architecture[$system]}" ] ||
+ die "image for $system reports architecture $actual, expected ${architecture[$system]}"
+ log "image for $system is ${architecture[$system]}"
+done
+
+for registry in "${registries[@]}"; do
+ for channel in "${channels[@]}"; do
+ for system in "${systems[@]}"; do
+ ref=$registry:${image_tag["$channel/$system"]}
+ log "pushing $ref"
+ run "$skopeo" --insecure-policy copy "nix:${image_json[$system]}" "docker://$ref"
+ done
+ done
+
+ for channel in "${channels[@]}"; do
+ manifests=()
+ for system in "${systems[@]}"; do
+ manifests+=(-m "$registry:${image_tag["$channel/$system"]}")
+ done
+ log "publishing $registry:${index_tag[$channel]}"
+ run "$crane" index append "${manifests[@]}" -t "$registry:${index_tag[$channel]}"
+ done
+done
+
+if [ "${DRY_RUN:-0}" = 1 ]; then
+ log "dry run, skipping the index checks"
+ exit 0
+fi
+
+for registry in "${registries[@]}"; do
+ for channel in "${channels[@]}"; do
+ platforms=$("$crane" manifest "$registry:${index_tag[$channel]}" |
+ jq -r '.manifests[].platform | "\(.os)/\(.architecture)"' | sort | paste -sd, -)
+ [ "$platforms" = "linux/amd64,linux/arm64" ] ||
+ die "$registry:${index_tag[$channel]} contains [$platforms], expected [linux/amd64,linux/arm64]"
+ log "$registry:${index_tag[$channel]} serves linux/amd64,linux/arm64"
+ done
+done