97 lines
3.5 KiB
Bash
Executable file
97 lines
3.5 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Write path: repo -> HA instance, for the "api" adapter only (see
|
|
# services/home-assistant/DESIGN.md, "Deploy path: adapter per instance",
|
|
# "Sync model", "Validation gate").
|
|
#
|
|
# Scope: automations/scripts/scenes only. Dashboards and helpers are
|
|
# WebSocket-only (scripts/ha/lib/ha_ws.py has no mutating command,
|
|
# deliberately) — writing those is a separate, not-yet-built task.
|
|
#
|
|
# Hard sequence enforced in lib/deploy_api.py, no shortcuts: DRIFT-CHECK ->
|
|
# VALIDATE (local + live check_config) -> WRITE (per object) -> VERIFY (per
|
|
# object). Any drift or validation failure aborts before a single mutating
|
|
# call is made. --dry-run runs the same DRIFT-CHECK/VALIDATE steps (both
|
|
# read-only in effect) and stops before WRITE/VERIFY.
|
|
#
|
|
# Instances with status != "active" (e.g. ken-legacy: archived, chelsty-ha:
|
|
# offline) or adapter != "api" are refused unconditionally — see
|
|
# instances.yaml and DESIGN.md's "Incident log" for why ken-legacy must
|
|
# never be a deploy target again.
|
|
#
|
|
# Usage: deploy.sh <instance> [--dry-run] [file...]
|
|
# no file args -> every automations/scripts/scenes file currently in
|
|
# services/home-assistant/config/<instance>/
|
|
# file args -> only those objects (path as given, repo-root-relative,
|
|
# or config-dir-relative all accepted)
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
SERVICE_DIR="$(cd "$SCRIPT_DIR/../../services/home-assistant" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
INSTANCES_FILE="$SERVICE_DIR/instances.yaml"
|
|
|
|
usage() {
|
|
echo "usage: $0 <instance> [--dry-run] [file...]" >&2
|
|
}
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
usage
|
|
exit 2
|
|
fi
|
|
|
|
INSTANCE="$1"
|
|
shift
|
|
|
|
DRY_RUN=0
|
|
FILE_ARGS=()
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--dry-run) DRY_RUN=1 ;;
|
|
--) ;;
|
|
*) FILE_ARGS+=("$arg") ;;
|
|
esac
|
|
done
|
|
|
|
if [[ ! -f "$INSTANCES_FILE" ]]; then
|
|
echo "error: instances file not found: $INSTANCES_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
INSTANCE_CONFIG_OUTPUT="$(python3 "$SCRIPT_DIR/lib/instance_config.py" "$INSTANCES_FILE" "$INSTANCE")" || exit 1
|
|
eval "$INSTANCE_CONFIG_OUTPUT"
|
|
|
|
if [[ "$HA_STATUS" != "active" ]]; then
|
|
echo "error: instance '$INSTANCE' has status='$HA_STATUS', not 'active' — deploy.sh refuses" >&2
|
|
echo " non-active instances unconditionally, no override flag (see instances.yaml)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$HA_ADAPTER" != "api" ]]; then
|
|
echo "error: instance '$INSTANCE' uses adapter='$HA_ADAPTER' — deploy.sh only implements" >&2
|
|
echo " the 'api' write path (see DESIGN.md, 'Deploy path: adapter per instance')." >&2
|
|
exit 1
|
|
fi
|
|
|
|
api_token_file="${HA_TOKEN_PATH/#\~/$HOME}"
|
|
if [[ -z "$HA_TOKEN_PATH" || ! -f "$api_token_file" ]]; then
|
|
echo "error: instance '$INSTANCE' has no token at '${api_token_file:-<unset>}' — deploy.sh" >&2
|
|
echo " has no fallback without a deploy_agent long-lived access token (see" >&2
|
|
echo " DESIGN.md, 'Tokens')." >&2
|
|
exit 1
|
|
fi
|
|
|
|
CONFIG_DIR="$SERVICE_DIR/config/$INSTANCE"
|
|
CONFIG_RELPATH="services/home-assistant/config/$INSTANCE"
|
|
|
|
if [[ ! -d "$CONFIG_DIR" ]]; then
|
|
echo "error: no config/$INSTANCE/ directory — run scripts/ha/import.sh $INSTANCE at least" >&2
|
|
echo " once first, so there is a last-known snapshot to drift-check against." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "== ha deploy: instance=$INSTANCE adapter=$HA_ADAPTER dry_run=$DRY_RUN ==" >&2
|
|
|
|
python3 "$SCRIPT_DIR/lib/deploy_api.py" \
|
|
"$HA_BASE_URL" "$api_token_file" "$REPO_ROOT" "$CONFIG_DIR" "$CONFIG_RELPATH" "$INSTANCE" "$DRY_RUN" \
|
|
"${FILE_ARGS[@]}"
|