176 lines
5.9 KiB
Bash
Executable File
176 lines
5.9 KiB
Bash
Executable File
#!/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"
|
|
RUN_SCRIPT="$SCRIPT_DIR/run.sh"
|
|
DOCKERFILE="$SCRIPT_DIR/Dockerfile"
|
|
|
|
[[ -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"
|
|
}
|
|
|
|
validate_addon_port_contract() {
|
|
local ingress_port
|
|
ingress_port="$(awk '/^ingress_port:/ {print $2; exit}' "$CONFIG")"
|
|
[[ "$ingress_port" =~ ^[0-9]+$ ]] || { echo "Invalid ingress_port in $CONFIG" >&2; exit 1; }
|
|
grep -Fqx "readonly HA_HTTP_PORT=${ingress_port}" "$RUN_SCRIPT" || {
|
|
echo "HA port contract mismatch: ingress_port=${ingress_port}, run.sh differs" >&2
|
|
exit 1
|
|
}
|
|
grep -Fq "watchdog: \"http://[HOST]:[PORT:${ingress_port}]/api/health\"" "$CONFIG" || {
|
|
echo "HA port contract mismatch: watchdog must use [PORT:${ingress_port}]" >&2
|
|
exit 1
|
|
}
|
|
grep -Fqx "hassio_api: true" "$CONFIG" || {
|
|
echo "HA add-on contract mismatch: hassio_api must be enabled for Supervisor runtime discovery" >&2
|
|
exit 1
|
|
}
|
|
local expose_count
|
|
expose_count="$(grep -Ec "^EXPOSE ${ingress_port}/tcp$" "$DOCKERFILE" || true)"
|
|
(( expose_count >= 1 )) || {
|
|
echo "HA port contract mismatch: Dockerfile must expose ${ingress_port}/tcp" >&2
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
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
|
|
validate_addon_port_contract
|
|
|
|
case "$ACTION" in
|
|
render) ;;
|
|
push) build_push ;;
|
|
load) build_load "${2:-amd64}" ;;
|
|
*)
|
|
echo "Usage: $0 [render|push|load [amd64|aarch64]]" >&2
|
|
exit 2
|
|
;;
|
|
esac
|