Files
macos-tools/harbor/scripts/orbstack-harbor-mirrorctl.sh

343 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
LOCAL_DIR="${ORBSTACK_LOCAL_DIR:-$HOME/.orbstack/local}"
ENV_FILE="${HARBOR_MIRROR_ENV_FILE:-$LOCAL_DIR/harbor-mirror.env}"
PROXY_SRC="$ROOT_DIR/scripts/dockerhub-mirror-auth-proxy.py"
PROXY_DST="$LOCAL_DIR/dockerhub_mirror_auth_proxy.py"
PROXY_CONTAINER="${HARBOR_MIRROR_CONTAINER:-dockerhub-mirror-auth}"
PROXY_IMAGE="${HARBOR_MIRROR_PROXY_IMAGE:-python:3.11-alpine}"
PROXY_PORT="${HARBOR_MIRROR_PORT:-5002}"
PROXY_CONTAINER_PORT="${HARBOR_MIRROR_CONTAINER_PORT:-5001}"
HARBOR_NETWORK="${HARBOR_NETWORK:-harbor_core}"
MIRROR_URL="http://127.0.0.1:${PROXY_PORT}"
DOCKER_CONFIG_FILES=("$HOME/.orbstack/config/docker.json" "$HOME/.orbstack/docker")
log() { printf '[%s] %s\n' "$1" "$2"; }
info() { log INFO "$1"; }
ok() { log OK "$1"; }
warn() { log WARN "$1"; }
fail() { log ERROR "$1"; exit 1; }
usage() {
cat <<EOF
Usage: $(basename "$0") <command>
Commands:
init-env Create $ENV_FILE template
install Copy proxy, start container, configure OrbStack Docker
ensure Keep proxy container running; warn if Docker mirror is missing
start-proxy Recreate the local auth proxy container
configure-docker Add $MIRROR_URL to OrbStack Docker config files
restart-docker Restart OrbStack Docker
verify-docker Pull a standard Docker Hub image and show proxy evidence
verify-k3s Pull a standard Docker Hub image from k3s and show Harbor evidence
doctor Run status + Docker verification, then k3s verification when kubectl exists
status Show proxy, Docker mirror, and k3s registry state
logs Show proxy + Harbor nginx/registry evidence logs
install-launchd Install a user LaunchAgent that runs "ensure" every 5 minutes
EOF
}
load_env() {
if [[ -f "$ENV_FILE" ]]; then
# shellcheck disable=SC1090
set -a
. "$ENV_FILE"
set +a
fi
if [[ -z "${HARBOR_HOST:-}" ]]; then
HARBOR_HOST="$(docker inspect "$PROXY_CONTAINER" \
--format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null \
| sed -n 's/^HARBOR_HOST=//p' | head -1 || true)"
fi
HARBOR_PROJECT="${HARBOR_PROJECT:-dockerhub}"
HARBOR_VERIFY_TLS="${HARBOR_VERIFY_TLS:-false}"
HARBOR_NETWORK="${HARBOR_NETWORK:-harbor_core}"
PROXY_PORT="${HARBOR_MIRROR_PORT:-$PROXY_PORT}"
MIRROR_URL="http://127.0.0.1:${PROXY_PORT}"
if [[ -z "${HARBOR_HOST:-}" ]]; then
fail "HARBOR_HOST is not set. Run: $0 init-env, then edit $ENV_FILE"
fi
}
init_env() {
mkdir -p "$LOCAL_DIR"
if [[ -f "$ENV_FILE" ]]; then
warn "env file already exists: $ENV_FILE"
return 0
fi
local detected_host
detected_host="$(docker inspect "$PROXY_CONTAINER" \
--format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null \
| sed -n 's/^HARBOR_HOST=//p' | head -1 || true)"
detected_host="${detected_host:-REPLACE_WITH_HARBOR_HOST}"
cat >"$ENV_FILE" <<'EOF'
# Local-only settings for OrbStack -> Harbor mirror.
# Do not commit this file.
EOF
{
printf 'HARBOR_HOST=%s\n' "$detected_host"
cat <<'EOF'
HARBOR_PROJECT=dockerhub
HARBOR_VERIFY_TLS=false
HARBOR_NETWORK=harbor_core
HARBOR_MIRROR_PORT=5002
EOF
} >>"$ENV_FILE"
ok "created $ENV_FILE"
}
copy_proxy() {
mkdir -p "$LOCAL_DIR"
install -m 0644 "$PROXY_SRC" "$PROXY_DST"
ok "proxy script installed: $PROXY_DST"
}
start_proxy() {
load_env
copy_proxy
docker network inspect "$HARBOR_NETWORK" >/dev/null 2>&1 \
|| fail "Docker network not found: $HARBOR_NETWORK"
if docker ps -a --format '{{.Names}}' | grep -qx "$PROXY_CONTAINER"; then
docker rm -f "$PROXY_CONTAINER" >/dev/null
fi
docker run -d \
--name "$PROXY_CONTAINER" \
--restart unless-stopped \
--network "$HARBOR_NETWORK" \
-p "127.0.0.1:${PROXY_PORT}:${PROXY_CONTAINER_PORT}" \
-e "HARBOR_HOST=${HARBOR_HOST}" \
-e "HARBOR_PROJECT=${HARBOR_PROJECT}" \
-e "HARBOR_VERIFY_TLS=${HARBOR_VERIFY_TLS}" \
-e "LISTEN=0.0.0.0" \
-e "PORT=${PROXY_CONTAINER_PORT}" \
-v "${PROXY_DST}:/app/proxy.py:ro" \
"$PROXY_IMAGE" python /app/proxy.py >/dev/null
ok "proxy container running: ${PROXY_CONTAINER} on ${MIRROR_URL}"
}
merge_docker_config() {
local file="$1"
local mirror="$2"
mkdir -p "$(dirname "$file")"
if [[ -f "$file" ]]; then
cp "$file" "${file}.bak.harbor-mirror.$(date +%Y%m%d%H%M%S)"
fi
python3 - "$file" "$mirror" "$PROXY_PORT" <<'PY'
import json
import os
import sys
path, mirror, port = sys.argv[1], sys.argv[2], sys.argv[3]
try:
with open(path, "r", encoding="utf-8") as fh:
data = json.load(fh)
except FileNotFoundError:
data = {}
except json.JSONDecodeError as exc:
raise SystemExit(f"{path}: invalid JSON: {exc}")
def prepend_unique(values, item):
values = list(values or [])
return [item] + [v for v in values if v != item]
data["registry-mirrors"] = prepend_unique(data.get("registry-mirrors"), mirror)
data["insecure-registries"] = prepend_unique(data.get("insecure-registries"), f"127.0.0.1:{port}")
data["insecure-registries"] = prepend_unique(data.get("insecure-registries"), f"localhost:{port}")
tmp = f"{path}.tmp"
with open(tmp, "w", encoding="utf-8") as fh:
json.dump(data, fh, indent=2, ensure_ascii=False)
fh.write("\n")
os.replace(tmp, path)
PY
ok "updated Docker config: $file"
}
configure_docker() {
for file in "${DOCKER_CONFIG_FILES[@]}"; do
merge_docker_config "$file" "$MIRROR_URL"
done
warn "restart OrbStack Docker to apply the mirror: $0 restart-docker"
}
restart_docker() {
if [[ -x "$HOME/.orbstack/bin/orbctl" ]]; then
"$HOME/.orbstack/bin/orbctl" restart docker
else
fail "orbctl not found at $HOME/.orbstack/bin/orbctl"
fi
}
docker_mirror_configured() {
local docker_info
docker_info="$(docker info 2>/dev/null || true)"
printf '%s\n' "$docker_info" | grep -Fq "$MIRROR_URL/" \
|| printf '%s\n' "$docker_info" | grep -Fq "$MIRROR_URL"
}
verify_docker() {
load_env
docker ps --format '{{.Names}}' | grep -qx "$PROXY_CONTAINER" \
|| fail "proxy container is not running"
docker_mirror_configured || fail "Docker registry mirror is not configured: $MIRROR_URL"
local image="${1:-testcontainers/ryuk:0.12.0}"
info "pulling standard Docker Hub image through Docker mirror: $image"
docker pull "$image"
ok "Docker pull succeeded: $image"
docker logs --tail 80 "$PROXY_CONTAINER" 2>&1 | grep -E 'manifests|blobs' | tail -10 || true
}
verify_k3s() {
load_env
command -v kubectl >/dev/null || fail "kubectl not found"
local image="${1:-testcontainers/ryuk:0.11.0}"
local pod="k3s-harbor-pull-test-$(date +%s)"
trap 'kubectl delete pod "$pod" --ignore-not-found --wait=false >/dev/null 2>&1 || true' RETURN
info "pulling standard image from k3s: $image"
kubectl delete pod "$pod" --ignore-not-found >/dev/null 2>&1 || true
kubectl run "$pod" \
--image="$image" \
--image-pull-policy=Always \
--restart=Never \
--command -- /bin/sh -c 'sleep 30' >/dev/null
kubectl wait --for=condition=Ready "pod/$pod" --timeout=120s
kubectl describe pod "$pod" | sed -n '/Events:/,$p'
local repo="${image%%:*}"
if [[ "$repo" != */* ]]; then
repo="library/$repo"
fi
local encoded_repo="${repo//\//%2F}"
info "Harbor nginx evidence:"
docker logs --since 5m nginx 2>&1 \
| grep -E "dockerhub/${repo}|repository%3Adockerhub%2F${encoded_repo}%3Apull" \
| tail -20 || true
}
status() {
info "proxy container"
docker ps -a --filter "name=^/${PROXY_CONTAINER}$" \
--format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' || true
echo
info "Docker registry mirrors"
docker info 2>/dev/null | sed -n '/Registry Mirrors:/,/Live Restore Enabled:/p' || true
echo
info "k3s registries.yaml evidence"
kubectl get node >/dev/null 2>&1 && kubectl get node -o wide || true
}
logs() {
load_env
info "proxy logs"
docker logs --tail 80 "$PROXY_CONTAINER" 2>&1 || true
echo
info "Harbor nginx Docker Hub evidence"
docker logs --since 15m nginx 2>&1 | grep -E 'dockerhub/|containerd/v|Docker-Client' | tail -80 || true
echo
info "Harbor registry Docker Hub evidence"
docker logs --since 15m registry 2>&1 | grep -E 'dockerhub/' | tail -80 || true
}
ensure() {
load_env
copy_proxy
if ! docker ps --format '{{.Names}}' | grep -qx "$PROXY_CONTAINER"; then
warn "proxy container is not running; recreating"
start_proxy
fi
if ! docker_mirror_configured; then
warn "Docker mirror is missing from OrbStack config: $MIRROR_URL"
fi
ok "ensure completed"
}
doctor() {
status
verify_docker
if command -v kubectl >/dev/null && kubectl get node >/dev/null 2>&1; then
verify_k3s
else
warn "kubectl/k3s unavailable; skipped k3s verification"
fi
}
install_launchd() {
local plist="$HOME/Library/LaunchAgents/com.orbstack.harbor-mirror.ensure.plist"
mkdir -p "$HOME/Library/LaunchAgents" "$ROOT_DIR/logs"
cat >"$plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.orbstack.harbor-mirror.ensure</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>${ROOT_DIR}/scripts/orbstack-harbor-mirrorctl.sh</string>
<string>ensure</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>${ROOT_DIR}/logs/orbstack-harbor-mirror-stdout.log</string>
<key>StandardErrorPath</key>
<string>${ROOT_DIR}/logs/orbstack-harbor-mirror-stderr.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
</dict>
</plist>
EOF
launchctl unload "$plist" >/dev/null 2>&1 || true
launchctl load "$plist"
ok "LaunchAgent installed: $plist"
}
install_all() {
load_env
start_proxy
configure_docker
restart_docker
verify_docker
}
cmd="${1:-}"
case "$cmd" in
init-env) init_env ;;
install) install_all ;;
ensure) ensure ;;
start-proxy) start_proxy ;;
configure-docker) configure_docker ;;
restart-docker) restart_docker ;;
verify-docker) shift; verify_docker "$@" ;;
verify-k3s) shift; verify_k3s "$@" ;;
doctor) doctor ;;
status) status ;;
logs) logs ;;
install-launchd) install_launchd ;;
-h|--help|help|"") usage ;;
*) usage; exit 2 ;;
esac