A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
at loom 187 lines 5.7 kB view raw
1#!/bin/bash 2set -e 3 4# Usage function 5usage() { 6 echo "Usage: $0 <source-image> [target-image]" 7 echo "" 8 echo "Examples:" 9 echo " $0 ghcr.io/evanjarrett/myapp:latest" 10 echo " $0 ghcr.io/evanjarrett/myapp:latest atcr.io/evan.jarrett.net/myapp:latest" 11 echo "" 12 echo "If target-image is not specified, it will use atcr.io/<username>/<repo>:<tag>" 13 exit 1 14} 15 16# Check arguments 17if [ $# -lt 1 ]; then 18 usage 19fi 20 21SOURCE_IMAGE="$1" 22TARGET_IMAGE="${2:-}" 23 24# Parse source image to extract components 25# Format: [registry/]repository[:tag|@digest] 26parse_image_ref() { 27 local ref="$1" 28 local registry="" 29 local repository="" 30 local tag="latest" 31 32 # Remove digest if present (we'll fetch the manifest-list) 33 ref="${ref%@*}" 34 35 # Extract tag 36 if [[ "$ref" == *:* ]]; then 37 tag="${ref##*:}" 38 ref="${ref%:*}" 39 fi 40 41 # Extract registry and repository 42 if [[ "$ref" == */*/* ]]; then 43 # Has registry 44 registry="${ref%%/*}" 45 repository="${ref#*/}" 46 else 47 # No registry, assume Docker Hub 48 registry="docker.io" 49 repository="$ref" 50 fi 51 52 echo "$registry" "$repository" "$tag" 53} 54 55# Parse source image 56read -r SOURCE_REGISTRY SOURCE_REPO SOURCE_TAG <<< "$(parse_image_ref "$SOURCE_IMAGE")" 57 58# If no target specified, auto-generate it 59if [ -z "$TARGET_IMAGE" ]; then 60 # Extract just the repo name (last component) 61 REPO_NAME="${SOURCE_REPO##*/}" 62 # Try to extract username from source 63 if [[ "$SOURCE_REPO" == */* ]]; then 64 USERNAME="${SOURCE_REPO%/*}" 65 USERNAME="${USERNAME##*/}" 66 else 67 USERNAME="default" 68 fi 69 TARGET_IMAGE="atcr.io/${USERNAME}/${REPO_NAME}:${SOURCE_TAG}" 70fi 71 72# Parse target image 73read -r TARGET_REGISTRY TARGET_REPO TARGET_TAG <<< "$(parse_image_ref "$TARGET_IMAGE")" 74 75echo "=== Migrating multi-arch image ===" 76echo "Source: ${SOURCE_REGISTRY}/${SOURCE_REPO}:${SOURCE_TAG}" 77echo "Target: ${TARGET_REGISTRY}/${TARGET_REPO}:${TARGET_TAG}" 78echo "" 79 80# Full source reference 81SOURCE_REF="${SOURCE_REGISTRY}/${SOURCE_REPO}:${SOURCE_TAG}" 82TARGET_REF="${TARGET_REGISTRY}/${TARGET_REPO}:${TARGET_TAG}" 83 84# Fetch the manifest list 85echo ">>> Fetching manifest list from source..." 86MANIFEST_JSON=$(docker manifest inspect "$SOURCE_REF" 2>/dev/null || { 87 echo "Error: Failed to fetch manifest list. This may not be a multi-arch image." 88 echo "Trying as single-arch image..." 89 90 # Try pulling as single image 91 docker pull "$SOURCE_REF" 92 docker tag "$SOURCE_REF" "$TARGET_REF" 93 docker push "$TARGET_REF" 94 echo "=== Migration complete (single-arch) ===" 95 exit 0 96}) 97 98# Check if this is a manifest list 99MEDIA_TYPE=$(echo "$MANIFEST_JSON" | jq -r '.mediaType // .schemaVersion') 100if [[ ! "$MEDIA_TYPE" =~ "manifest.list" ]] && [[ ! "$MEDIA_TYPE" =~ "index" ]]; then 101 echo "Warning: Source appears to be a single-arch image, not a manifest list." 102 docker pull "$SOURCE_REF" 103 docker tag "$SOURCE_REF" "$TARGET_REF" 104 docker push "$TARGET_REF" 105 echo "=== Migration complete (single-arch) ===" 106 exit 0 107fi 108 109echo "Found multi-arch manifest list" 110echo "" 111 112# Extract platform information and digests (skip unknown/unknown for cosign artifacts) 113PLATFORMS=$(echo "$MANIFEST_JSON" | jq -r '.manifests[] | select(.platform.os != "unknown" and .platform.architecture != "unknown") | "\(.platform.os)|\(.platform.architecture)|\(.platform.variant // "")|\(.digest)"') 114 115# Arrays to store pushed images for manifest creation 116declare -a PUSHED_IMAGES 117declare -a PLATFORM_INFO 118 119# Process each platform 120while IFS='|' read -r os arch variant digest; do 121 # Create platform tag (e.g., "linux-amd64" or "linux-arm-v7") 122 PLATFORM_TAG="${os}-${arch}" 123 if [ -n "$variant" ]; then 124 PLATFORM_TAG="${PLATFORM_TAG}-${variant}" 125 fi 126 127 echo ">>> Processing ${os}/${arch}${variant:+/$variant}..." 128 echo " Digest: $digest" 129 130 # Pull by digest 131 echo " Pulling image..." 132 docker pull "${SOURCE_REGISTRY}/${SOURCE_REPO}@${digest}" 133 134 # Tag for target 135 TARGET_PLATFORM_REF="${TARGET_REGISTRY}/${TARGET_REPO}:${TARGET_TAG}-${PLATFORM_TAG}" 136 echo " Tagging as: ${TARGET_PLATFORM_REF}" 137 docker tag "${SOURCE_REGISTRY}/${SOURCE_REPO}@${digest}" "${TARGET_PLATFORM_REF}" 138 139 # Push platform-specific image 140 echo " Pushing..." 141 docker push "${TARGET_PLATFORM_REF}" 142 143 # Store for manifest creation 144 PUSHED_IMAGES+=("${TARGET_PLATFORM_REF}") 145 PLATFORM_INFO+=("${os}|${arch}|${variant}") 146 147 echo "" 148done <<< "$PLATFORMS" 149 150# Create multi-arch manifest 151echo ">>> Creating multi-arch manifest..." 152MANIFEST_CREATE_CMD="docker manifest create ${TARGET_REF}" 153for img in "${PUSHED_IMAGES[@]}"; do 154 MANIFEST_CREATE_CMD="${MANIFEST_CREATE_CMD} --amend ${img}" 155done 156 157eval "$MANIFEST_CREATE_CMD" 158echo "" 159 160# Annotate each platform 161echo ">>> Annotating manifest with platform information..." 162for i in "${!PUSHED_IMAGES[@]}"; do 163 IFS='|' read -r os arch variant <<< "${PLATFORM_INFO[$i]}" 164 165 ANNOTATE_CMD="docker manifest annotate ${TARGET_REF} ${PUSHED_IMAGES[$i]} --os ${os} --arch ${arch}" 166 if [ -n "$variant" ]; then 167 ANNOTATE_CMD="${ANNOTATE_CMD} --variant ${variant}" 168 fi 169 170 echo " Annotating ${os}/${arch}${variant:+/$variant}..." 171 eval "$ANNOTATE_CMD" 172done 173echo "" 174 175# Push the manifest list 176echo ">>> Pushing multi-arch manifest..." 177docker manifest push "${TARGET_REF}" 178echo "" 179 180echo "=== Migration complete! ===" 181echo "You can now pull: docker pull ${TARGET_REF}" 182echo "" 183echo "Migrated platforms:" 184for i in "${!PLATFORM_INFO[@]}"; do 185 IFS='|' read -r os arch variant <<< "${PLATFORM_INFO[$i]}" 186 echo " - ${os}/${arch}${variant:+/$variant}" 187done