Commit e211307
Changed files (5)
.forgejo/workflows/publish.yml
@@ -8,7 +8,7 @@ on:
- flake.nix
- flake.lock
- package.nix
- - scripts/**
+ - publish.nix
- .forgejo/workflows/publish.yml
workflow_dispatch:
@@ -49,4 +49,4 @@ jobs:
- name: Build and publish
env:
DOCKER_CONFIG: /run/ci
- run: ./scripts/publish.sh
+ run: nix run .#publish
scripts/publish.sh
@@ -1,145 +0,0 @@
-#!/usr/bin/env bash
-#
-# Build and publish one multi-arch index per registry, plus per-arch tags.
-# The caller must point DOCKER_CONFIG at a directory holding a config.json
-# with the registry credentials.
-#
-# Environment:
-# REGISTRIES space separated "registry/repository" list
-# CHUNKED_REGISTRIES space separated registry hosts whose CDN rejects
-# large request bodies, so their blobs are sent in
-# chunks
-# UPLOAD_CHUNK_SIZE bytes per chunked upload request
-# UPLOAD_CHUNK_MAX blob size above which an upload is chunked
-# 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"
-
-chunked_registries=${CHUNKED_REGISTRIES:-"git.trin.one"}
-read -r -a chunked_registries <<<"$chunked_registries"
-chunk_size=${UPLOAD_CHUNK_SIZE:-$((20 * 1024 * 1024))}
-chunk_max=${UPLOAD_CHUNK_MAX:-$((8 * 1024 * 1024))}
-
-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, crane and regctl from the flake inputs"
-tool() {
- 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
-regctl=$(tool 'nixpkgs#regclient')/bin/regctl
-
-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
-
-# Reject a wrong architecture label before it reaches the 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
-
-workdir=$(mktemp -d)
-trap 'rm -rf "$workdir"' EXIT
-
-# regctl chunks only when the host is configured for it
-if [ "${#chunked_registries[@]}" -gt 0 ]; then
- export REGCTL_CONFIG=$workdir/regctl.json
- for registry in "${chunked_registries[@]}"; do
- log "chunking uploads to $registry at $chunk_size bytes"
- run "$regctl" registry set "$registry" \
- --blob-chunk "$chunk_size" --blob-max "$chunk_max" --skip-check
- done
-fi
-
-# regctl cannot read the nix2container image file,
-# stages each architecture in an OCI layout first.
-declare -A layout
-for system in "${systems[@]}"; do
- layout[$system]=$workdir/oci-$system
- run "$skopeo" --insecure-policy copy "nix:${image_json[$system]}" "oci:${layout[$system]}:image"
-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 "$regctl" image copy "ocidir://${layout[$system]}:image" "$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
flake.nix
@@ -29,6 +29,10 @@
latest = self'.packages.default.override {
tag = "latest-${system}";
};
+ publish = pkgs.callPackage ./publish.nix {
+ self = inputs.self;
+ skopeo = inputs'.nix2container.packages.skopeo-nix2container;
+ };
};
};
});
publish.nix
@@ -0,0 +1,119 @@
+{
+ lib,
+ pkgs,
+ self,
+ skopeo,
+}: let
+ systems = [
+ "x86_64-linux"
+ "aarch64-linux"
+ ];
+
+ channels = {
+ version = "default";
+ latest = "latest";
+ };
+
+ registries = [
+ "docker.io/hpcesia/nix-act-image"
+ "git.trin.one/goi/nix-act-image"
+ ];
+
+ # git.trin.one's CDN rejects large request bodies, so its blobs go up in
+ # chunks.
+ chunkedHosts = ["git.trin.one"];
+ chunkSize = 64 * 1024 * 1024;
+ chunkMax = 8 * 1024 * 1024;
+
+ channelNames = lib.attrNames channels;
+
+ imageOf = system: self.packages.${system}.default;
+ perArchTag = channel: system: self.packages.${system}.${channels.${channel}}.imageTag;
+
+ images = lib.genAttrs systems imageOf;
+
+ perArch = lib.listToAttrs (
+ lib.concatMap (
+ channel:
+ map (system: lib.nameValuePair "${channel}/${system}" (perArchTag channel system)) systems
+ )
+ channelNames
+ );
+
+ indexTag = channel: let
+ stripped = map (system: lib.removeSuffix "-${system}" (perArchTag channel system)) systems;
+ in
+ assert lib.all (tag: tag == lib.head stripped) stripped;
+ lib.head stripped;
+
+ indexTags = lib.genAttrs channelNames indexTag;
+
+ pairs = attrs:
+ lib.concatStringsSep " " (lib.mapAttrsToList (name: value: "[${name}]=${value}") attrs);
+
+ data = ''
+ systems=(${lib.concatStringsSep " " systems})
+ channels=(${lib.concatStringsSep " " channelNames})
+ registries=(${lib.concatStringsSep " " registries})
+ chunked=(${lib.concatStringsSep " " chunkedHosts})
+ declare -A images=(${pairs images})
+ declare -A per_arch=(${pairs perArch})
+ declare -A index_tag=(${pairs indexTags})
+ '';
+
+ text = ''
+ ${data}
+
+ log() { printf 'publish: %s\n' "$*" >&2; }
+ run() {
+ if [ "''${DRY_RUN:-0}" = 1 ]; then
+ printf 'publish: would run: %s\n' "$*" >&2
+ else
+ "$@"
+ fi
+ }
+
+ workdir=$(mktemp -d)
+ trap 'rm -rf "$workdir"' EXIT
+
+ if [ "''${#chunked[@]}" -gt 0 ]; then
+ export REGCTL_CONFIG=$workdir/regctl.json
+ for host in "''${chunked[@]}"; do
+ log "chunking uploads to $host at ${toString chunkSize} bytes"
+ run regctl registry set "$host" \
+ --blob-chunk ${toString chunkSize} --blob-max ${toString chunkMax} \
+ --skip-check
+ done
+ fi
+
+ # regctl cannot read the nix: transport, so each arch is staged as an OCI
+ # layout first.
+ for system in "''${systems[@]}"; do
+ run skopeo --insecure-policy copy \
+ "nix:''${images[$system]}" "oci:$workdir/$system:image"
+ done
+
+ for registry in "''${registries[@]}"; do
+ for channel in "''${channels[@]}"; do
+ refs=()
+ for system in "''${systems[@]}"; do
+ ref="$registry:''${per_arch[$channel/$system]}"
+ log "pushing $ref"
+ run regctl image copy "ocidir://$workdir/$system:image" "$ref"
+ refs+=(--ref "$ref")
+ done
+ log "publishing $registry:''${index_tag[$channel]}"
+ run regctl index create "$registry:''${index_tag[$channel]}" "''${refs[@]}"
+ done
+ done
+ '';
+in
+ pkgs.writeShellApplication {
+ name = "publish";
+ runtimeInputs = [
+ pkgs.coreutils
+ pkgs.regclient
+ skopeo
+ ];
+ inherit text;
+ }
README.md
@@ -37,8 +37,11 @@ podman run -it docker.io/hpcesia/nix-act-image:latest
```
```sh
-# Push image
-nix run .#packages.x86_64-linux.default.copyToRegistry
+# Publish the indexes and per-arch tags to every registry
+nix run .#publish
+
+# Print the commands instead of running them
+DRY_RUN=1 nix run --impure .#publish
```
## Acknowledgements