Ctrl K
ComunidadGuías…

scripts/publish-m2images-r2.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# Publish a complete M2Image release to a Cloudflare R2-backed registry.

set -euo pipefail

unset CDPATH
script_dir=$(cd -- "$(dirname -- "$0")" && pwd -P)
repo_dir=$(cd -- "${script_dir}/.." && pwd -P)
manifest=${M2IMAGE_MANIFEST:-${repo_dir}/packaging/m2images.json}
dist_dir=${DIST_DIR:-${repo_dir}/dist/m2images}
bucket=${R2_BUCKET:-}
rclone_remote=${R2_REMOTE:-r2}
backend=${R2_BACKEND:-auto}
dry_run=0

usage() {
  cat <<'EOF'
Usage: ./scripts/publish-m2images-r2.sh --bucket <bucket> [options]

Options:
  --bucket <name>             Cloudflare R2 bucket (or R2_BUCKET)
  --backend <auto|rclone|wrangler>
                              Upload client (default: auto; prefers rclone)
  --rclone-remote <name>      rclone remote configured for R2 (default: r2)
  --manifest <path>           Alternate release manifest
  --dist-dir <path>           Package root (default: dist/m2images)
  --dry-run                   Validate and print uploads without changing R2
  -h, --help                  Show this help

The publisher requires every manifest alias for x86_64 and aarch64, including
its exact corresponding-source sibling archive. Source archives and M2Images
are uploaded before catalog.json, so consumers never discover a release whose
source bytes are absent. Configure rclone's S3 provider as Cloudflare for large
artifacts. Wrangler supports only files up to 315 MB and is a fallback.
EOF
}

info() { printf '[INFO] %s\n' "$*"; }
fail() { printf '[FAIL] %s\n' "$*" >&2; exit 1; }
require_command() { command -v "$1" >/dev/null 2>&1 || fail "$1 is required"; }

while [ "$#" -gt 0 ]; do
  case "$1" in
    -h|--help) usage; exit 0 ;;
    --bucket) [ "$#" -ge 2 ] || fail 'missing value for --bucket'; bucket=$2; shift 2 ;;
    --bucket=*) bucket=${1#--bucket=}; shift ;;
    --backend) [ "$#" -ge 2 ] || fail 'missing value for --backend'; backend=$2; shift 2 ;;
    --backend=*) backend=${1#--backend=}; shift ;;
    --rclone-remote) [ "$#" -ge 2 ] || fail 'missing value for --rclone-remote'; rclone_remote=$2; shift 2 ;;
    --rclone-remote=*) rclone_remote=${1#--rclone-remote=}; shift ;;
    --manifest) [ "$#" -ge 2 ] || fail 'missing value for --manifest'; manifest=$2; shift 2 ;;
    --manifest=*) manifest=${1#--manifest=}; shift ;;
    --dist-dir) [ "$#" -ge 2 ] || fail 'missing value for --dist-dir'; dist_dir=$2; shift 2 ;;
    --dist-dir=*) dist_dir=${1#--dist-dir=}; shift ;;
    --dry-run) dry_run=1; shift ;;
    *) fail "unknown argument: $1" ;;
  esac
done

[ -n "$bucket" ] || fail '--bucket or R2_BUCKET is required'
case "$bucket" in *[!a-z0-9.-]*|'') fail "invalid R2 bucket name: $bucket" ;; esac
case "$backend" in auto|rclone|wrangler) ;; *) fail "unsupported backend: $backend" ;; esac
require_command python3
require_command sha256sum
python3 "${script_dir}/m2image-manifest.py" --manifest "$manifest" validate >/dev/null

if [ "$backend" = auto ]; then
  if command -v rclone >/dev/null 2>&1; then backend=rclone
  elif command -v wrangler >/dev/null 2>&1; then backend=wrangler
  elif [ "$dry_run" -eq 1 ]; then backend=rclone
  else fail 'install rclone (recommended) or Wrangler v4'
  fi
