Commit 8095276

HPCesia <me@hpcesia.com>
2026-10-18 14:06:25
Chunked image push
1 parent 1234046
Changed files (4)
.forgejo/workflows/publish.yml
@@ -48,6 +48,5 @@ jobs:
               --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
@@ -1,11 +1,17 @@
 #!/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.
+# The caller must point DOCKER_CONFIG at a directory holding a config.json
+# with the registry credentials.
 #
 # Environment:
-#   REGISTRIES  space separated "registry/repository" list
-#   DRY_RUN     1 to print commands instead of running them
+#   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; }
@@ -28,6 +34,11 @@ registries=${REGISTRIES:-"docker.io/hpcesia/nix-act-image git.trin.one/goi/nix-a
 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)
@@ -51,14 +62,14 @@ for channel in "${channels[@]}"; do
   done
 done
 
-log "resolving skopeo and crane from the flake inputs"
+log "resolving skopeo, crane and regctl 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
+regctl=$(tool 'nixpkgs#regclient')/bin/regctl
 
 log "building images for: ${systems[*]}"
 installables=()
@@ -70,7 +81,7 @@ for system in "${systems[@]}"; do
   image_json[$system]=$(nix eval --raw "$flake#packages.$system.default.outPath")
 done
 
-# Check architecture field before reaches registry.
+# 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]}" ] ||
@@ -78,12 +89,33 @@ for system in "${systems[@]}"; do
   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 "$skopeo" --insecure-policy copy "nix:${image_json[$system]}" "docker://$ref"
+      run "$regctl" image copy "ocidir://${layout[$system]}:image" "$ref"
     done
   done
 
package.nix
@@ -3,7 +3,7 @@
   pkgs,
   nix2container,
   name,
-  # NOTE: Skopeo didn't support multi arch image push
+  # Skopeo cannot assemble a multi-arch index, so a tag names one architecture.
   tag ? "nixos-${lib.version}-${pkgs.stdenv.system}",
 }: let
   fakeNss = pkgs.dockerTools.fakeNss.override {
README.md
@@ -28,9 +28,7 @@ Images are published to
 
 ## Usage
 
-This image uses [nlewo/nix2container](https://github.com/nlewo/nix2container)
-to build OCI images from Nix expressions. Check its documentation for more
-details.
+Built with [nlewo/nix2container](https://github.com/nlewo/nix2container).
 
 ```sh
 # Test image
@@ -43,8 +41,6 @@ podman run -it docker.io/hpcesia/nix-act-image:latest
 nix run .#packages.x86_64-linux.default.copyToRegistry
 ```
 
-You can find generated images in [docker.io](https://hub.docker.com/r/hpcesia/nix-act-image).
-
 ## Acknowledgements
 
 - [catthehacker/docker_images](https://github.com/catthehacker/docker_images)