mirror of
https://github.com/lildude/ha-addon-teslamate.git
synced 2026-10-13 13:10:30 -04:00
Compare commits
11
Commits
v2.4.0
...
6b07490526
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b07490526 | ||
|
|
a5b5699b49 | ||
|
|
d6d3beb3ee | ||
|
|
6a20a1e31d | ||
|
|
2e360aab07 | ||
|
|
0610675d2b | ||
|
|
371596126d | ||
|
|
4787650bbf | ||
|
|
5b57a82b6a | ||
|
|
157dbc81c2 | ||
|
|
4e5eab5bd9 |
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"name": "HA Addon: TeslaMate",
|
||||
"image": "ghcr.io/home-assistant/devcontainer:2-addons",
|
||||
"image": "ghcr.io/home-assistant/devcontainer:4-apps",
|
||||
"overrideCommand": false,
|
||||
"appPort": ["7123:8123", "7357:4357"],
|
||||
"postStartCommand": "bash devcontainer_bootstrap",
|
||||
"runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"],
|
||||
"workspaceFolder": "/mnt/supervisor/addons/local/${localWorkspaceFolderBasename}",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind,consistency=cached",
|
||||
"containerEnv": {
|
||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||
},
|
||||
@@ -29,6 +32,7 @@
|
||||
},
|
||||
"mounts": [
|
||||
"type=volume,target=/var/lib/docker",
|
||||
"type=volume,target=/var/lib/containerd",
|
||||
"type=volume,target=/mnt/supervisor"
|
||||
]
|
||||
}
|
||||
Executable
+601
@@ -0,0 +1,601 @@
|
||||
#!/usr/bin/env bash
|
||||
# setup-ha.sh - Idempotent Home Assistant setup for TeslaMate development
|
||||
#
|
||||
# This script:
|
||||
# 1. Completes HA onboarding (creates admin user)
|
||||
# 2. Adds the alexbelgium addon repository (for PostgreSQL)
|
||||
# 3. Installs and starts PostgreSQL 17
|
||||
# 4. Installs, configures, and starts the local TeslaMate addon
|
||||
#
|
||||
# MQTT and Grafana dashboard import are disabled so TeslaMate boots cleanly
|
||||
# against PostgreSQL alone. Enable them in the addon configuration once a
|
||||
# broker / Grafana instance is available.
|
||||
#
|
||||
# Usage: bash .devcontainer/setup-ha.sh
|
||||
# DEBUG=1 bash .devcontainer/setup-ha.sh # verbose output + error details
|
||||
#
|
||||
# Safe to run multiple times (idempotent).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HA_URL="http://localhost:8123"
|
||||
ALEXBELGIUM_REPO="https://github.com/alexbelgium/hassio-addons"
|
||||
ADMIN_USER="admin"
|
||||
ADMIN_PASS="pass"
|
||||
CLIENT_ID="${HA_URL}/"
|
||||
|
||||
POSTGRES_PASSWORD="homeassistant"
|
||||
POSTGRES_USER="postgres"
|
||||
POSTGRES_DB="teslamate"
|
||||
POSTGRES_PORT=5432
|
||||
# Addon slug = repo_hash + "_" + addon_slug_from_config
|
||||
# Repo hash: first 8 chars of sha1("https://github.com/alexbelgium/hassio-addons")
|
||||
POSTGRES_SLUG="db21ed7f_postgres_latest"
|
||||
|
||||
TESLAMATE_SLUG="local_teslamate"
|
||||
TESLAMATE_PORT=4000
|
||||
TIMEZONE="Europe/London"
|
||||
|
||||
# Path to the addon's config.json. Home Assistant mounts the local add-on store
|
||||
# at /mnt/supervisor/addons/local on older Supervisor versions and at
|
||||
# /mnt/supervisor/apps/local on newer ones. Derive the addon's directory name
|
||||
# from the script's own location (so it is correct regardless of where the
|
||||
# script is invoked from), then anchor ADDON_DIR to whichever canonical
|
||||
# Supervisor path actually exists - that is the config.json the Supervisor reads.
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ADDON_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ADDON_NAME="$(basename "$ADDON_DIR")"
|
||||
for _local_base in /mnt/supervisor/addons/local /mnt/supervisor/apps/local; do
|
||||
if [ -d "${_local_base}/${ADDON_NAME}" ]; then
|
||||
ADDON_DIR="${_local_base}/${ADDON_NAME}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
CONFIG_JSON="${ADDON_DIR}/config.json"
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
DIM='\033[2m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() { echo -e "${CYAN}==>${NC} ${BOLD}$*${NC}"; }
|
||||
warn() { echo -e "${YELLOW}WARNING:${NC} $*" >&2; }
|
||||
err() { group_end; echo -e "${RED}ERROR:${NC} $*" >&2; exit 1; }
|
||||
ok() { echo -e "${CYAN}==>${NC} ${GREEN}$*${NC}"; }
|
||||
|
||||
debug() {
|
||||
[ -n "$DEBUG" ] || return 0
|
||||
echo -e "${DIM}DEBUG:${NC} $*" >&2
|
||||
}
|
||||
|
||||
|
||||
debug_response() {
|
||||
[ -n "$DEBUG" ] || return 0
|
||||
local resp="$1" max=800
|
||||
if [ "${#resp}" -gt "$max" ]; then
|
||||
debug "response (truncated): ${resp:0:$max}..."
|
||||
else
|
||||
debug "response: ${resp}"
|
||||
fi
|
||||
}
|
||||
|
||||
# group_start()/group_end() wrap verbose output in GitHub Actions log groups so
|
||||
# it is folded by default. They are no-ops unless DEBUG is enabled and the
|
||||
# script is running inside GitHub Actions, so local and non-debug runs are
|
||||
# unchanged. Markers go to stderr to stay ordered with debug() and err() output.
|
||||
_group_open=0
|
||||
group_start() {
|
||||
[ -n "$DEBUG" ] && [ "${GITHUB_ACTIONS:-}" = "true" ] || return 0
|
||||
echo "::group::$*" >&2
|
||||
_group_open=1
|
||||
}
|
||||
group_end() {
|
||||
[ "$_group_open" = "1" ] || return 0
|
||||
echo "::endgroup::" >&2
|
||||
_group_open=0
|
||||
}
|
||||
|
||||
# run_step() runs a setup step, wrapping its output in a titled GitHub Actions
|
||||
# log group (when enabled) so the verbose output folds under that heading.
|
||||
run_step() {
|
||||
local title="$1"
|
||||
shift
|
||||
group_start "$title"
|
||||
"$@"
|
||||
group_end
|
||||
}
|
||||
|
||||
# Run Supervisor API call via hassio_cli container
|
||||
# IMPORTANT: Use single quotes for outer sh -c argument so $SUPERVISOR_TOKEN
|
||||
# is expanded by the inner shell (where the env var exists), not the outer bash.
|
||||
supervisor_api() {
|
||||
local method="$1" endpoint="$2"
|
||||
debug "API ${method} ${endpoint}"
|
||||
local resp
|
||||
resp=$(docker exec hassio_cli sh -c \
|
||||
'curl -s -X '"${method}"' -H "Authorization: Bearer $SUPERVISOR_TOKEN" -H "Content-Type: application/json" http://supervisor'"${endpoint}")
|
||||
debug_response "$resp"
|
||||
printf '%s\n' "$resp"
|
||||
}
|
||||
|
||||
supervisor_api_with_body() {
|
||||
local method="$1" endpoint="$2" body="$3"
|
||||
debug "API ${method} ${endpoint} body=${body}"
|
||||
local resp
|
||||
resp=$(docker exec -e "BODY=${body}" hassio_cli sh -c \
|
||||
'curl -s -X '"${method}"' -H "Authorization: Bearer $SUPERVISOR_TOKEN" -H "Content-Type: application/json" -d "$BODY" http://supervisor'"${endpoint}")
|
||||
debug_response "$resp"
|
||||
printf '%s\n' "$resp"
|
||||
}
|
||||
|
||||
# supervisor_api_post() performs a state-changing POST (optionally with a JSON
|
||||
# body) and aborts via err() if the Supervisor returns {"result":"error",...}.
|
||||
# These endpoints (install/start/options/reload) are synchronous, so without
|
||||
# this check a failure is silently swallowed and the script then polls for a
|
||||
# state that will never arrive. The response is emitted on stdout on success.
|
||||
supervisor_api_post() {
|
||||
local endpoint="$1" body="${2:-}"
|
||||
local resp result
|
||||
if [ -n "$body" ]; then
|
||||
resp=$(supervisor_api_with_body POST "$endpoint" "$body")
|
||||
else
|
||||
resp=$(supervisor_api POST "$endpoint")
|
||||
fi
|
||||
result=$(printf '%s' "$resp" | jq -r '.result // empty' 2>/dev/null)
|
||||
if [ "$result" = "error" ]; then
|
||||
err "Supervisor API error on POST ${endpoint}: $(printf '%s' "$resp" | jq -r '.message // .' 2>/dev/null)"
|
||||
fi
|
||||
printf '%s\n' "$resp"
|
||||
}
|
||||
|
||||
# POST to the local HA API for an optional onboarding step. Failures are
|
||||
# non-fatal; the request and any response/error are shown only when debugging.
|
||||
ha_post_optional() {
|
||||
local token="$1" endpoint="$2" data="$3"
|
||||
debug "POST ${endpoint} ${data}"
|
||||
local resp rc=0
|
||||
resp=$(curl -sS -X POST "${HA_URL}${endpoint}" \
|
||||
-H "Authorization: Bearer ${token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" 2>&1) || rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
debug "${endpoint} failed (rc=${rc}): ${resp}"
|
||||
elif [ -n "$resp" ]; then
|
||||
debug "${endpoint} response: ${resp}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Query a specific jq field from a Supervisor API response
|
||||
# Runs curl and jq inside hassio_cli to avoid docker exec stdout pipe issues
|
||||
supervisor_api_jq() {
|
||||
local endpoint="$1" jq_filter="$2"
|
||||
docker exec -e "JQ_FILTER=${jq_filter}" hassio_cli sh -c \
|
||||
'curl -s -H "Authorization: Bearer $SUPERVISOR_TOKEN" http://supervisor'"${endpoint}"' | jq -r "$JQ_FILTER"'
|
||||
}
|
||||
|
||||
# Check if a jq expression matches (returns 0 if match found, 1 otherwise)
|
||||
supervisor_api_jq_test() {
|
||||
local endpoint="$1" jq_filter="$2"
|
||||
docker exec -e "JQ_FILTER=${jq_filter}" hassio_cli sh -c \
|
||||
'curl -s -H "Authorization: Bearer $SUPERVISOR_TOKEN" http://supervisor'"${endpoint}"' | jq -e "$JQ_FILTER" > /dev/null 2>&1'
|
||||
}
|
||||
|
||||
# --- Step 0: Wait for HA to be ready ---
|
||||
|
||||
wait_for_ha() {
|
||||
log "Waiting for Home Assistant to be ready..."
|
||||
local max_attempts=60
|
||||
local attempt=0
|
||||
local http_code
|
||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||
# 200 = onboarding pending, 404 = already onboarded; both mean HA's HTTP
|
||||
# server is up and serving. A connection failure yields 000.
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" "${HA_URL}/api/onboarding" 2>/dev/null || true)
|
||||
if [ "$http_code" = "200" ] || [ "$http_code" = "404" ]; then
|
||||
ok "Home Assistant is ready"
|
||||
return 0
|
||||
fi
|
||||
debug "Attempt $((attempt + 1))/${max_attempts}: Home Assistant not ready yet"
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
err "Home Assistant did not become ready in time"
|
||||
}
|
||||
|
||||
# --- Step 1: Complete onboarding ---
|
||||
|
||||
complete_onboarding() {
|
||||
log "Checking onboarding status..."
|
||||
# Capture the body and HTTP status separately. A configured instance returns
|
||||
# 404 here because the onboarding endpoints are removed once onboarding
|
||||
# completes, so don't use curl -f (which would abort under set -e).
|
||||
local onboarding http_code
|
||||
onboarding=$(curl -s -w $'\n%{http_code}' "${HA_URL}/api/onboarding")
|
||||
http_code=$(printf '%s' "$onboarding" | tail -n1)
|
||||
onboarding=$(printf '%s' "$onboarding" | sed '$d')
|
||||
|
||||
if [ "$http_code" = "404" ]; then
|
||||
ok "Onboarding already completed (endpoint returned 404)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local user_done
|
||||
user_done=$(echo "$onboarding" | jq -r '.[] | select(.step == "user") | .done')
|
||||
|
||||
if [ "$user_done" = "true" ]; then
|
||||
ok "Onboarding already completed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Creating admin user '${ADMIN_USER}'..."
|
||||
local auth_response
|
||||
auth_response=$(curl -sf -X POST "${HA_URL}/api/onboarding/users" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"client_id\":\"${CLIENT_ID}\",\"name\":\"Admin\",\"username\":\"${ADMIN_USER}\",\"password\":\"${ADMIN_PASS}\",\"language\":\"en\"}")
|
||||
debug "users response: ${auth_response}"
|
||||
|
||||
local auth_code
|
||||
auth_code=$(echo "$auth_response" | jq -r '.auth_code')
|
||||
|
||||
if [ -z "$auth_code" ] || [ "$auth_code" = "null" ]; then
|
||||
err "Failed to create user: ${auth_response}"
|
||||
fi
|
||||
|
||||
# Exchange auth code for access token
|
||||
local token_response
|
||||
token_response=$(curl -sf -X POST "${HA_URL}/auth/token" \
|
||||
--data-urlencode "grant_type=authorization_code" \
|
||||
--data-urlencode "code=${auth_code}" \
|
||||
--data-urlencode "client_id=${CLIENT_ID}")
|
||||
debug "token response: ${token_response}"
|
||||
|
||||
local access_token
|
||||
access_token=$(echo "$token_response" | jq -r '.access_token')
|
||||
|
||||
if [ -z "$access_token" ] || [ "$access_token" = "null" ]; then
|
||||
err "Failed to get access token: ${token_response}"
|
||||
fi
|
||||
|
||||
# Complete remaining onboarding steps (best-effort; failures are non-fatal)
|
||||
log "Completing onboarding steps..."
|
||||
ha_post_optional "$access_token" "/api/onboarding/core_config" "{}"
|
||||
ha_post_optional "$access_token" "/api/onboarding/analytics" "{}"
|
||||
ha_post_optional "$access_token" "/api/onboarding/integration" \
|
||||
"{\"client_id\":\"${CLIENT_ID}\",\"redirect_uri\":\"${HA_URL}/\"}"
|
||||
|
||||
ok "Onboarding complete"
|
||||
}
|
||||
|
||||
# --- Step 2: Add alexbelgium addon repository ---
|
||||
|
||||
add_addon_repo() {
|
||||
log "Checking addon repositories..."
|
||||
|
||||
if supervisor_api_jq_test "/store/repositories" ".data[] | select(.source == \"${ALEXBELGIUM_REPO}\")"; then
|
||||
ok "Repository already added: ${ALEXBELGIUM_REPO}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Adding addon repository: ${ALEXBELGIUM_REPO}"
|
||||
supervisor_api_post "/store/repositories" \
|
||||
"{\"repository\":\"${ALEXBELGIUM_REPO}\"}" > /dev/null
|
||||
|
||||
# Wait for the store to refresh by checking if the postgres addon is available
|
||||
log "Waiting for store to refresh..."
|
||||
local max_attempts=60
|
||||
local attempt=0
|
||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||
if supervisor_api_jq_test "/addons/${POSTGRES_SLUG}/info" '.data.name'; then
|
||||
ok "Store refreshed successfully"
|
||||
return 0
|
||||
fi
|
||||
debug "Attempt $((attempt + 1))/${max_attempts}: postgres addon not in store yet"
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
err "Store did not refresh in time - postgres addon not found"
|
||||
}
|
||||
|
||||
# --- Step 3: Install PostgreSQL ---
|
||||
|
||||
install_postgres() {
|
||||
local postgres_slug="$1"
|
||||
|
||||
log "Checking PostgreSQL addon status (${postgres_slug})..."
|
||||
local version
|
||||
version=$(supervisor_api_jq "/addons/${postgres_slug}/info" '.data.version // empty')
|
||||
|
||||
if [ -n "$version" ]; then
|
||||
local state
|
||||
state=$(supervisor_api_jq "/addons/${postgres_slug}/info" '.data.state')
|
||||
ok "PostgreSQL addon already installed (version: ${version}, state: ${state})"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Installing PostgreSQL addon..."
|
||||
supervisor_api_post "/addons/${postgres_slug}/install" > /dev/null
|
||||
|
||||
# Wait for installation to complete
|
||||
log "Waiting for PostgreSQL installation..."
|
||||
local max_attempts=120
|
||||
local attempt=0
|
||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||
version=$(supervisor_api_jq "/addons/${postgres_slug}/info" '.data.version // empty')
|
||||
if [ -n "$version" ]; then
|
||||
ok "PostgreSQL addon installed (version: ${version})"
|
||||
return 0
|
||||
fi
|
||||
debug "Attempt $((attempt + 1))/${max_attempts}: PostgreSQL not installed yet"
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
err "PostgreSQL addon installation timed out"
|
||||
}
|
||||
|
||||
# --- Step 4: Configure and start PostgreSQL ---
|
||||
|
||||
configure_and_start_postgres() {
|
||||
local postgres_slug="$1"
|
||||
|
||||
log "Configuring PostgreSQL addon..."
|
||||
local options_json
|
||||
options_json=$(jq -n \
|
||||
--arg pw "$POSTGRES_PASSWORD" \
|
||||
--arg db "$POSTGRES_DB" \
|
||||
'{options: {POSTGRES_PASSWORD: $pw, POSTGRES_DB: $db, env_vars: []}}')
|
||||
|
||||
supervisor_api_post "/addons/${postgres_slug}/options" \
|
||||
"${options_json}" > /dev/null
|
||||
|
||||
# Check if already running
|
||||
local state
|
||||
state=$(supervisor_api_jq "/addons/${postgres_slug}/info" '.data.state')
|
||||
|
||||
if [ "$state" = "started" ]; then
|
||||
ok "PostgreSQL addon already running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Starting PostgreSQL addon..."
|
||||
supervisor_api_post "/addons/${postgres_slug}/start" > /dev/null
|
||||
|
||||
# Wait for postgres to be ready
|
||||
log "Waiting for PostgreSQL to be ready..."
|
||||
local max_attempts=60
|
||||
local attempt=0
|
||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||
state=$(supervisor_api_jq "/addons/${postgres_slug}/info" '.data.state')
|
||||
if [ "$state" = "started" ]; then
|
||||
ok "PostgreSQL addon is running"
|
||||
# Give postgres time to initialize the database
|
||||
sleep 10
|
||||
return 0
|
||||
fi
|
||||
debug "Attempt $((attempt + 1))/${max_attempts}: PostgreSQL state=${state}"
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
err "PostgreSQL addon did not start in time"
|
||||
}
|
||||
|
||||
# --- Step 5: Install and configure TeslaMate ---
|
||||
|
||||
# Remove the "image" key from config.json so the Supervisor builds the addon
|
||||
# image locally from the Dockerfile instead of pulling a pre-built image.
|
||||
remove_addon_image() {
|
||||
log "Ensuring TeslaMate addon builds locally..."
|
||||
|
||||
if [ ! -f "$CONFIG_JSON" ]; then
|
||||
warn "config.json not found at ${CONFIG_JSON} - skipping image removal"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! jq -e 'has("image")' "$CONFIG_JSON" > /dev/null 2>&1; then
|
||||
ok "config.json already has no 'image' key - addon will build locally"
|
||||
return 0
|
||||
fi
|
||||
|
||||
jq 'del(.image)' "$CONFIG_JSON" > "${CONFIG_JSON}.tmp" && mv "${CONFIG_JSON}.tmp" "$CONFIG_JSON"
|
||||
ok "Removed 'image' key from config.json"
|
||||
|
||||
# Reload the store so the Supervisor re-reads the local addon config, then
|
||||
# confirm the store entry actually has no image before installing. The reload
|
||||
# is eventually-consistent: if we install while the store still has the
|
||||
# prebuilt image set, the Supervisor tries to PULL ghcr.io/...-{arch}:dev
|
||||
# (which is never published) and fails with addon_unknown_error.
|
||||
log "Reloading addon store..."
|
||||
supervisor_api_post "/store/reload" > /dev/null
|
||||
|
||||
log "Verifying TeslaMate will build locally..."
|
||||
local attempt=0 image
|
||||
while [ "$attempt" -lt 12 ]; do
|
||||
image=$(supervisor_api_jq "/store/addons/${TESLAMATE_SLUG}" '.data.image // empty')
|
||||
if [ -z "$image" ]; then
|
||||
ok "TeslaMate store entry has no image - will build from Dockerfile"
|
||||
return 0
|
||||
fi
|
||||
debug "Attempt $((attempt + 1))/12: store still reports image=${image}"
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
err "Store still reports a prebuilt image for TeslaMate after reload; aborting to avoid pulling a non-existent image"
|
||||
}
|
||||
|
||||
install_teslamate() {
|
||||
log "Checking TeslaMate addon status..."
|
||||
local version
|
||||
version=$(supervisor_api_jq "/addons/${TESLAMATE_SLUG}/info" '.data.version // empty')
|
||||
|
||||
if [ -n "$version" ]; then
|
||||
local state
|
||||
state=$(supervisor_api_jq "/addons/${TESLAMATE_SLUG}/info" '.data.state')
|
||||
ok "TeslaMate addon already installed (version: ${version}, state: ${state})"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Installing TeslaMate addon..."
|
||||
supervisor_api_post "/addons/${TESLAMATE_SLUG}/install" > /dev/null
|
||||
|
||||
# Wait for installation to complete (the TeslaMate image is large)
|
||||
log "Waiting for TeslaMate installation..."
|
||||
local max_attempts=120
|
||||
local attempt=0
|
||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||
version=$(supervisor_api_jq "/addons/${TESLAMATE_SLUG}/info" '.data.version // empty')
|
||||
if [ -n "$version" ]; then
|
||||
ok "TeslaMate addon installed (version: ${version})"
|
||||
return 0
|
||||
fi
|
||||
debug "Attempt $((attempt + 1))/${max_attempts}: TeslaMate not installed yet"
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
err "TeslaMate addon installation timed out"
|
||||
}
|
||||
|
||||
configure_and_start_teslamate() {
|
||||
local postgres_slug="$1"
|
||||
|
||||
# Derive the postgres hostname from its slug (underscores -> hyphens)
|
||||
local postgres_host
|
||||
postgres_host=$(echo "$postgres_slug" | tr '_' '-')
|
||||
|
||||
log "Configuring TeslaMate addon (db host: ${postgres_host})..."
|
||||
|
||||
# Fetch the addon's current options - already populated with defaults - and overlay only
|
||||
# the values we need to change.
|
||||
local current_options overrides options_json
|
||||
current_options=$(supervisor_api_jq "/addons/${TESLAMATE_SLUG}/info" '.data.options')
|
||||
overrides=$(jq -n \
|
||||
--arg user "$POSTGRES_USER" \
|
||||
--arg pass "$POSTGRES_PASSWORD" \
|
||||
--argjson port "$POSTGRES_PORT" \
|
||||
--arg host "$postgres_host" \
|
||||
--arg db "$POSTGRES_DB" \
|
||||
--arg tz "$TIMEZONE" \
|
||||
'{database_user: $user, database_pass: $pass, database_port: $port, database_host: $host, database_name: $db, database_ssl: false, disable_mqtt: true, grafana_import_dashboards: false, timezone: $tz}')
|
||||
options_json=$(jq -n --argjson cur "$current_options" --argjson ovr "$overrides" '{options: ($cur + $ovr)}')
|
||||
debug "options payload: ${options_json}"
|
||||
|
||||
supervisor_api_post "/addons/${TESLAMATE_SLUG}/options" "${options_json}" > /dev/null
|
||||
|
||||
# Check if already running
|
||||
local state
|
||||
state=$(supervisor_api_jq "/addons/${TESLAMATE_SLUG}/info" '.data.state')
|
||||
|
||||
if [ "$state" = "started" ]; then
|
||||
ok "TeslaMate addon already running"
|
||||
wait_for_teslamate_ready
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Starting TeslaMate addon..."
|
||||
supervisor_api_post "/addons/${TESLAMATE_SLUG}/start" > /dev/null
|
||||
|
||||
# Wait for addon state to become "started"
|
||||
log "Waiting for TeslaMate addon to start..."
|
||||
local max_attempts=60
|
||||
local attempt=0
|
||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||
state=$(supervisor_api_jq "/addons/${TESLAMATE_SLUG}/info" '.data.state')
|
||||
if [ "$state" = "started" ]; then
|
||||
break
|
||||
fi
|
||||
if [ "$state" = "error" ]; then
|
||||
dump_diagnostics 50
|
||||
err "TeslaMate addon failed to start (state: ${state})"
|
||||
fi
|
||||
debug "Attempt $((attempt + 1))/${max_attempts}: TeslaMate state=${state}"
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
if [ "$state" != "started" ]; then
|
||||
dump_diagnostics 50
|
||||
err "TeslaMate addon did not start in time (state: ${state})"
|
||||
fi
|
||||
|
||||
wait_for_teslamate_ready
|
||||
ok "TeslaMate addon started and ready"
|
||||
}
|
||||
|
||||
# dump_diagnostics() prints PostgreSQL and TeslaMate add-on logs (full lines) so
|
||||
# a startup failure shows its real cause. PostgreSQL is reported first because
|
||||
# TeslaMate halts if it cannot reach the database, so a stopped/crashed postgres
|
||||
# addon is the most common root cause. Closes any open GitHub Actions log group
|
||||
# first so the output is never hidden inside a collapsed fold.
|
||||
dump_diagnostics() {
|
||||
local lines="${1:-50}"
|
||||
group_end
|
||||
|
||||
local pg_state
|
||||
pg_state=$(supervisor_api_jq "/addons/${POSTGRES_SLUG}/info" '.data.state' 2>/dev/null || true)
|
||||
warn "PostgreSQL addon state: ${pg_state:-unknown}"
|
||||
warn "Last ${lines} lines of PostgreSQL add-on logs:"
|
||||
supervisor_api GET "/addons/${POSTGRES_SLUG}/logs" 2>/dev/null | tail -n "${lines}" >&2 || true
|
||||
|
||||
warn "Last ${lines} lines of TeslaMate add-on logs:"
|
||||
supervisor_api GET "/addons/${TESLAMATE_SLUG}/logs" 2>/dev/null | tail -n "${lines}" >&2 || true
|
||||
}
|
||||
|
||||
wait_for_teslamate_ready() {
|
||||
log "Waiting for TeslaMate to be ready (listening on port ${TESLAMATE_PORT})..."
|
||||
local max_attempts=60
|
||||
local attempt=0
|
||||
local logs state
|
||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||
logs=$(supervisor_api GET "/addons/${TESLAMATE_SLUG}/logs" 2>/dev/null || true)
|
||||
if echo "$logs" | grep -q "TeslaMateWeb.Endpoint"; then
|
||||
ok "TeslaMate is ready"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# TeslaMate's s6 finish script halts the whole container if the service
|
||||
# exits non-zero, so a crash during startup shows up as the add-on leaving
|
||||
# the "started" state. Detect that and fail immediately with the real logs
|
||||
# instead of polling uselessly until the timeout.
|
||||
state=$(supervisor_api_jq "/addons/${TESLAMATE_SLUG}/info" '.data.state')
|
||||
if [ "$state" = "error" ] || [ "$state" = "stopped" ]; then
|
||||
dump_diagnostics 50
|
||||
err "TeslaMate crashed during startup (add-on state: ${state})"
|
||||
fi
|
||||
|
||||
debug "Attempt $((attempt + 1))/${max_attempts}: endpoint not up yet (state=${state})"
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
dump_diagnostics 50
|
||||
err "TeslaMate did not become ready in time (never saw 'TeslaMateWeb.Endpoint' in logs)"
|
||||
}
|
||||
|
||||
# --- Main ---
|
||||
|
||||
main() {
|
||||
log "Setting up Home Assistant for TeslaMate development"
|
||||
echo ""
|
||||
|
||||
debug "Verbose debug output enabled"
|
||||
debug "HA_URL=${HA_URL} POSTGRES_SLUG=${POSTGRES_SLUG} TESLAMATE_SLUG=${TESLAMATE_SLUG}"
|
||||
|
||||
run_step "Wait for Home Assistant" wait_for_ha
|
||||
run_step "Complete onboarding" complete_onboarding
|
||||
run_step "Add addon repository" add_addon_repo
|
||||
|
||||
run_step "Install PostgreSQL" install_postgres "$POSTGRES_SLUG"
|
||||
run_step "Configure and start PostgreSQL" configure_and_start_postgres "$POSTGRES_SLUG"
|
||||
run_step "Prepare TeslaMate local build" remove_addon_image
|
||||
run_step "Install TeslaMate" install_teslamate
|
||||
run_step "Configure and start TeslaMate" configure_and_start_teslamate "$POSTGRES_SLUG"
|
||||
|
||||
echo ""
|
||||
ok "Setup complete!"
|
||||
log " Home Assistant: ${HA_URL} (user: ${ADMIN_USER}, pass: ${ADMIN_PASS})"
|
||||
log " PostgreSQL: ${POSTGRES_SLUG} (user: ${POSTGRES_USER}, db: ${POSTGRES_DB})"
|
||||
log " TeslaMate: open the TeslaMate panel via HA ingress (${HA_URL})"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -2,7 +2,8 @@
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":disableDependencyDashboard"
|
||||
":disableDependencyDashboard",
|
||||
"helpers:pinGitHubActionDigests"
|
||||
],
|
||||
"prHourlyLimit": 4,
|
||||
"prBodyTemplate": "{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}",
|
||||
@@ -12,10 +13,9 @@
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"fileMatch": "^build.json$",
|
||||
"fileMatch": "(^|/)Dockerfile$",
|
||||
"matchStrings": [
|
||||
"\"teslamate/teslamate:(?<currentValue>\\d+\\.\\d+\\.\\d+)\"",
|
||||
"\"teslamate_version\": \"(?<currentValue>\\d+\\.\\d+\\.\\d+)\""
|
||||
"ARG teslamate_version=(?<currentValue>\\d+\\.\\d+\\.\\d+)"
|
||||
],
|
||||
"datasourceTemplate": "github-releases",
|
||||
"depNameTemplate": "teslamate-org/teslamate"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Integration Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- config.json
|
||||
- Dockerfile
|
||||
- rootfs/**
|
||||
- .devcontainer/**
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- config.json
|
||||
- Dockerfile
|
||||
- rootfs/**
|
||||
- .devcontainer/**
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
name: Integration test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Run integration test in devcontainer
|
||||
uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3.1900000450
|
||||
with:
|
||||
env: |
|
||||
DEBUG=${{ vars.DEBUG }}
|
||||
GITHUB_ACTIONS=true
|
||||
runCmd: |
|
||||
if jq -e 'has("image")' config.json >/dev/null 2>&1; then
|
||||
jq 'del(.image)' config.json > config.json.tmp && mv config.json.tmp config.json
|
||||
fi
|
||||
|
||||
printf '#!/bin/sh\nexit 0\n' | sudo tee /usr/sbin/apparmor_parser > /dev/null && sudo chmod +x /usr/sbin/apparmor_parser
|
||||
sudo sed -i 's|docker run --rm --privileged|docker run --rm --privileged -v /usr/sbin/apparmor_parser:/usr/sbin/apparmor_parser:ro|' /usr/bin/supervisor_run
|
||||
if [ -n "${DEBUG:-}" ]; then
|
||||
supervisor_run &
|
||||
else
|
||||
supervisor_run > /dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
bash .devcontainer/setup-ha.sh
|
||||
@@ -14,9 +14,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⤵️ Check out code from GitHub
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: 🚀 Run Home Assistant Add-on Lint
|
||||
uses: frenck/action-addon-linter@v2
|
||||
uses: frenck/action-addon-linter@f995494fd84fae6310d23617e66d0e37de4f14eb # v2.21.0
|
||||
with:
|
||||
path: "./"
|
||||
|
||||
@@ -11,41 +11,76 @@ on:
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish
|
||||
init:
|
||||
name: Initialize build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
matrix: ${{ steps.matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: |
|
||||
version="${{ inputs.version }}"
|
||||
if [ -n "$version" ]; then
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "version=$(curl --header "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" --silent "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/latest" | jq -r .tag_name | sed s/v//)" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build matrix
|
||||
id: matrix
|
||||
run: |
|
||||
# Derive the per-arch build matrix from config.json. amd64 and aarch64
|
||||
# build on native runners (no QEMU). Base image, build args and labels
|
||||
# all live in the Dockerfile, so nothing extra is needed here.
|
||||
image_template=$(jq -r '.image' config.json)
|
||||
architectures=$(jq -r '.arch[]' config.json)
|
||||
matrix='{"include":[]}'
|
||||
for arch in $architectures; do
|
||||
case "${arch}" in
|
||||
amd64) os="ubuntu-24.04" ;;
|
||||
aarch64) os="ubuntu-24.04-arm" ;;
|
||||
*) echo "::error::Unsupported architecture: ${arch}"; exit 1 ;;
|
||||
esac
|
||||
image="${image_template//\{arch\}/${arch}}"
|
||||
matrix=$(jq -c \
|
||||
--arg arch "$arch" \
|
||||
--arg os "$os" \
|
||||
--arg image "$image" \
|
||||
'.include += [{arch: $arch, os: $os, image: $image}]' <<< "${matrix}")
|
||||
done
|
||||
echo "matrix=${matrix}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.arch }} image
|
||||
needs: init
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["amd64", "aarch64"]
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Get version
|
||||
run: |
|
||||
version=${{ inputs.version }}
|
||||
if [ -n "$version" ]; then
|
||||
echo "latest_release=$version" >> $GITHUB_ENV
|
||||
else
|
||||
echo "latest_release=$(curl --header "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" --silent https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r .tag_name | sed s/v// )" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Build and publish
|
||||
uses: home-assistant/builder@2026.02.1
|
||||
uses: home-assistant/builder/actions/build-image@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
|
||||
with:
|
||||
args: |
|
||||
--${{ matrix.arch }} \
|
||||
--version ${{ env.latest_release }} \
|
||||
--target /data/ \
|
||||
--addon
|
||||
arch: ${{ matrix.arch }}
|
||||
image: ${{ matrix.image }}
|
||||
image-tags: |
|
||||
${{ needs.init.outputs.version }}
|
||||
latest
|
||||
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
push: "true"
|
||||
cosign: "false"
|
||||
version: ${{ needs.init.outputs.version }}
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
path: teslamate
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
echo "latest_release=$(curl --header "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" --silent https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r .tag_name | sed s/v// )" >> $GITHUB_ENV
|
||||
- name: Increment semver
|
||||
id: semver
|
||||
uses: matt-FFFFFF/simple-semver@v0.1.1
|
||||
uses: matt-FFFFFF/simple-semver@967d66a65b0c5afae2a815ab2d311f66a32293ab # v0.1.1
|
||||
with:
|
||||
semver-input: ${{ env.latest_release }}
|
||||
increment: ${{ github.event.inputs.semverIncrement }}
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
git push --tags
|
||||
working-directory: ${{ github.workspace }}/teslamate
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2.5.0
|
||||
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
|
||||
with:
|
||||
tag_name: v${{ steps.semver.outputs.semver }}
|
||||
body_path: CHANGELOG.txt
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
prerelease: false
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Checkout addon repo
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
path: repo
|
||||
repository: lildude/ha-addons
|
||||
|
||||
+52
-18
@@ -5,9 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- build.yaml
|
||||
- config.yaml
|
||||
- build.json
|
||||
- config.json
|
||||
- Dockerfile
|
||||
- rootfs/**
|
||||
@@ -15,34 +12,71 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- build.yaml
|
||||
- config.yaml
|
||||
- build.json
|
||||
- config.json
|
||||
- Dockerfile
|
||||
- rootfs/**
|
||||
|
||||
jobs:
|
||||
build:
|
||||
init:
|
||||
name: Initialize build
|
||||
runs-on: ubuntu-latest
|
||||
name: Build add-on
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
matrix: ${{ steps.matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Build matrix
|
||||
id: matrix
|
||||
run: |
|
||||
# Derive the per-arch build matrix from config.json. amd64 and aarch64
|
||||
# build on native runners (no QEMU). Base image, build args and labels
|
||||
# all live in the Dockerfile, so nothing extra is needed here.
|
||||
image_template=$(jq -r '.image' config.json)
|
||||
architectures=$(jq -r '.arch[]' config.json)
|
||||
matrix='{"include":[]}'
|
||||
for arch in $architectures; do
|
||||
case "${arch}" in
|
||||
amd64) os="ubuntu-24.04" ;;
|
||||
aarch64) os="ubuntu-24.04-arm" ;;
|
||||
*) echo "::error::Unsupported architecture: ${arch}"; exit 1 ;;
|
||||
esac
|
||||
image="${image_template//\{arch\}/${arch}}"
|
||||
matrix=$(jq -c \
|
||||
--arg arch "$arch" \
|
||||
--arg os "$os" \
|
||||
--arg image "$image" \
|
||||
'.include += [{arch: $arch, os: $os, image: $image}]' <<< "${matrix}")
|
||||
done
|
||||
echo "matrix=${matrix}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.arch }} add-on
|
||||
needs: init
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["amd64", "aarch64"]
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Build add-on
|
||||
uses: home-assistant/builder@2026.02.1
|
||||
uses: home-assistant/builder/actions/build-image@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
|
||||
with:
|
||||
args: |
|
||||
--test \
|
||||
--${{ matrix.arch }} \
|
||||
--target /data/ \
|
||||
--addon
|
||||
arch: ${{ matrix.arch }}
|
||||
image: ${{ matrix.image }}
|
||||
image-tags: test
|
||||
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
load: "true"
|
||||
cosign: "false"
|
||||
version: test
|
||||
|
||||
- name: Image labels
|
||||
run: |
|
||||
docker inspect ghcr.io/lildude/ha-addon-teslamate-${{ matrix.arch }} --format='{{json .Config.Labels}}' | jq
|
||||
docker inspect ${{ matrix.image }}:test --format='{{json .Config.Labels}}' | jq
|
||||
|
||||
Vendored
-6
@@ -1,11 +1,5 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"**/build.json"
|
||||
],
|
||||
"url": "https://raw.githubusercontent.com/frenck/action-addon-linter/main/src/build.schema.json"
|
||||
},
|
||||
{
|
||||
"fileMatch": [
|
||||
"**/config.json"
|
||||
|
||||
+12
-5
@@ -1,11 +1,9 @@
|
||||
ARG BUILD_FROM
|
||||
ARG teslamate_version
|
||||
FROM teslamate/grafana:${teslamate_version} as grafana
|
||||
ARG teslamate_version=4.0.1
|
||||
FROM teslamate/grafana:${teslamate_version} AS grafana
|
||||
|
||||
#---
|
||||
|
||||
ARG BUILD_FROM
|
||||
FROM $BUILD_FROM
|
||||
FROM teslamate/teslamate:${teslamate_version}
|
||||
|
||||
ARG BUILD_ARCH
|
||||
ARG BASHIO_VERSION=0.16.2
|
||||
@@ -49,5 +47,14 @@ COPY rootfs /
|
||||
COPY --from=grafana /dashboards /dashboards
|
||||
COPY --from=grafana /dashboards_internal /dashboards
|
||||
|
||||
LABEL \
|
||||
maintainer="Colin Seymour (https://github.com/lildude)" \
|
||||
org.opencontainers.image.authors="Colin Seymour (https://github.com/lildude)" \
|
||||
org.opencontainers.image.description="A self-hosted data logger for your Tesla." \
|
||||
org.opencontainers.image.documentation="https://github.com/lildude/ha-addon-teslamate/blob/main/DOCS.md" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.source="https://github.com/lildude/ha-addon-teslamate/" \
|
||||
org.opencontainers.image.title="Home Assistant Add-on: TeslaMate"
|
||||
|
||||
# S6-Overlay
|
||||
ENTRYPOINT ["/init"]
|
||||
|
||||
@@ -85,6 +85,6 @@ Everything should pick up where it was before.
|
||||
[repo-btn]: https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg
|
||||
[sponsor-badge]: https://img.shields.io/badge/Sponsor_Me-%E2%9D%A4-ec6cb9?logo=GitHub
|
||||
[teslamate-backup]: https://docs.teslamate.org/docs/maintenance/backup_restore
|
||||
[teslamate-version]: https://img.shields.io/badge/dynamic/json?label=TeslaMate%20Version&url=https%3A%2F%2Fraw.githubusercontent.com%2Flildude%2Fha-addon-teslamate%2Fmain%2Fbuild.json&query=%24.args.teslamate_version
|
||||
[teslamate-version]: https://img.shields.io/badge/dynamic/regex?url=https%3A%2F%2Fgithub.com%2Flildude%2Fha-addon-teslamate%2Fblob%2Fmain%2FDockerfile&search=teslamate_version%3D(%3F%3Ccurrent_ver%3E%5Cd%2B%5C.%5Cd%2B%5C.%5Cd%2B)&replace=%241&label=TeslaMate%20Version
|
||||
[teslamate]: https://github.com/teslamate-org/teslamate/
|
||||
[timescaledb]: https://github.com/expaso/hassos-addon-timescaledb
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"build_from": {
|
||||
"aarch64": "teslamate/teslamate:3.0.0",
|
||||
"amd64": "teslamate/teslamate:3.0.0"
|
||||
},
|
||||
"args": {
|
||||
"teslamate_version": "3.0.0"
|
||||
},
|
||||
"labels":{
|
||||
"maintainer": "Colin Seymour (https://github.com/lildude)",
|
||||
"org.opencontainers.image.authors": "Colin Seymour (https://github.com/lildude)",
|
||||
"org.opencontainers.image.description": "A self-hosted data logger for your Tesla.",
|
||||
"org.opencontainers.image.documentation": "https://github.com/lildude/ha-addon-teslamate/blob/main/DOCS.md",
|
||||
"org.opencontainers.image.licenses": "MIT",
|
||||
"org.opencontainers.image.source": "https://github.com/lildude/ha-addon-teslamate/",
|
||||
"org.opencontainers.image.title": "Home Assistant Add-on: TeslaMate"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user