fi
if [ "$dry_run" -eq 0 ]; then require_command "$backend"; fi
if [ "$backend" = wrangler ] && [ "$dry_run" -eq 0 ]; then
  wrangler_version=$(wrangler --version 2>/dev/null | sed -nE 's/.* ([0-9]+)\..*/\1/p' | head -n1)
  if [ -z "$wrangler_version" ] || [ "$wrangler_version" -lt 4 ]; then
    fail 'Wrangler v4 or newer is required'
  fi
fi

mapfile -t aliases < <(python3 "${script_dir}/m2image-manifest.py" --manifest "$manifest" aliases)
mapfile -t architectures < <(python3 "${script_dir}/m2image-manifest.py" --manifest "$manifest" architectures)

for architecture in "${architectures[@]}"; do
  checksum_file="${dist_dir}/${architecture}/SHA256SUMS"
  [ -f "$checksum_file" ] || fail "checksum file not found: $checksum_file"
  (cd "${dist_dir}/${architecture}" && sha256sum -c SHA256SUMS)
done

# Catalog construction is a fail-closed completeness check: it now requires the
# binary and source sibling for every alias/architecture and binds both hashes.
catalog_tmp=$(mktemp "${dist_dir}/.catalog.XXXXXX")
trap 'rm -f -- "$catalog_tmp"' EXIT
python3 "${script_dir}/m2image-manifest.py" --manifest "$manifest" catalog \
  --dist-dir "$dist_dir" --output "$catalog_tmp"

print_command() {
  printf '[DRY-RUN]'
  printf ' %q' "$@"
  printf '\n'
}

upload() {
  local source=$1
  local key=$2
  local content_type=$3
  local content_disposition=
  [ -f "$source" ] || fail "upload source not found: $source"

  if [ "$content_type" = application/zstd ]; then
    content_disposition="attachment; filename=\"$(basename -- "$key")\""
  fi

  if [ "$backend" = rclone ]; then
    command=(rclone copyto "$source" "${rclone_remote}:${bucket}/${key}")
    [ -z "$content_disposition" ] \
      || command+=(--metadata-set "content-disposition=${content_disposition}")
    [ "$dry_run" -eq 0 ] || command+=(--dry-run)
  else
    bytes=$(wc -c <"$source" | tr -d ' ')
    if [ "$bytes" -gt 315000000 ]; then
      fail "Wrangler cannot upload ${source} (${bytes} bytes, limit 315 MB); use --backend rclone"
    fi
    command=(wrangler r2 object put "${bucket}/${key}" --file "$source" --remote \
      --content-type "$content_type")
    [ -z "$content_disposition" ] \
      || command+=(--content-disposition "$content_disposition")
    [ "$content_type" != application/zstd ] || command+=(--cache-control 'public, max-age=31536000, immutable')
  fi

  if [ "$dry_run" -eq 1 ]; then print_command "${command[@]}"
  else "${command[@]}"
  fi
}

for alias in "${aliases[@]}"; do
  for architecture in "${architectures[@]}"; do
    source_package="${dist_dir}/${architecture}/${alias}.sources.tar.zst"
    source_key=$(python3 "${script_dir}/m2image-manifest.py" --manifest "$manifest" \
      source-registry-key "$alias" "$architecture")
    info "uploading exact source ${alias}/${architecture} -> r2://${bucket}/${source_key}"
    upload "$source_package" "$source_key" application/zstd

    package="${dist_dir}/${architecture}/${alias}.tar.zst"
    key=$(python3 "${script_dir}/m2image-manifest.py" --manifest "$manifest" \
      registry-key "$alias" "$architecture")
    info "uploading ${alias}/${architecture} -> r2://${bucket}/${key}"
    upload "$package" "$key" application/zstd
  done
done

catalog_key=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["registry"]["catalogKey"])' "$manifest")
info "publishing catalog last -> r2://${bucket}/${catalog_key}"
upload "$catalog_tmp" "$catalog_key" application/json

info "R2 publication complete (${backend})"