#!/usr/bin/env bash set -Eeuo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" ENV_FILE="${HA_ADDON_ENV_FILE:-$SCRIPT_DIR/.env}" [[ -f "$ENV_FILE" ]] || { echo "Missing $ENV_FILE. Copy $SCRIPT_DIR/.env.example to $SCRIPT_DIR/.env and edit it." >&2 exit 1 } set -a # shellcheck disable=SC1090 source "$ENV_FILE" set +a required=(REGISTRY IMAGE_NAME DISTRO RUST_VERSION BUILD_PLATFORMS BUILDER HA_REPO_URL ADDON_DIR INTEGRATION_DOMAIN) for name in "${required[@]}"; do [[ -n "${!name:-}" ]] || { echo "Missing $name in $ENV_FILE" >&2; exit 1; } done case "$DISTRO" in trixie|alpine) ;; *) echo "DISTRO must be one of: trixie, alpine" >&2; exit 2 ;; esac ACTION="${1:-push}" VERSION="$(awk -F '"' '/^version = "/ {print $2; exit}' "$PROJECT_ROOT/Cargo.toml")" [[ -n "$VERSION" ]] || { echo "Cannot determine version from Cargo.toml" >&2; exit 1; } IMAGE_REPO="${REGISTRY%/}/${IMAGE_NAME#/}" IMAGE_TAG="${IMAGE_REPO}:${VERSION}" CONFIG="$SCRIPT_DIR/repository/$ADDON_DIR/config.yaml" LOCKFILE="$PROJECT_ROOT/Cargo.lock" INTEGRATION_MANIFEST="$SCRIPT_DIR/home-assistant/custom_components/$INTEGRATION_DOMAIN/manifest.json" REPOSITORY_CONFIG="$SCRIPT_DIR/repository/repository.yaml" [[ -f "$CONFIG" ]] || { echo "Missing add-on config: $CONFIG" >&2; exit 1; } [[ -f "$INTEGRATION_MANIFEST" ]] || { echo "Missing integration manifest: $INTEGRATION_MANIFEST" >&2; exit 1; } [[ -f "$REPOSITORY_CONFIG" ]] || { echo "Missing repository config: $REPOSITORY_CONFIG" >&2; exit 1; } render_metadata() { command -v python3 >/dev/null 2>&1 || { echo "python3 is required" >&2; exit 1; } python3 - "$CONFIG" "$LOCKFILE" "$INTEGRATION_MANIFEST" "$REPOSITORY_CONFIG" "$VERSION" "$IMAGE_REPO" "$HA_REPO_URL" <<'PY' from pathlib import Path import re import sys config = Path(sys.argv[1]) lockfile = Path(sys.argv[2]) manifest = Path(sys.argv[3]) repository = Path(sys.argv[4]) version = sys.argv[5] image = sys.argv[6] repo_url = sys.argv[7] lines = config.read_text(encoding="utf-8").splitlines() out = [] for line in lines: if line.startswith("version:"): out.append(f'version: "{version}"') elif line.startswith("image:"): out.append(f'image: "{image}"') elif line.startswith("url:"): out.append(f'url: "{repo_url}"') else: out.append(line) config.write_text("\n".join(out) + "\n", encoding="utf-8") lock = lockfile.read_text(encoding="utf-8") pattern = r'(\[\[package\]\]\nname = "gree-controller"\nversion = ")[^"]+("\n)' updated, count = re.subn(pattern, rf'\g<1>{version}\g<2>', lock, count=1) if count != 1: raise SystemExit("Could not find gree-controller package version in Cargo.lock") lockfile.write_text(updated, encoding="utf-8") import json data = json.loads(manifest.read_text(encoding="utf-8")) data["version"] = version manifest.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") repo_lines = repository.read_text(encoding="utf-8").splitlines() repo_out = [f"url: {repo_url}" if line.startswith("url:") else line for line in repo_lines] repository.write_text("\n".join(repo_out) + "\n", encoding="utf-8") PY printf 'Synced version %s; image %s\n' "$VERSION" "$IMAGE_REPO" } ensure_builder() { command -v docker >/dev/null 2>&1 || { echo "docker is required" >&2; exit 1; } docker buildx version >/dev/null 2>&1 || { echo "docker buildx is required" >&2; exit 1; } if docker buildx inspect "$BUILDER" >/dev/null 2>&1; then docker buildx use "$BUILDER" else args=(create --name "$BUILDER" --driver docker-container --use) [[ -z "${BUILDER_CONFIG:-}" ]] || args+=(--config "$BUILDER_CONFIG") docker buildx "${args[@]}" >/dev/null fi docker buildx inspect --bootstrap >/dev/null } build_push() { ensure_builder docker buildx build \ --platform "$BUILD_PLATFORMS" \ -f "$SCRIPT_DIR/Dockerfile" \ --target "runtime-${DISTRO}" \ --build-arg "BUILD_VERSION=$VERSION" \ --build-arg "RUST_VERSION=$RUST_VERSION" \ -t "$IMAGE_TAG" \ --push \ "$PROJECT_ROOT" printf 'Published %s (%s, %s)\n' "$IMAGE_TAG" "$BUILD_PLATFORMS" "$DISTRO" } build_load() { local arch="${2:-amd64}" platform case "$arch" in amd64) platform="linux/amd64" ;; aarch64|arm64) platform="linux/arm64" ;; *) echo "Usage: $0 load [amd64|aarch64]" >&2; exit 2 ;; esac ensure_builder docker buildx build \ --platform "$platform" \ -f "$SCRIPT_DIR/Dockerfile" \ --target "runtime-${DISTRO}" \ --build-arg "BUILD_VERSION=$VERSION" \ --build-arg "RUST_VERSION=$RUST_VERSION" \ -t "${IMAGE_REPO}:local" \ --load \ "$PROJECT_ROOT" printf 'Loaded %s:local for %s (%s)\n' "$IMAGE_REPO" "$arch" "$DISTRO" } render_metadata case "$ACTION" in render) ;; push) build_push ;; load) build_load "${2:-amd64}" ;; *) echo "Usage: $0 [render|push|load [amd64|aarch64]]" >&2 exit 2 ;; esac