167 lines
4.8 KiB
Bash
Executable File
167 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# Fixed pipeline (order is fixed):
|
||
# 1. git add -A
|
||
# 2. git commit (or --allow-empty if nothing staged)
|
||
# 3. GIT_COMMIT → Docker --build-arg (baked into binaries)
|
||
# 4. buildx --push per arch + imagetools manifest (server + client) — uses Docker’s existing ghcr auth (no docker login here)
|
||
# 5. git push — always
|
||
#
|
||
# Configuration (use your machine / repo — no hardcoded owner or registry):
|
||
# - Remote to parse for ghcr.io/owner/repo: git config ship.remote <name> (default: github)
|
||
# - Optional full registry base (no tag): git config ship.registryBase ghcr.io/org/image
|
||
# - Image tags: git config ship.tagServer / ship.tagClient
|
||
# - Platforms (comma-separated): git config ship.platforms
|
||
# - Buildx builder: git config ship.buildxBuilder (default: orbstack if present, else default)
|
||
#
|
||
# The push remote URL must be github.com (ssh or https). Owner/repo set GHCR path unless ship.registryBase is set.
|
||
#
|
||
# Usage: ./ship.sh <commit message>
|
||
|
||
set -euo pipefail
|
||
|
||
if [[ $# -lt 1 ]] || [[ -z "${1// }" ]]; then
|
||
echo "usage: $0 <commit message>" >&2
|
||
exit 1
|
||
fi
|
||
COMMIT_MSG="$*"
|
||
|
||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||
cd "$ROOT"
|
||
|
||
for cmd in git docker; 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
|
||
|
||
GIT_REMOTE="$(git config ship.remote 2>/dev/null)" || true
|
||
GIT_REMOTE="${GIT_REMOTE:-github}"
|
||
if ! REMOTE_URL="$(git remote get-url "$GIT_REMOTE" 2>/dev/null)"; then
|
||
echo "ship: fatal: git remote '$GIT_REMOTE' not found (set: git config ship.remote <name>)" >&2
|
||
exit 1
|
||
fi
|
||
|
||
REMOTE_OWNER=""
|
||
REMOTE_REPO=""
|
||
if [[ "$REMOTE_URL" =~ ^git@github\.com:([^/]+)/(.+)$ ]]; then
|
||
REMOTE_OWNER="${BASH_REMATCH[1]}"
|
||
REMOTE_REPO="${BASH_REMATCH[2]%.git}"
|
||
elif [[ "$REMOTE_URL" =~ ^https://github\.com/([^/]+)/([^/?#]+) ]]; then
|
||
REMOTE_OWNER="${BASH_REMATCH[1]}"
|
||
REMOTE_REPO="${BASH_REMATCH[2]%.git}"
|
||
else
|
||
echo "ship: fatal: remote '$GIT_REMOTE' URL is not github.com: $REMOTE_URL" >&2
|
||
exit 1
|
||
fi
|
||
|
||
REGISTRY_BASE="$(git config ship.registryBase 2>/dev/null)" || true
|
||
if [[ -z "$REGISTRY_BASE" ]]; then
|
||
o_lc="$(echo "$REMOTE_OWNER" | tr '[:upper:]' '[:lower:]')"
|
||
r_lc="$(echo "$REMOTE_REPO" | tr '[:upper:]' '[:lower:]')"
|
||
REGISTRY_BASE="ghcr.io/${o_lc}/${r_lc}"
|
||
fi
|
||
REGISTRY_BASE="${REGISTRY_BASE%/}"
|
||
|
||
TAG_SERVER="$(git config ship.tagServer 2>/dev/null)" || true
|
||
TAG_SERVER="${TAG_SERVER:-production}"
|
||
TAG_CLIENT="$(git config ship.tagClient 2>/dev/null)" || true
|
||
TAG_CLIENT="${TAG_CLIENT:-client-production}"
|
||
|
||
PLATFORMS="$(git config ship.platforms 2>/dev/null)" || true
|
||
PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}"
|
||
|
||
BUILDER_OVERRIDE="$(git config ship.buildxBuilder 2>/dev/null)" || true
|
||
|
||
echo "==> ship: remote=$GIT_REMOTE ($REMOTE_OWNER/$REMOTE_REPO)"
|
||
echo "==> ship: registry=$REGISTRY_BASE tags=$TAG_SERVER $TAG_CLIENT"
|
||
echo "==> ship: platforms=$PLATFORMS"
|
||
|
||
echo "==> [1/5] git add -A"
|
||
git add -A
|
||
|
||
echo "==> [2/5] 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}"
|
||
|
||
pick_builder() {
|
||
if [[ -n "$BUILDER_OVERRIDE" ]]; then
|
||
echo "$BUILDER_OVERRIDE"
|
||
return
|
||
fi
|
||
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//\//-}"
|
||
}
|
||
|
||
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 "==> [3/5] 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 "==> [4/5] 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}"
|