#!/usr/bin/env bash # # Fixed pipeline (do not reorder or skip steps): # 1. git add -A # 2. git commit (normal if staged changes exist, else --allow-empty with the same message) # 3. GIT_COMMIT = current HEAD (baked into Go binaries inside each image via Dockerfile ARG) # 4. docker login ghcr.io # 5. For each of server + client targets: per-arch buildx --push (linux/amd64, linux/arm64), then imagetools manifest # 6. git push GIT_REMOTE BRANCH # # Usage: ./ship.sh # Requires: git, docker (buildx), gh; remote "github"; gh token with write:packages for GHCR. # # Multi-arch: Intel/AMD 64-bit (linux/amd64) and ARM64 (linux/arm64), one manifest tag each for server and client. set -euo pipefail if [[ $# -lt 1 ]] || [[ -z "${1// }" ]]; then echo "usage: $0 " >&2 exit 1 fi COMMIT_MSG="$*" ROOT="$(cd "$(dirname "$0")" && pwd)" cd "$ROOT" # --- release targets (edit only here) --- GITHUB_OWNER="Leopere" REGISTRY_BASE="ghcr.io/leopere/better-argo-tunnels" TAG_SERVER="production" TAG_CLIENT="client-production" # Exactly two platforms → one multi-arch manifest per image (amd64 + arm64). PLATFORMS="linux/amd64,linux/arm64" GIT_REMOTE="github" # --- end --- for cmd in git docker gh; do command -v "$cmd" >/dev/null 2>&1 || { echo "ship: fatal: missing required command: $cmd" >&2 exit 1 } done if ! docker buildx version >/dev/null 2>&1; then echo "ship: fatal: docker buildx not available" >&2 exit 1 fi BRANCH="$(git branch --show-current)" if [[ -z "$BRANCH" ]]; then echo "ship: fatal: detached HEAD" >&2 exit 1 fi if ! git remote get-url "$GIT_REMOTE" >/dev/null 2>&1; then echo "ship: fatal: git remote '$GIT_REMOTE' is not configured" >&2 exit 1 fi echo "==> [1/6] git add -A" git add -A echo "==> [2/6] git commit" if git diff --cached --quiet; then git commit --allow-empty -m "$COMMIT_MSG" else git commit -m "$COMMIT_MSG" fi GIT_COMMIT="$(git rev-parse HEAD)" BA=(--build-arg "GIT_COMMIT=${GIT_COMMIT}") echo "==> GIT_COMMIT=${GIT_COMMIT}" echo "==> [3/6] docker login ghcr.io" if ! token="$(gh auth token)" || [[ -z "${token}" ]]; then echo "ship: fatal: gh auth token empty" >&2 exit 1 fi if ! printf '%s' "$token" | docker login ghcr.io -u "$GITHUB_OWNER" --password-stdin; then echo "ship: fatal: docker login ghcr.io failed (need: gh auth refresh -h github.com -s write:packages -s read:packages)" >&2 exit 1 fi pick_builder() { if docker buildx inspect orbstack >/dev/null 2>&1; then echo "orbstack" else echo "default" fi } platform_slug() { local p="${1// /}" p="${p#linux/}" echo "${p//\//-}" } # Per-arch push then single multi-arch manifest (orbstack/docker driver compatible). push_multi() { local target="$1" local final_tag="$2" local builder="$3" local base="${final_tag%:*}" local name="${final_tag##*:}" local arch_refs=() local p slug arch_tag IFS=',' read -ra plats <<< "$PLATFORMS" for p in "${plats[@]}"; do p="${p// /}" [[ -z "$p" ]] && continue slug="$(platform_slug "$p")" arch_tag="${base}:${name}-${slug}" echo "==> [4/6] docker buildx push ${target} ${p} -> ${arch_tag}" docker buildx build \ "${BA[@]}" \ --builder "$builder" \ --platform "$p" \ --target "$target" \ -t "$arch_tag" \ --push \ --provenance=false \ "$ROOT" arch_refs+=("$arch_tag") done echo "==> [5/6] imagetools manifest ${final_tag}" docker buildx imagetools create -t "$final_tag" "${arch_refs[@]}" } BUILDER="$(pick_builder)" echo "==> buildx builder: ${BUILDER}" SERVER_TAG="${REGISTRY_BASE}:${TAG_SERVER}" CLIENT_TAG="${REGISTRY_BASE}:${TAG_CLIENT}" push_multi server "$SERVER_TAG" "$BUILDER" push_multi client "$CLIENT_TAG" "$BUILDER" echo "==> [6/6] git push ${GIT_REMOTE} ${BRANCH}" git push "$GIT_REMOTE" "$BRANCH" echo "ship: ok commit=${GIT_COMMIT}" echo "ship: images ${SERVER_TAG} ${CLIENT_TAG} (multi-arch: ${PLATFORMS})"