main
  1{
  2  lib,
  3  pkgs,
  4  self,
  5  skopeo,
  6}: let
  7  systems = [
  8    "x86_64-linux"
  9    "aarch64-linux"
 10  ];
 11
 12  channels = {
 13    version = "default";
 14    latest = "latest";
 15  };
 16
 17  registries = [
 18    "docker.io/hpcesia/nix-act-image"
 19    "git.trin.one/goi/nix-act-image"
 20  ];
 21
 22  # git.trin.one's CDN rejects large request bodies, so its blobs go up in
 23  # chunks.
 24  chunkedHosts = ["git.trin.one"];
 25  chunkSize = 64 * 1024 * 1024;
 26  chunkMax = 8 * 1024 * 1024;
 27
 28  channelNames = lib.attrNames channels;
 29
 30  imageOf = system: self.packages.${system}.default;
 31  perArchTag = channel: system: self.packages.${system}.${channels.${channel}}.imageTag;
 32
 33  images = lib.genAttrs systems imageOf;
 34
 35  perArch = lib.listToAttrs (
 36    lib.concatMap (
 37      channel:
 38        map (system: lib.nameValuePair "${channel}/${system}" (perArchTag channel system)) systems
 39    )
 40    channelNames
 41  );
 42
 43  indexTag = channel: let
 44    stripped = map (system: lib.removeSuffix "-${system}" (perArchTag channel system)) systems;
 45  in
 46    assert lib.all (tag: tag == lib.head stripped) stripped;
 47      lib.head stripped;
 48
 49  indexTags = lib.genAttrs channelNames indexTag;
 50
 51  pairs = attrs:
 52    lib.concatStringsSep " " (lib.mapAttrsToList (name: value: "[${name}]=${value}") attrs);
 53
 54  data = ''
 55    systems=(${lib.concatStringsSep " " systems})
 56    channels=(${lib.concatStringsSep " " channelNames})
 57    registries=(${lib.concatStringsSep " " registries})
 58    chunked=(${lib.concatStringsSep " " chunkedHosts})
 59    declare -A images=(${pairs images})
 60    declare -A per_arch=(${pairs perArch})
 61    declare -A index_tag=(${pairs indexTags})
 62  '';
 63
 64  text = ''
 65    ${data}
 66
 67    log() { printf 'publish: %s\n' "$*" >&2; }
 68    run() {
 69      if [ "''${DRY_RUN:-0}" = 1 ]; then
 70        printf 'publish: would run: %s\n' "$*" >&2
 71      else
 72        "$@"
 73      fi
 74    }
 75
 76    workdir=$(mktemp -d)
 77    trap 'rm -rf "$workdir"' EXIT
 78
 79    if [ "''${#chunked[@]}" -gt 0 ]; then
 80      export REGCTL_CONFIG=$workdir/regctl.json
 81      for host in "''${chunked[@]}"; do
 82        log "chunking uploads to $host at ${toString chunkSize} bytes"
 83        run regctl registry set "$host" \
 84          --blob-chunk ${toString chunkSize} --blob-max ${toString chunkMax} \
 85          --skip-check
 86      done
 87    fi
 88
 89    # regctl cannot read the nix: transport, so each arch is staged as an OCI
 90    # layout first.
 91    for system in "''${systems[@]}"; do
 92      run skopeo --insecure-policy copy \
 93        "nix:''${images[$system]}" "oci:$workdir/$system:image"
 94    done
 95
 96    for registry in "''${registries[@]}"; do
 97      for channel in "''${channels[@]}"; do
 98        refs=()
 99        for system in "''${systems[@]}"; do
100          ref="$registry:''${per_arch[$channel/$system]}"
101          log "pushing $ref"
102          run regctl image copy "ocidir://$workdir/$system:image" "$ref"
103          refs+=(--ref "$ref")
104        done
105        log "publishing $registry:''${index_tag[$channel]}"
106        run regctl index create "$registry:''${index_tag[$channel]}" "''${refs[@]}"
107      done
108    done
109  '';
110in
111  pkgs.writeShellApplication {
112    name = "publish";
113    runtimeInputs = [
114      pkgs.coreutils
115      pkgs.regclient
116      skopeo
117    ];
118    inherit text;
119  }