#!/bin/sh # sn-install - install the sn CLI from the platform container registry. # # Needs only `az` (logged in), `curl` and a sha256 tool. No Go, no Git LFS, no # clone of this repo, and no oras or docker: the registry API is plain HTTPS, # and an AAD token traded for a registry token is the only credential involved. # Safe to rerun; it always installs whatever tools/sn:latest currently points at. # # ./bin/sn-install install to /usr/local/bin # SN_INSTALL_DIR=~/.local/bin ./bin/sn-install install without sudo # # Your identity needs AcrPull on the registry, which infra-operators grants. # That is a data-plane role, so nothing here touches Azure Resource Manager - # an operator with no control-plane access to the shared subscription (not even # Reader, and so no `shared` entry in `az account list`) can still install. # Once installed, `sn upgrade` does the same job without this script. set -eu REGISTRY=acrsensenplatform.azurecr.io REPO=tools/sn TAG=latest MANIFEST_TYPE=application/vnd.oci.image.manifest.v1+json INSTALL_DIR=${SN_INSTALL_DIR:-/usr/local/bin} die() { echo "sn-install: $*" >&2; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required but not installed"; } need az need curl if command -v sha256sum >/dev/null 2>&1; then checksum() { sha256sum "$1" | cut -d' ' -f1; } elif command -v shasum >/dev/null 2>&1; then checksum() { shasum -a 256 "$1" | cut -d' ' -f1; } else die "need sha256sum or shasum to verify the download" fi echo "authenticating to $REGISTRY" # `az acr login` would resolve the registry through ARM first, which fails for # an identity holding only the data-plane pull role. Mint the ARM-audience AAD # token ourselves and trade it at the registry's own exchange endpoint instead: # same credential, no control-plane call. aad_token=$(az account get-access-token --resource https://management.azure.com/ \ --output tsv --query accessToken) || die "no Azure token - run 'az login' (add --allow-no-subscriptions if you have no subscriptions)" refresh_token=$(curl -sS -X POST "https://$REGISTRY/oauth2/exchange" \ -d "grant_type=access_token&service=$REGISTRY&access_token=$aad_token" | sed -n 's/.*"refresh_token":"\([^"]*\)".*/\1/p') [ -n "$refresh_token" ] || die "$REGISTRY rejected your Azure token - is your identity in infra-operators?" # ACR rejects Basic auth on /v2, so the refresh token has to be exchanged for # a bearer token. Scope it to pulling this one repository and nothing else. access_token=$(curl -sS -X POST "https://$REGISTRY/oauth2/token" \ -d "grant_type=refresh_token&service=$REGISTRY&scope=repository:$REPO:pull&refresh_token=$refresh_token" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p') [ -n "$access_token" ] || die "no pull token for $REPO - is your identity in infra-operators?" # The binary is the manifest's single layer, and a layer's digest is the # sha256 of its content, so the manifest doubles as the checksum. digest=$(curl -sS -H "Authorization: Bearer $access_token" -H "Accept: $MANIFEST_TYPE" \ "https://$REGISTRY/v2/$REPO/manifests/$TAG" | sed 's/.*"layers"//' | grep -o 'sha256:[0-9a-f]\{64\}' | head -1) [ -n "$digest" ] || die "no layer digest in the $REPO:$TAG manifest" tmp=$(mktemp) trap 'rm -f "$tmp"' EXIT echo "downloading ${digest#sha256:}" curl -fsSL -H "Authorization: Bearer $access_token" \ "https://$REGISTRY/v2/$REPO/blobs/$digest" -o "$tmp" || die "download failed" got=$(checksum "$tmp") [ "sha256:$got" = "$digest" ] || die "checksum mismatch: got $got, expected ${digest#sha256:}" mkdir -p "$INSTALL_DIR" 2>/dev/null || true if [ -w "$INSTALL_DIR" ]; then install -m 0755 "$tmp" "$INSTALL_DIR/sn" elif command -v sudo >/dev/null 2>&1; then echo "installing to $INSTALL_DIR (needs sudo)" sudo install -m 0755 "$tmp" "$INSTALL_DIR/sn" else die "$INSTALL_DIR is not writable and sudo is unavailable - set SN_INSTALL_DIR" fi echo "$INSTALL_DIR/sn" version case ":$PATH:" in *":$INSTALL_DIR:"*) ;; *) echo "note: $INSTALL_DIR is not on your PATH" ;; esac