mirror of
https://github.com/lildude/ha-addon-teslamate.git
synced 2026-10-13 21:19:16 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b069703f3d | ||
|
|
39cd7ea918 | ||
|
|
ff39e95482 | ||
|
|
8b048671cb | ||
|
|
932352d43a | ||
|
|
b21b77c5b0 | ||
|
|
0de3dd5aa2 | ||
|
|
6b07490526 | ||
|
|
a5b5699b49 | ||
|
|
d6d3beb3ee | ||
|
|
6a20a1e31d | ||
|
|
2e360aab07 | ||
|
|
0610675d2b | ||
|
|
371596126d | ||
|
|
4787650bbf | ||
|
|
5b57a82b6a | ||
|
|
157dbc81c2 | ||
|
|
4e5eab5bd9 | ||
|
|
2456784d56 | ||
|
|
f7b4745a20 | ||
|
|
bdc682d040 | ||
|
|
0bd729517b | ||
|
|
145e7e6807 | ||
|
|
d42b7aa0e8 | ||
|
|
320389ea16 | ||
|
|
61c0a0fada | ||
|
|
d7cea32f07 | ||
|
|
1baaf635f5 | ||
|
|
eec7ac2822 | ||
|
|
7a86a1f0c3 | ||
|
|
83c29fbed8 | ||
|
|
ee6faf280d | ||
|
|
2421d0a805 | ||
|
|
aa5dee6c4a | ||
|
|
77e7510e3b | ||
|
|
6031fe6fa4 | ||
|
|
eb7945344e | ||
|
|
e9c77210b7 | ||
|
|
64cbe20138 | ||
|
|
1c521e808c | ||
|
|
0d7507b3d9 | ||
|
|
d8aa9cb7d9 | ||
|
|
2dfda5bbc0 | ||
|
|
522e9890ca | ||
|
|
453252d864 | ||
|
|
800d3ee3ab | ||
|
|
562fd279b0 | ||
|
|
9d0729c9bf | ||
|
|
e3a352b44d | ||
|
|
fc5f6d6c2d | ||
|
|
db77437898 | ||
|
|
111527b4b6 | ||
|
|
df2f87600b | ||
|
|
4bc610e9a7 | ||
|
|
48cc287c98 | ||
|
|
04eb3228bd | ||
|
|
a5180bcb8e | ||
|
|
f2d6fcd514 | ||
|
|
540732b90a |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "ghcr.io/eitsupi/devcontainer-features/jq-likes@sha256:6a8b044f9f424097ec6d212167836b26994a76a556cbcb202084be79fe8032bd",
|
||||||
|
"integrity": "sha256:6a8b044f9f424097ec6d212167836b26994a76a556cbcb202084be79fe8032bd"
|
||||||
|
},
|
||||||
|
"ghcr.io/jungaretti/features/ripgrep:1": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "ghcr.io/jungaretti/features/ripgrep@sha256:c0922b6f4a9184080c8435b6ef25983dee6c734733f0a749d852ad0d33de2253",
|
||||||
|
"integrity": "sha256:c0922b6f4a9184080c8435b6ef25983dee6c734733f0a749d852ad0d33de2253"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "HA Addon: TeslaMate",
|
"name": "HA Addon: TeslaMate",
|
||||||
"image": "ghcr.io/home-assistant/devcontainer:addons",
|
"image": "ghcr.io/home-assistant/devcontainer:6-apps",
|
||||||
"appPort": ["7123:8123", "7357:4357"],
|
"overrideCommand": false,
|
||||||
"postStartCommand": "sudo sed -i 's/export SUPERVISOR_VERSION=.*/export SUPERVISOR_VERSION=2024.03.1.dev1803/' /etc/supervisor_scripts/common && bash devcontainer_bootstrap", // Workaround until https://github.com/home-assistant/supervisor/issues/5021 is fixed"
|
"remoteUser": "vscode",
|
||||||
|
"appPort": ["7123:8123", "7180:80", "7357:4357"],
|
||||||
|
"postStartCommand": "bash devcontainer_bootstrap",
|
||||||
"runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"],
|
"runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"],
|
||||||
|
"workspaceFolder": "/mnt/supervisor/addons/local/${localWorkspaceFolderBasename}",
|
||||||
|
"workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind,consistency=cached",
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}",
|
||||||
|
"SUPERVISOR_CHANNEL": "stable"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {},
|
||||||
|
"ghcr.io/jungaretti/features/ripgrep:1": {}
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
@@ -27,5 +36,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mounts": ["type=volume,target=/var/lib/docker"]
|
"mounts": [
|
||||||
|
"type=volume,target=/var/lib/docker",
|
||||||
|
"type=volume,target=/var/lib/containerd",
|
||||||
|
"type=volume,target=/mnt/supervisor",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
Executable
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Remove an add-on's registry image before the Supervisor starts so it must
|
||||||
|
# build the current checkout's Dockerfile.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
config="${1:-config.json}"
|
||||||
|
|
||||||
|
if [ ! -f "$config" ]; then
|
||||||
|
echo "ERROR: Add-on config not found: ${config}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! jq empty "$config"; then
|
||||||
|
echo "ERROR: Invalid add-on config: ${config}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if jq -e 'has("image")' "$config" > /dev/null; then
|
||||||
|
temporary_config=$(mktemp "${config}.XXXXXX")
|
||||||
|
trap 'rm -f "$temporary_config"' EXIT
|
||||||
|
|
||||||
|
jq 'del(.image)' "$config" > "$temporary_config"
|
||||||
|
mv "$temporary_config" "$config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if jq -e 'has("image")' "$config" > /dev/null; then
|
||||||
|
echo "ERROR: Failed to remove image from ${config}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Registry image disabled; the Supervisor will build the current checkout"
|
||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# run-integration-test.sh - Boot the Supervisor and run setup-ha.sh against it.
|
||||||
|
#
|
||||||
|
# Usage: bash .devcontainer/run-integration-test.sh [channel]
|
||||||
|
#
|
||||||
|
# The channel (stable, beta or dev) selects which Home Assistant release the
|
||||||
|
# Supervisor installs. It defaults to stable so that CI tests what users actually
|
||||||
|
# run; the devcontainer image itself defaults to dev, which means unreleased
|
||||||
|
# Home Assistant changes would otherwise land straight in CI.
|
||||||
|
#
|
||||||
|
# Set DEBUG=1 to stream the Supervisor log instead of capturing it.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export SUPERVISOR_CHANNEL="${1:-stable}"
|
||||||
|
SUPERVISOR_LOG="/tmp/supervisor.log"
|
||||||
|
|
||||||
|
bash .devcontainer/prepare-local-addon.sh
|
||||||
|
|
||||||
|
# The v6 devcontainer exposes host AppArmor through OS Agent, but profiles
|
||||||
|
# applied to containers in its nested Docker daemon break the PostgreSQL image.
|
||||||
|
sudo systemctl stop haos-agent
|
||||||
|
|
||||||
|
# supervisor_run hardcodes SUPERVISOR_DEV=1, which makes the Supervisor force its
|
||||||
|
# update channel to dev and so install nightly Home Assistant builds whatever
|
||||||
|
# channel is asked for.
|
||||||
|
sudo sed -i '/-e SUPERVISOR_DEV=1/d' /usr/bin/supervisor_run
|
||||||
|
|
||||||
|
# The channel is otherwise only reachable through the Supervisor API, by which
|
||||||
|
# point it has already installed Home Assistant, so seed it before first boot.
|
||||||
|
sudo mkdir -p /mnt/supervisor
|
||||||
|
printf '{"channel": "%s"}\n' "${SUPERVISOR_CHANNEL}" | sudo tee /mnt/supervisor/updater.json > /dev/null
|
||||||
|
|
||||||
|
echo "Home Assistant channel: ${SUPERVISOR_CHANNEL}"
|
||||||
|
|
||||||
|
if [ -n "${DEBUG:-}" ]; then
|
||||||
|
supervisor_run 2>&1 | tee "${SUPERVISOR_LOG}" &
|
||||||
|
else
|
||||||
|
supervisor_run > "${SUPERVISOR_LOG}" 2>&1 &
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! bash .devcontainer/setup-ha.sh; then
|
||||||
|
echo "::group::Supervisor log (last 300 lines, image pull progress omitted)"
|
||||||
|
grep -v -E "docker_image_pull_update|pull_progress" "${SUPERVISOR_LOG}" | tail -n 300
|
||||||
|
echo "::endgroup::"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Executable
+504
@@ -0,0 +1,504 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# setup-ha.sh - Idempotent Home Assistant setup for addon 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 addon
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# --- Addon-specific configuration ---
|
||||||
|
ADDON_NAME="$(jq -r '.name' config.json)"
|
||||||
|
ADDON_SLUG="local_$(jq -r '.slug' config.json)"
|
||||||
|
ADDON_PORT="$(jq -r '.ports | keys[0] | split("/") | .[0]' config.json)"
|
||||||
|
ADDON_READY_LOG="TeslaMateWeb.Endpoint"
|
||||||
|
TIMEZONE="Europe/London"
|
||||||
|
|
||||||
|
# --- Home Assistant configuration ---
|
||||||
|
HA_HOST="localhost"
|
||||||
|
# Core binds port 80 under the Supervisor from 2026.8 onwards, and 8123 before
|
||||||
|
# that, so wait_for_ha resolves the port at runtime rather than assuming one.
|
||||||
|
HA_PORTS=(80 8123)
|
||||||
|
HA_URL=""
|
||||||
|
CLIENT_ID=""
|
||||||
|
ALEXBELGIUM_REPO="https://github.com/alexbelgium/hassio-addons"
|
||||||
|
ADMIN_USER="admin"
|
||||||
|
ADMIN_PASS="pass"
|
||||||
|
|
||||||
|
# --- PostgreSQL configuration ---
|
||||||
|
POSTGRES_PASSWORD="homeassistant"
|
||||||
|
POSTGRES_USER="postgres"
|
||||||
|
POSTGRES_DB="${ADDON_NAME,,}"
|
||||||
|
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"
|
||||||
|
|
||||||
|
# --- Helpers ---
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
log() { echo -e "${CYAN}==>${NC} ${BOLD}$*${NC}"; }
|
||||||
|
warn() { echo -e "${YELLOW}WARNING:${NC} $*" >&2; }
|
||||||
|
err() { echo -e "${RED}ERROR:${NC} $*" >&2; exit 1; }
|
||||||
|
ok() { echo -e "${CYAN}==>${NC} ${GREEN}$*${NC}"; }
|
||||||
|
|
||||||
|
# Parse JSON, aborting with the offending body instead of a bare jq parse error
|
||||||
|
jq_or_die() {
|
||||||
|
local json="$1" filter="$2" context="$3"
|
||||||
|
if [ -z "$json" ] || ! jq empty <<< "$json" 2>/dev/null; then
|
||||||
|
err "${context}: expected JSON, got: ${json}"
|
||||||
|
fi
|
||||||
|
jq -r "$filter" <<< "$json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
docker exec hassio_cli sh -c \
|
||||||
|
'curl -s -X '"${method}"' -H "Authorization: Bearer $SUPERVISOR_TOKEN" -H "Content-Type: application/json" http://supervisor'"${endpoint}"
|
||||||
|
}
|
||||||
|
|
||||||
|
supervisor_api_with_body() {
|
||||||
|
local method="$1" endpoint="$2" body="$3"
|
||||||
|
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}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Query a specific jq field from a Supervisor API response.
|
||||||
|
# Supervisor reports HTTP errors as plain text (e.g. "404: Not Found"), which is
|
||||||
|
# not JSON, so surface those and return nothing to let callers keep polling.
|
||||||
|
supervisor_api_jq() {
|
||||||
|
local endpoint="$1" jq_filter="$2"
|
||||||
|
local response
|
||||||
|
response=$(supervisor_api GET "${endpoint}")
|
||||||
|
if [ -z "$response" ] || ! jq empty <<< "$response" 2>/dev/null; then
|
||||||
|
warn "Supervisor API ${endpoint} returned non-JSON: ${response}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
jq -r "$jq_filter" <<< "$response"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 ---
|
||||||
|
|
||||||
|
# Print an addon's log to help diagnose a failure
|
||||||
|
dump_addon_logs() {
|
||||||
|
local slug="$1"
|
||||||
|
echo "::group::${slug} logs"
|
||||||
|
supervisor_api GET "/addons/${slug}/logs" || true
|
||||||
|
echo "::endgroup::"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Best-effort Home Assistant Core state from the Supervisor, for diagnostics
|
||||||
|
ha_core_state() {
|
||||||
|
local info
|
||||||
|
info=$(supervisor_api GET "/core/info" 2>/dev/null) || true
|
||||||
|
if [ -n "$info" ] && jq empty <<< "$info" 2>/dev/null; then
|
||||||
|
jq -r '.data.state // "unknown"' <<< "$info"
|
||||||
|
else
|
||||||
|
echo "unavailable"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# HA answers on its HTTP port long before the API is usable, and from 2026.8 the
|
||||||
|
# port itself moved from 8123 to 80 under the Supervisor. Older releases still use
|
||||||
|
# 8123, and during onboarding new releases answer on both - but 8123 only serves a
|
||||||
|
# redirect that Core tears down once onboarding completes. So port 80 is tried
|
||||||
|
# first, redirects are deliberately not followed, and a port only counts as ready
|
||||||
|
# when it serves the onboarding payload itself rather than merely valid JSON.
|
||||||
|
HA_PROBE=""
|
||||||
|
|
||||||
|
ha_probe() {
|
||||||
|
local port="$1" raw status body
|
||||||
|
raw=$(curl -s --max-time 5 -w $'\n%{http_code}' "http://${HA_HOST}:${port}/api/onboarding" 2>/dev/null || true)
|
||||||
|
status=${raw##*$'\n'}
|
||||||
|
body=${raw%$'\n'*}
|
||||||
|
HA_PROBE="${HA_PROBE:+${HA_PROBE} }port=${port} http=${status:-none} body=${body:0:80}"
|
||||||
|
jq -e 'type == "array" and (.[0] | has("step"))' <<< "$body" > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sets HA_URL and CLIENT_ID on success. Must not run in a subshell.
|
||||||
|
ha_resolve_url() {
|
||||||
|
local port
|
||||||
|
HA_PROBE=""
|
||||||
|
for port in "${HA_PORTS[@]}"; do
|
||||||
|
if ha_probe "$port"; then
|
||||||
|
HA_URL="http://${HA_HOST}:${port}"
|
||||||
|
CLIENT_ID="${HA_URL}/"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_ha() {
|
||||||
|
log "Waiting for Home Assistant to be ready..."
|
||||||
|
local max_attempts=120
|
||||||
|
local attempt=0
|
||||||
|
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||||
|
if ha_resolve_url; then
|
||||||
|
ok "Home Assistant is ready at ${HA_URL}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ $((attempt % 6)) -eq 0 ]; then
|
||||||
|
log " attempt ${attempt}/${max_attempts}: core=$(ha_core_state) ${HA_PROBE}"
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
err "Home Assistant did not become ready in time (core=$(ha_core_state) ${HA_PROBE})"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Step 1: Complete onboarding ---
|
||||||
|
|
||||||
|
complete_onboarding() {
|
||||||
|
log "Checking onboarding status..."
|
||||||
|
local onboarding
|
||||||
|
onboarding=$(curl -sfL "${HA_URL}/api/onboarding")
|
||||||
|
|
||||||
|
local user_done
|
||||||
|
user_done=$(jq_or_die "$onboarding" '.[] | select(.step == "user") | .done' "Onboarding status")
|
||||||
|
|
||||||
|
if [ "$user_done" = "true" ]; then
|
||||||
|
ok "Onboarding already completed"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Creating admin user '${ADMIN_USER}'..."
|
||||||
|
local auth_response
|
||||||
|
auth_response=$(curl -sfL -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\"}")
|
||||||
|
|
||||||
|
local auth_code
|
||||||
|
auth_code=$(jq_or_die "$auth_response" '.auth_code' "Create admin user")
|
||||||
|
|
||||||
|
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 -sfL -X POST "${HA_URL}/auth/token" \
|
||||||
|
--data-urlencode "grant_type=authorization_code" \
|
||||||
|
--data-urlencode "code=${auth_code}" \
|
||||||
|
--data-urlencode "client_id=${CLIENT_ID}")
|
||||||
|
|
||||||
|
local access_token
|
||||||
|
access_token=$(jq_or_die "$token_response" '.access_token' "Token exchange")
|
||||||
|
|
||||||
|
if [ -z "$access_token" ] || [ "$access_token" = "null" ]; then
|
||||||
|
err "Failed to get access token: ${token_response}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Complete remaining onboarding steps
|
||||||
|
log "Completing onboarding steps..."
|
||||||
|
curl -sfL -X POST "${HA_URL}/api/onboarding/core_config" \
|
||||||
|
-H "Authorization: Bearer ${access_token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{}" > /dev/null 2>&1 || true
|
||||||
|
|
||||||
|
curl -sfL -X POST "${HA_URL}/api/onboarding/analytics" \
|
||||||
|
-H "Authorization: Bearer ${access_token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{}" > /dev/null 2>&1 || true
|
||||||
|
|
||||||
|
curl -sfL -X POST "${HA_URL}/api/onboarding/integration" \
|
||||||
|
-H "Authorization: Bearer ${access_token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"client_id\":\"${CLIENT_ID}\",\"redirect_uri\":\"${HA_URL}/\"}" > /dev/null 2>&1 || true
|
||||||
|
|
||||||
|
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_with_body 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
|
||||||
|
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
|
||||||
|
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_with_body 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"
|
||||||
|
wait_for_postgres_ready "$postgres_slug"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
err "PostgreSQL addon did not start in time"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_postgres_ready() {
|
||||||
|
local postgres_slug="$1"
|
||||||
|
# Supervisor renamed add-on containers from addon_* to app_*, so try both.
|
||||||
|
local containers=("app_${postgres_slug}" "addon_${postgres_slug}")
|
||||||
|
|
||||||
|
log "Waiting for PostgreSQL database to accept connections..."
|
||||||
|
local max_attempts=60
|
||||||
|
local attempt=0
|
||||||
|
local container probe
|
||||||
|
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||||
|
for container in "${containers[@]}"; do
|
||||||
|
if probe=$(docker exec -e "PGPASSWORD=${POSTGRES_PASSWORD}" "$container" \
|
||||||
|
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT 1" 2>&1); then
|
||||||
|
ok "PostgreSQL database is ready"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
sleep 5
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
warn "Last psql output: ${probe}"
|
||||||
|
dump_addon_logs "$postgres_slug"
|
||||||
|
err "PostgreSQL database did not become ready in time"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Step 5: Install and configure addon ---
|
||||||
|
|
||||||
|
install_addon() {
|
||||||
|
log "Checking ${ADDON_NAME} addon status..."
|
||||||
|
local version
|
||||||
|
version=$(supervisor_api_jq "/addons/${ADDON_SLUG}/info" '.data.version // empty')
|
||||||
|
|
||||||
|
if [ -n "$version" ]; then
|
||||||
|
local state
|
||||||
|
state=$(supervisor_api_jq "/addons/${ADDON_SLUG}/info" '.data.state')
|
||||||
|
ok "${ADDON_NAME} addon already installed (version: ${version}, state: ${state})"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Installing ${ADDON_NAME} addon..."
|
||||||
|
supervisor_api POST "/addons/${ADDON_SLUG}/install" > /dev/null
|
||||||
|
|
||||||
|
# Wait for installation to complete (the TeslaMate image is large)
|
||||||
|
log "Waiting for ${ADDON_NAME} installation..."
|
||||||
|
local max_attempts=120
|
||||||
|
local attempt=0
|
||||||
|
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||||
|
version=$(supervisor_api_jq "/addons/${ADDON_SLUG}/info" '.data.version // empty')
|
||||||
|
if [ -n "$version" ]; then
|
||||||
|
ok "${ADDON_NAME} addon installed (version: ${version})"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
err "${ADDON_NAME} addon installation timed out"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_and_start_addon() {
|
||||||
|
local postgres_slug="$1"
|
||||||
|
|
||||||
|
# Derive the postgres hostname from its slug (underscores → hyphens)
|
||||||
|
local postgres_host
|
||||||
|
postgres_host=$(echo "$postgres_slug" | tr '_' '-')
|
||||||
|
|
||||||
|
log "Configuring ${ADDON_NAME} addon (db host: ${postgres_host})..."
|
||||||
|
local current_options overrides options_json
|
||||||
|
current_options=$(supervisor_api_jq "/addons/${ADDON_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)}')
|
||||||
|
|
||||||
|
supervisor_api_with_body POST "/addons/${ADDON_SLUG}/options" \
|
||||||
|
"${options_json}" > /dev/null
|
||||||
|
|
||||||
|
# Check if already running
|
||||||
|
local state
|
||||||
|
state=$(supervisor_api_jq "/addons/${ADDON_SLUG}/info" '.data.state')
|
||||||
|
|
||||||
|
if [ "$state" = "started" ]; then
|
||||||
|
ok "${ADDON_NAME} addon already running"
|
||||||
|
wait_for_addon_ready
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Starting ${ADDON_NAME} addon..."
|
||||||
|
supervisor_api POST "/addons/${ADDON_SLUG}/start" > /dev/null
|
||||||
|
|
||||||
|
# Wait for addon state to become "started"
|
||||||
|
log "Waiting for ${ADDON_NAME} addon to start..."
|
||||||
|
local max_attempts=60
|
||||||
|
local attempt=0
|
||||||
|
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||||
|
state=$(supervisor_api_jq "/addons/${ADDON_SLUG}/info" '.data.state')
|
||||||
|
if [ "$state" = "started" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
if [ "$state" != "started" ]; then
|
||||||
|
dump_addon_logs "${ADDON_SLUG}"
|
||||||
|
err "${ADDON_NAME} addon did not start in time (state: ${state})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait_for_addon_ready
|
||||||
|
ok "${ADDON_NAME} addon started and ready"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_addon_ready() {
|
||||||
|
log "Waiting for ${ADDON_NAME} to be ready (listening on port ${ADDON_PORT})..."
|
||||||
|
local max_attempts=60
|
||||||
|
local attempt=0
|
||||||
|
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||||
|
local logs
|
||||||
|
logs=$(supervisor_api GET "/addons/${ADDON_SLUG}/logs" 2>/dev/null || true)
|
||||||
|
if echo "$logs" | grep -qF "$ADDON_READY_LOG"; then
|
||||||
|
ok "${ADDON_NAME} is ready"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
dump_addon_logs "${ADDON_SLUG}"
|
||||||
|
err "${ADDON_NAME} did not become ready in time (never saw '${ADDON_READY_LOG}' in logs)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main ---
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "Setting up Home Assistant for ${ADDON_NAME} development"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
wait_for_ha
|
||||||
|
complete_onboarding
|
||||||
|
add_addon_repo
|
||||||
|
|
||||||
|
install_postgres "$POSTGRES_SLUG"
|
||||||
|
configure_and_start_postgres "$POSTGRES_SLUG"
|
||||||
|
install_addon
|
||||||
|
configure_and_start_addon "$POSTGRES_SLUG"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
ok "Setup complete!"
|
||||||
|
local host_port
|
||||||
|
# Mirrors the appPort list in devcontainer.json.
|
||||||
|
case "${HA_URL##*:}" in
|
||||||
|
80) host_port=7180 ;;
|
||||||
|
*) host_port=7123 ;;
|
||||||
|
esac
|
||||||
|
log " Home Assistant: ${HA_URL} (user: ${ADMIN_USER}, pass: ${ADMIN_PASS})"
|
||||||
|
log " from the host: http://localhost:${host_port}"
|
||||||
|
log " PostgreSQL: ${POSTGRES_SLUG} (user: ${POSTGRES_USER}, db: ${POSTGRES_DB})"
|
||||||
|
log " ${ADDON_NAME}: accessible via Home Assistant ingress"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": [
|
||||||
"config:recommended",
|
"config:recommended",
|
||||||
":disableDependencyDashboard"
|
":disableDependencyDashboard",
|
||||||
|
"helpers:pinGitHubActionDigests"
|
||||||
],
|
],
|
||||||
"prHourlyLimit": 4,
|
"prHourlyLimit": 4,
|
||||||
"prBodyTemplate": "{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}",
|
"prBodyTemplate": "{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}",
|
||||||
@@ -12,10 +13,9 @@
|
|||||||
"customManagers": [
|
"customManagers": [
|
||||||
{
|
{
|
||||||
"customType": "regex",
|
"customType": "regex",
|
||||||
"fileMatch": "^build.json$",
|
"fileMatch": "(^|/)Dockerfile$",
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"\"teslamate/teslamate:(?<currentValue>\\d+\\.\\d+\\.\\d+)\"",
|
"ARG teslamate_version=(?<currentValue>\\d+\\.\\d+\\.\\d+)"
|
||||||
"\"teslamate_version\": \"(?<currentValue>\\d+\\.\\d+\\.\\d+)\""
|
|
||||||
],
|
],
|
||||||
"datasourceTemplate": "github-releases",
|
"datasourceTemplate": "github-releases",
|
||||||
"depNameTemplate": "teslamate-org/teslamate"
|
"depNameTemplate": "teslamate-org/teslamate"
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
name: Integration Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- config.json
|
||||||
|
- Dockerfile
|
||||||
|
- rootfs/**
|
||||||
|
- .devcontainer/**
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- config.json
|
||||||
|
- Dockerfile
|
||||||
|
- rootfs/**
|
||||||
|
- .devcontainer/**
|
||||||
|
# Home Assistant and the third-party add-on repository this test installs both
|
||||||
|
# move independently of this repo, so the test can break with no change here.
|
||||||
|
# Run it on a schedule to catch that on main rather than on an unrelated pull
|
||||||
|
# request that happens to touch one of the paths above.
|
||||||
|
schedule:
|
||||||
|
- cron: "17 6 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
integration:
|
||||||
|
name: Integration test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
|
- name: Run integration test in devcontainer
|
||||||
|
uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3.1900000450
|
||||||
|
with:
|
||||||
|
runCmd: bash .devcontainer/run-integration-test.sh stable
|
||||||
|
|
||||||
|
# Early warning for Home Assistant changes that have not reached stable yet.
|
||||||
|
# Deliberately never runs on a pull request, so a broken beta cannot block a
|
||||||
|
# merge, and is non-blocking when it does run.
|
||||||
|
canary:
|
||||||
|
name: Integration test (beta)
|
||||||
|
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
continue-on-error: true
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
|
- name: Run integration test in devcontainer
|
||||||
|
uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3.1900000450
|
||||||
|
with:
|
||||||
|
runCmd: bash .devcontainer/run-integration-test.sh beta
|
||||||
@@ -14,9 +14,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: ⤵️ Check out code from GitHub
|
- name: ⤵️ Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: 🚀 Run Home Assistant Add-on Lint
|
- name: 🚀 Run Home Assistant Add-on Lint
|
||||||
uses: frenck/action-addon-linter@v2
|
uses: frenck/action-addon-linter@b9cfd1bc62ba60d2c4fc96317b5f7ada5923f4a7 # v2.21.1
|
||||||
with:
|
with:
|
||||||
path: "./"
|
path: "./"
|
||||||
|
|||||||
@@ -11,41 +11,76 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
init:
|
||||||
name: Publish
|
name: Initialize build
|
||||||
runs-on: ubuntu-latest
|
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
|
- 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:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
fail-fast: false
|
||||||
arch: ["amd64", "armv7", "aarch64"]
|
matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- 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@v3.3.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and publish
|
- name: Build and publish
|
||||||
uses: home-assistant/builder@2024.08.2
|
uses: home-assistant/builder/actions/build-image@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
|
||||||
with:
|
with:
|
||||||
args: |
|
arch: ${{ matrix.arch }}
|
||||||
--${{ matrix.arch }} \
|
image: ${{ matrix.image }}
|
||||||
--version ${{ env.latest_release }} \
|
image-tags: |
|
||||||
--target /data/ \
|
${{ needs.init.outputs.version }}
|
||||||
--addon
|
latest
|
||||||
|
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
push: "true"
|
||||||
|
cosign: "false"
|
||||||
|
version: ${{ needs.init.outputs.version }}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
path: teslamate
|
path: teslamate
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
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
|
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
|
- name: Increment semver
|
||||||
id: semver
|
id: semver
|
||||||
uses: matt-FFFFFF/simple-semver@v0.1.1
|
uses: matt-FFFFFF/simple-semver@967d66a65b0c5afae2a815ab2d311f66a32293ab # v0.1.1
|
||||||
with:
|
with:
|
||||||
semver-input: ${{ env.latest_release }}
|
semver-input: ${{ env.latest_release }}
|
||||||
increment: ${{ github.event.inputs.semverIncrement }}
|
increment: ${{ github.event.inputs.semverIncrement }}
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
git push --tags
|
git push --tags
|
||||||
working-directory: ${{ github.workspace }}/teslamate
|
working-directory: ${{ github.workspace }}/teslamate
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: softprops/action-gh-release@v2.2.1
|
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{ steps.semver.outputs.semver }}
|
tag_name: v${{ steps.semver.outputs.semver }}
|
||||||
body_path: CHANGELOG.txt
|
body_path: CHANGELOG.txt
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
prerelease: false
|
prerelease: false
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Checkout addon repo
|
- name: Checkout addon repo
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
path: repo
|
path: repo
|
||||||
repository: lildude/ha-addons
|
repository: lildude/ha-addons
|
||||||
|
|||||||
+52
-18
@@ -5,9 +5,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- build.yaml
|
|
||||||
- config.yaml
|
|
||||||
- build.json
|
|
||||||
- config.json
|
- config.json
|
||||||
- Dockerfile
|
- Dockerfile
|
||||||
- rootfs/**
|
- rootfs/**
|
||||||
@@ -15,34 +12,71 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- build.yaml
|
|
||||||
- config.yaml
|
|
||||||
- build.json
|
|
||||||
- config.json
|
- config.json
|
||||||
- Dockerfile
|
- Dockerfile
|
||||||
- rootfs/**
|
- rootfs/**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
init:
|
||||||
|
name: Initialize build
|
||||||
runs-on: ubuntu-latest
|
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
|
- 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:
|
strategy:
|
||||||
matrix:
|
fail-fast: false
|
||||||
arch: ["amd64", "armv7", "aarch64"]
|
matrix: ${{ fromJSON(needs.init.outputs.matrix) }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Build add-on
|
- name: Build add-on
|
||||||
uses: home-assistant/builder@2024.08.2
|
uses: home-assistant/builder/actions/build-image@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
|
||||||
with:
|
with:
|
||||||
args: |
|
arch: ${{ matrix.arch }}
|
||||||
--test \
|
image: ${{ matrix.image }}
|
||||||
--${{ matrix.arch }} \
|
image-tags: test
|
||||||
--target /data/ \
|
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
--addon
|
load: "true"
|
||||||
|
cosign: "false"
|
||||||
|
version: test
|
||||||
|
|
||||||
- name: Image labels
|
- name: Image labels
|
||||||
run: |
|
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": [
|
"json.schemas": [
|
||||||
{
|
|
||||||
"fileMatch": [
|
|
||||||
"**/build.json"
|
|
||||||
],
|
|
||||||
"url": "https://raw.githubusercontent.com/frenck/action-addon-linter/main/src/build.schema.json"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fileMatch": [
|
"fileMatch": [
|
||||||
"**/config.json"
|
"**/config.json"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ A self-hosted data logger for your Tesla 🚘
|
|||||||
[TeslaMate][teslamate] is a powerful, self-hosted data logger for your Tesla.
|
[TeslaMate][teslamate] is a powerful, self-hosted data logger for your Tesla.
|
||||||
|
|
||||||
- Written in **[Elixir][elixir]**
|
- Written in **[Elixir][elixir]**
|
||||||
- Data is stored in a **Postgres** database
|
- Data is stored in a **PostgreSQL** database
|
||||||
- Visualization and data analysis with **Grafana**
|
- Visualization and data analysis with **Grafana**
|
||||||
- Vehicle data is published to a local **MQTT** Broker
|
- Vehicle data is published to a local **MQTT** Broker
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ This add-on allows you to run [TeslaMate][teslamate] on your Home Assistant serv
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The configuration is self-explanatory, but essentially we need details about accessing the PostgreSQL database, Grafana and optionally MQTT.
|
The configuration is self-explanatory, but essentially we need details about accessing the PostgreSQL database, Grafana and optionally MQTT.
|
||||||
|
You will also need to configure TeslaMate (see "TeslaMate Configuration" below) so the links to TeslaMate in the Grafana dashboards work.
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
@@ -76,7 +77,6 @@ custom_plugins:
|
|||||||
- name: panodata-map-panel
|
- name: panodata-map-panel
|
||||||
url: https://github.com/panodata/panodata-map-panel/releases/download/0.16.0/panodata-map-panel-0.16.0.zip
|
url: https://github.com/panodata/panodata-map-panel/releases/download/0.16.0/panodata-map-panel-0.16.0.zip
|
||||||
unsigned: true
|
unsigned: true
|
||||||
custom_plugins: []
|
|
||||||
env_vars:
|
env_vars:
|
||||||
- name: GF_SECURITY_ADMIN_USER
|
- name: GF_SECURITY_ADMIN_USER
|
||||||
value: <youruser>
|
value: <youruser>
|
||||||
@@ -107,7 +107,7 @@ Once you have Grafana up and running, you'll need to configure a data source to
|
|||||||
|
|
||||||
‼️ **Note**: The name must be `TeslaMate` as all the dashboards expect the datasource to use this name.
|
‼️ **Note**: The name must be `TeslaMate` as all the dashboards expect the datasource to use this name.
|
||||||
|
|
||||||
![Grafana Postgres data source][grafana-datasource]
|
![Grafana PostgreSQL data source][grafana-datasource]
|
||||||
|
|
||||||
#### Other Options
|
#### Other Options
|
||||||
|
|
||||||
@@ -126,6 +126,96 @@ Once you have Grafana up and running, you'll need to configure a data source to
|
|||||||
|
|
||||||
Note: These will also overwrite any environment variable set using the configuration options above.
|
Note: These will also overwrite any environment variable set using the configuration options above.
|
||||||
|
|
||||||
|
## TeslaMate Configuration
|
||||||
|
|
||||||
|
In order for the links in the Grafana dashboards and the TeslaMate UI to link to the correct locations, you will also need to configure TeslaMate.
|
||||||
|
Once you have started the add-on, look at the log output for the section which looks like this and take note of the two lines with `=>`:
|
||||||
|
|
||||||
|
```
|
||||||
|
[09:26:32] INFO: Configure TeslaMate settings by adding these values
|
||||||
|
to the URL you use to access your Home Assistant instance:
|
||||||
|
|
||||||
|
=> Web App: /api/hassio_ingress/ljiIc6sOVGOSQfTRjscvLVJJS5Rxp33gsdEtf9y3oQY
|
||||||
|
=> Dashboards: /api/hassio_ingress/G9ocwA44wt9Bcba8LvP8tDhlfFiFnnPftOZBwp-Pgzs
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
From the add-on Info tab, click "Open Web UI", enter the Tesla API access and refresh tokens if you haven't already, and then go to TeslaMate's Settings.
|
||||||
|
Scroll to the very bottom and set each of the URLs to the URL you use to access your Home Assistant instance with each of the above appended.
|
||||||
|
|
||||||
|
For example, if you access your Home Assistant instance at `https://ha.example.com`, set each as follows:
|
||||||
|
|
||||||
|
- Web App: `https://ha.example.com/api/hassio_ingress/ljiIc6sOVGOSQfTRjscvLVJJS5Rxp33gsdEtf9y3oQY`
|
||||||
|
- Dashboards: `https://ha.example.com/api/hassio_ingress/G9ocwA44wt9Bcba8LvP8tDhlfFiFnnPftOZBwp-Pgzs`
|
||||||
|
|
||||||
|
Do _not_ use these values. Use the values from your log output.
|
||||||
|
|
||||||
|
**Note:** If you do not see the links in your add-on logs, clear the values in the TeslaMate configuration and restart the add-on.
|
||||||
|
|
||||||
|
## Migrate TeslaMate to a Different PostgreSQL Server
|
||||||
|
|
||||||
|
In order to migrate TeslaMate to a different PostgreSQL server, you will need to make a backup of the original data and then restore it to the destination server.
|
||||||
|
This isn't much of a challenge for the seasoned sysadmin familiar with Docker, PostgreSQL and the command line, however most Home Assistant users aren't expert sysadmins so this guide if for you.
|
||||||
|
|
||||||
|
1. Ensure you have both the current and new PostgreSQL servers installed and running and ensure you have the same users and roles configured on both.
|
||||||
|
|
||||||
|
2. Stop the TeslaMate add-on.
|
||||||
|
|
||||||
|
3. Install the [pgAdmin4](https://github.com/expaso/hassos-addons) add-on.
|
||||||
|
|
||||||
|
4. Start the pgAdmin4 add-on and open the web UI
|
||||||
|
|
||||||
|
5. Make a new connection to the current PostgreSQL server by clicking "Add New Server".
|
||||||
|
|
||||||
|
1. Give it a recognisable name in the General tab.
|
||||||
|
|
||||||
|
2. Switch to the Connection tab and enter the hostname, port, username and password currently used.
|
||||||
|
You can find these in your TeslaMate add-on configuration.
|
||||||
|
|
||||||
|
3. Click Save
|
||||||
|
|
||||||
|
6. Click the dropdown next to the new server -> Databases and then select the database you use for TeslaMate.
|
||||||
|
This will be the same name you configured in the TeslaMate add-on configuration.
|
||||||
|
|
||||||
|
7. Go to Tools -> Backup, enter a filename, if you know what you're doing feel free to make other customisations but it's not essential, and then click Backup. [^1]
|
||||||
|
|
||||||
|
8. Once the backup has finished, repeats step 5 for your new PostgreSQL server to establish a connection to your new PostgreSQL server.
|
||||||
|
|
||||||
|
9. Select the new server, if not already selected, and go to Object -> Create -> Database and enter the same name and owner as used on your old server and click Save.
|
||||||
|
|
||||||
|
10. If the database is not already selected, select it and then go to Tools -> Restore, enter the filename you entered in step 7 and click Restore.
|
||||||
|
You may need to use the file browser to locate the file. If you didn't change the path in step 7, the backup should be in `/root/`.
|
||||||
|
|
||||||
|
11. Once the restore has completed, go to the TeslaMate add-on settings and update the database settings to point to your new database server.
|
||||||
|
The easiest approach is to use the steps in [Migrating to this version of the add-on][migrate-addon] further down in this file and change the `database_host` to the name of your new PostgreSQL server.
|
||||||
|
|
||||||
|
12. Update the Grafana configuration by going to the Grafana add-on -> Open web UI -> Grafana logo in top left -> Connections -> Datasources, select the TeslaMate source and change the Host URL to your new PostgreSQL server and click "Save & test".
|
||||||
|
|
||||||
|
13. Start the TeslaMate add-on and verifying everything is working as it was before. [^2]
|
||||||
|
|
||||||
|
You can now uninstall the pgAdmin4 add-on if you want and stop and uninstall your old PostgreSQL server if you're not using it for anything else.
|
||||||
|
|
||||||
|
## Import TeslaMate Backup
|
||||||
|
|
||||||
|
If you have made a backup of a TeslaMate installation as per the [TeslaMate backup documentation][teslamate-backup] and now have a `teslamate.bak` file you want to use with this add-on, you can do so as follows:
|
||||||
|
|
||||||
|
1. Install this TeslaMate add-on if you haven't already, but don't start it.
|
||||||
|
|
||||||
|
2. Install a PostgreSQL server and start it if you haven't already.
|
||||||
|
We recommend the [PostgreSQL 17 add-on][alexbelgium-postgres] from @alexbelgium's repository, or [TimescaleDB][timescaledb] (for advanced users).
|
||||||
|
|
||||||
|
3. Perform steps 3, 4 and 5 from [Migrate TeslaMate to a Different PostreSQL Server][migrate-psql] above.
|
||||||
|
|
||||||
|
4. Transfer the `teslamate.bck` file to the `/share/teslamate` folder on your Home Assistant instance.
|
||||||
|
You can do this using the [Samba][samba-addon] or [SSH][ssh-addon] add-ons.
|
||||||
|
|
||||||
|
5. Perform steps 9 and 10 from [Migrate TeslaMate to a Different PostreSQL Server][migrate-psql] above.
|
||||||
|
Note: the file location will be `/share/teslamate/teslamate.bck`.
|
||||||
|
|
||||||
|
6. Configure TeslaMate and Grafana as per the details at the top of this file.
|
||||||
|
|
||||||
|
7. Start TeslaMate.
|
||||||
|
|
||||||
## Data Import from TeslaFi
|
## Data Import from TeslaFi
|
||||||
|
|
||||||
It is now possible to import CSV data from TeslaFi, refer to the [official docs][teslafi-import].
|
It is now possible to import CSV data from TeslaFi, refer to the [official docs][teslafi-import].
|
||||||
@@ -143,12 +233,27 @@ Follow this process:
|
|||||||
|
|
||||||
5. Once imported sucessfully, delete the CSV files to avoid the import screen being presented.
|
5. Once imported sucessfully, delete the CSV files to avoid the import screen being presented.
|
||||||
|
|
||||||
|
[alexbelgium-postgres]: https://github.com/alexbelgium/hassio-addons/tree/master/postgres_17
|
||||||
[docker]: https://hub.docker.com/r/teslamate/teslamate
|
[docker]: https://hub.docker.com/r/teslamate/teslamate
|
||||||
[elixir]: https://elixir-lang.org/
|
[elixir]: https://elixir-lang.org/
|
||||||
[grafana-addon]: https://github.com/hassio-addons/addon-grafana
|
[grafana-addon]: https://github.com/hassio-addons/addon-grafana
|
||||||
[grafana-datasource]: https://raw.githubusercontent.com/lildude/ha-addon-teslamate/main/imgs/grafana-postgres.png
|
[grafana-datasource]: https://raw.githubusercontent.com/lildude/ha-addon-teslamate/main/imgs/grafana-postgres.png
|
||||||
|
[migrate-psql]: #migrate-teslamate-to-a-different-postresql-server
|
||||||
|
[migrate-addon]: https://github.com/lildude/ha-addon-teslamate/blob/main/README.md#migrating-to-this-version-of-the-add-on
|
||||||
[mosquitto-docs]: https://github.com/home-assistant/addons/blob/master/mosquitto/DOCS.md
|
[mosquitto-docs]: https://github.com/home-assistant/addons/blob/master/mosquitto/DOCS.md
|
||||||
|
[pgadmin4]: https://github.com/expaso/hassos-addons
|
||||||
[samba-addon]: https://github.com/home-assistant/addons/blob/master/samba/DOCS.md
|
[samba-addon]: https://github.com/home-assistant/addons/blob/master/samba/DOCS.md
|
||||||
[ssh-addon]: https://github.com/home-assistant/addons/blob/master/ssh/DOCS.md
|
[ssh-addon]: https://github.com/home-assistant/addons/blob/master/ssh/DOCS.md
|
||||||
[teslafi-import]: https://docs.teslamate.org/docs/import/teslafi
|
[teslafi-import]: https://docs.teslamate.org/docs/import/teslafi
|
||||||
[teslamate]: https://github.com/teslamate-org/teslamate/
|
[teslamate]: https://github.com/teslamate-org/teslamate/
|
||||||
|
[teslamate-backup]: https://docs.teslamate.org/docs/maintenance/backup_restore/#backup
|
||||||
|
[timescaledb]: https://github.com/expaso/hassos-addon-timescaledb
|
||||||
|
|
||||||
|
[^1]:
|
||||||
|
If the backup or restore fails due to a `server version mismatch` error, you will need to select the latest version of PostgreSQL in pgAdmin4 by going to File -> Paths -> Binary paths and add the correct path for each version.
|
||||||
|
For example, for PostgreSQL 14, enter `/usr/local/pgsql-14`, for PostgreSQL 15, enter `/usr/local/pgsql-15` etc.
|
||||||
|
|
||||||
|
[^2]:
|
||||||
|
If you see the `ERROR: type "earth" does not exist at character` error in the TeslaMate log, you can fix this by going back into pgAdmin4 and right click on the TeslaMate database -> "CREATE script".
|
||||||
|
Delete everything auto generated and paste in the [codeblock here](https://github.com/diogob/activerecord-postgres-earthdistance/issues/30#issuecomment-2122829036).
|
||||||
|
Click "Execute script" (It's the ▶️ symbol at the top).
|
||||||
|
|||||||
+13
-7
@@ -1,11 +1,9 @@
|
|||||||
ARG BUILD_FROM
|
ARG teslamate_version=4.3.0
|
||||||
ARG teslamate_version
|
FROM teslamate/grafana:${teslamate_version} AS grafana
|
||||||
FROM teslamate/grafana:${teslamate_version} as grafana
|
|
||||||
|
|
||||||
#---
|
#---
|
||||||
|
|
||||||
ARG BUILD_FROM
|
FROM teslamate/teslamate:${teslamate_version}
|
||||||
FROM $BUILD_FROM
|
|
||||||
|
|
||||||
ARG BUILD_ARCH
|
ARG BUILD_ARCH
|
||||||
ARG BASHIO_VERSION=0.16.2
|
ARG BASHIO_VERSION=0.16.2
|
||||||
@@ -33,8 +31,7 @@ RUN \
|
|||||||
tzdata \
|
tzdata \
|
||||||
xz-utils \
|
xz-utils \
|
||||||
&& S6_ARCH="${BUILD_ARCH}" \
|
&& S6_ARCH="${BUILD_ARCH}" \
|
||||||
&& if [ "${BUILD_ARCH}" = "amd64" ]; then S6_ARCH="x86_64"; \
|
&& if [ "${BUILD_ARCH}" = "amd64" ]; then S6_ARCH="x86_64"; fi \
|
||||||
elif [ "${BUILD_ARCH}" = "armv7" ]; then S6_ARCH="arm"; fi \
|
|
||||||
&& curl -Ls "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" | tar xpJ -C / \
|
&& curl -Ls "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" | tar xpJ -C / \
|
||||||
&& curl -Ls "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" | tar xpJ -C / \
|
&& curl -Ls "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" | tar xpJ -C / \
|
||||||
&& curl -Ls "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-noarch.tar.xz" | tar Jxp -C / \
|
&& curl -Ls "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-noarch.tar.xz" | tar Jxp -C / \
|
||||||
@@ -50,5 +47,14 @@ COPY rootfs /
|
|||||||
COPY --from=grafana /dashboards /dashboards
|
COPY --from=grafana /dashboards /dashboards
|
||||||
COPY --from=grafana /dashboards_internal /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
|
# S6-Overlay
|
||||||
ENTRYPOINT ["/init"]
|
ENTRYPOINT ["/init"]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ A self-hosted data logger for your Tesla 🚘
|
|||||||
[TeslaMate][teslamate] is a powerful, self-hosted data logger for your Tesla.
|
[TeslaMate][teslamate] is a powerful, self-hosted data logger for your Tesla.
|
||||||
|
|
||||||
- Written in **[Elixir][elixir]**
|
- Written in **[Elixir][elixir]**
|
||||||
- Data is stored in a **Postgres** database
|
- Data is stored in a **PostgreSQL** database
|
||||||
- Visualization and data analysis with **Grafana**
|
- Visualization and data analysis with **Grafana**
|
||||||
- Vehicle data is published to a local **MQTT** Broker
|
- Vehicle data is published to a local **MQTT** Broker
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ Thanks to @matt-FFFFFF for maintaining this add-on in the past.
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
TeslaMate needs a PostgreSQL database.
|
**TeslaMate v2 needs a PostgreSQL 16.7 or 17.3 or later database.**
|
||||||
I recommend using the [PostgreSQL add-on][alexbelgium-postgres] from @alexbelgium's repository if you aren't already using Postgres.
|
I recommend using the [PostgreSQL 17 add-on][alexbelgium-postgres] from @alexbelgium's repository, or [TimescaleDB][timescaledb] (for advanced users), if you aren't already using PostgreSQL.
|
||||||
|
|
||||||
To get the full experience, it is recommended that you also install the community [Grafana add-on][grafana-addon] and [MQTT integration][mqtt].
|
To get the full experience, it is recommended that you also install the community [Grafana add-on][grafana-addon] and [MQTT integration][mqtt].
|
||||||
|
|
||||||
@@ -46,6 +46,13 @@ To get the full experience, it is recommended that you also install the communit
|
|||||||
1. Check the logs of the add-on to see if everything went well.
|
1. Check the logs of the add-on to see if everything went well.
|
||||||
1. Click the `OPEN WEB UI` button to open TeslaMate.
|
1. Click the `OPEN WEB UI` button to open TeslaMate.
|
||||||
|
|
||||||
|
## Upgrading this add-on from v1 to v2
|
||||||
|
|
||||||
|
TeslaMate v2 introduced a hard requirement on the version of PostgreSQL database.
|
||||||
|
**You will need to use PostgreSQL 16.7 or 17.3 or later.**
|
||||||
|
If you are currently using an earlier version of PostgreSQL, you will need to backup your TeslaMate data, upgrade the database, and then restore the data.
|
||||||
|
You can find details on how to do this in the "_Migrate TeslaMate to a Different Postres Server_" section of this add-on's documentation tab or in the [DOCS.md][docs-md] file in the add-on repository.
|
||||||
|
|
||||||
## Migrating to this version of the add-on
|
## Migrating to this version of the add-on
|
||||||
|
|
||||||
Migrating to this version of the add-on should not result in any loss of data, but you can never be too careful, so I recommend you take a full backup of your Home Assistant instance and also a direct backup of the TeslaMate database as detailed in the [TeslaMate documentation][teslamate-backup] before proceeding.
|
Migrating to this version of the add-on should not result in any loss of data, but you can never be too careful, so I recommend you take a full backup of your Home Assistant instance and also a direct backup of the TeslaMate database as detailed in the [TeslaMate documentation][teslamate-backup] before proceeding.
|
||||||
@@ -67,9 +74,10 @@ Everything should pick up where it was before.
|
|||||||
|
|
||||||
[addon]: https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Flildude%2Fha-addons
|
[addon]: https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Flildude%2Fha-addons
|
||||||
[addons-repo]: https://github.com/lildude/ha-addons
|
[addons-repo]: https://github.com/lildude/ha-addons
|
||||||
[alexbelgium-postgres]: https://github.com/alexbelgium/hassio-addons/tree/master/postgres
|
[alexbelgium-postgres]: https://github.com/alexbelgium/hassio-addons/tree/master/postgres_17
|
||||||
[archs]: https://img.shields.io/badge/dynamic/json?color=green&label=Arch&query=%24.arch&url=https%3A%2F%2Fraw.githubusercontent.com%2Flildude%2Fha-addon-teslamate%2Fmain%2Fconfig.json
|
[archs]: https://img.shields.io/badge/dynamic/json?color=green&label=Arch&query=%24.arch&url=https%3A%2F%2Fraw.githubusercontent.com%2Flildude%2Fha-addon-teslamate%2Fmain%2Fconfig.json
|
||||||
[docker]: https://hub.docker.com/r/teslamate/teslamate
|
[docker]: https://hub.docker.com/r/teslamate/teslamate
|
||||||
|
[docs-md]: https://github.com/lildude/ha-addon-teslamate/blob/main/DOCS.md#migrate-teslamate-to-a-different-postgresql-server
|
||||||
[elixir]: https://elixir-lang.org/
|
[elixir]: https://elixir-lang.org/
|
||||||
[grafana-addon]: https://github.com/hassio-addons/addon-grafana
|
[grafana-addon]: https://github.com/hassio-addons/addon-grafana
|
||||||
[ingres-badge]: https://img.shields.io/badge/dynamic/json?label=Ingress&query=%24.ingress&url=https%3A%2F%2Fraw.githubusercontent.com%2Flildude%2Fha-addon-teslamate%2Fmain%2Fconfig.json
|
[ingres-badge]: https://img.shields.io/badge/dynamic/json?label=Ingress&query=%24.ingress&url=https%3A%2F%2Fraw.githubusercontent.com%2Flildude%2Fha-addon-teslamate%2Fmain%2Fconfig.json
|
||||||
@@ -77,5 +85,6 @@ Everything should pick up where it was before.
|
|||||||
[repo-btn]: https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg
|
[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
|
[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-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/
|
[teslamate]: https://github.com/teslamate-org/teslamate/
|
||||||
|
[timescaledb]: https://github.com/expaso/hassos-addon-timescaledb
|
||||||
|
|||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"build_from": {
|
|
||||||
"aarch64": "teslamate/teslamate:1.32.0",
|
|
||||||
"amd64": "teslamate/teslamate:1.32.0",
|
|
||||||
"armv7": "teslamate/teslamate:1.32.0"
|
|
||||||
},
|
|
||||||
"args": {
|
|
||||||
"teslamate_version": "1.32.0"
|
|
||||||
},
|
|
||||||
"codenotary": {
|
|
||||||
"signer": "colin@symr.io"
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+81
-74
@@ -1,76 +1,83 @@
|
|||||||
{
|
{
|
||||||
"name": "TeslaMate",
|
"name": "TeslaMate",
|
||||||
"version": "dev",
|
"version": "dev",
|
||||||
"slug": "teslamate",
|
"slug": "teslamate",
|
||||||
"description": "A self-hosted data logger for your Tesla.",
|
"description": "A self-hosted data logger for your Tesla.",
|
||||||
"url": "https://github.com/lildude/ha-addon-teslamate",
|
"url": "https://github.com/lildude/ha-addon-teslamate",
|
||||||
"arch": [
|
"arch": [
|
||||||
"armv7",
|
"aarch64",
|
||||||
"aarch64",
|
"amd64"
|
||||||
"amd64"
|
],
|
||||||
],
|
"hassio_api": true,
|
||||||
"ingress": true,
|
"ingress": true,
|
||||||
"init": false,
|
"init": false,
|
||||||
"panel_icon": "mdi:car-connected",
|
"panel_icon": "mdi:car-connected",
|
||||||
"panel_title": "TeslaMate",
|
"panel_title": "TeslaMate",
|
||||||
"options": {
|
"ports": {
|
||||||
"database_user": null,
|
"4000/tcp": null
|
||||||
"database_pass": null,
|
},
|
||||||
"database_name": "teslamate",
|
"ports_description": {
|
||||||
"database_host": null,
|
"4000/tcp": "Web interface (not required for Home Assistant ingress)"
|
||||||
"database_port": 5432,
|
},
|
||||||
"database_ssl": false,
|
"options": {
|
||||||
"grafana_import_dashboards": true,
|
"database_user": null,
|
||||||
"grafana_folder_name": "TeslaMate",
|
"database_pass": null,
|
||||||
"grafana_host": "a0d7b954-grafana",
|
"database_name": "teslamate",
|
||||||
"grafana_port": 3000,
|
"database_host": null,
|
||||||
"grafana_user": "",
|
"database_port": 5432,
|
||||||
"grafana_pass": "",
|
"database_ssl": false,
|
||||||
"disable_mqtt": false,
|
"grafana_import_dashboards": true,
|
||||||
"mqtt_host": "core-mosquitto",
|
"grafana_folder_name": "TeslaMate",
|
||||||
"mqtt_user": "",
|
"grafana_host": "a0d7b954-grafana",
|
||||||
"mqtt_pass": "",
|
"grafana_port": 3000,
|
||||||
"mqtt_tls": false,
|
"grafana_user": "",
|
||||||
"mqtt_tls_accept_invalid_certs": false,
|
"grafana_pass": "",
|
||||||
"mqtt_namespace": "",
|
"disable_mqtt": false,
|
||||||
"timezone": "Europe/London",
|
"mqtt_host": "core-mosquitto",
|
||||||
"import_dir": "/share/teslamate",
|
"mqtt_user": "",
|
||||||
"default_geofence": "",
|
"mqtt_pass": "",
|
||||||
"encryption_key": "",
|
"mqtt_tls": false,
|
||||||
"env_vars": []
|
"mqtt_tls_accept_invalid_certs": false,
|
||||||
},
|
"mqtt_namespace": "",
|
||||||
"schema": {
|
"timezone": "Europe/London",
|
||||||
"database_user": "str",
|
"import_dir": "/share/teslamate",
|
||||||
"database_pass": "password",
|
"default_geofence": "",
|
||||||
"database_name": "str",
|
"encryption_key": "",
|
||||||
"database_host": "str",
|
"env_vars": []
|
||||||
"database_ssl": "bool",
|
},
|
||||||
"database_port": "int",
|
"schema": {
|
||||||
"grafana_import_dashboards": "bool",
|
"database_user": "str",
|
||||||
"grafana_folder_name": "str",
|
"database_pass": "password",
|
||||||
"grafana_host": "str",
|
"database_name": "str",
|
||||||
"grafana_port": "int",
|
"database_host": "str",
|
||||||
"grafana_user": "str?",
|
"database_ssl": "bool",
|
||||||
"grafana_pass": "password?",
|
"database_port": "int",
|
||||||
"disable_mqtt": "bool",
|
"grafana_import_dashboards": "bool",
|
||||||
"mqtt_host": "str?",
|
"grafana_folder_name": "str",
|
||||||
"mqtt_user": "str?",
|
"grafana_host": "str",
|
||||||
"mqtt_pass": "password?",
|
"grafana_port": "int",
|
||||||
"mqtt_tls": "bool",
|
"grafana_user": "str?",
|
||||||
"mqtt_tls_accept_invalid_certs": "bool",
|
"grafana_pass": "password?",
|
||||||
"mqtt_namespace": "str?",
|
"disable_mqtt": "bool",
|
||||||
"timezone": "str",
|
"mqtt_host": "str?",
|
||||||
"import_dir": "str",
|
"mqtt_user": "str?",
|
||||||
"default_geofence": "str?",
|
"mqtt_pass": "password?",
|
||||||
"encryption_key": "password?",
|
"mqtt_tls": "bool",
|
||||||
"env_vars": [{
|
"mqtt_tls_accept_invalid_certs": "bool",
|
||||||
"name": "str?",
|
"mqtt_namespace": "str?",
|
||||||
"value": "str?"
|
"timezone": "str",
|
||||||
}]
|
"import_dir": "str",
|
||||||
},
|
"default_geofence": "str?",
|
||||||
"map": [
|
"encryption_key": "password?",
|
||||||
"share:rw"
|
"env_vars": [
|
||||||
],
|
{
|
||||||
"codenotary": "colin@symr.io",
|
"name": "str?",
|
||||||
"image": "ghcr.io/lildude/ha-addon-teslamate-{arch}"
|
"value": "str?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"map": [
|
||||||
|
"share:rw"
|
||||||
|
],
|
||||||
|
"image": "ghcr.io/lildude/ha-addon-teslamate-{arch}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,16 +52,32 @@ for env_var in $(bashio::config 'env_vars|keys'); do
|
|||||||
export "${name}=${value}"
|
export "${name}=${value}"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Import dashboards
|
|
||||||
if bashio::config.true 'grafana_import_dashboards'; then
|
|
||||||
/dashboards.sh restore || bashio::log.error "Failed to import dashboards"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create the PostgreSQL database if it doesn't exist
|
# Create the PostgreSQL database if it doesn't exist
|
||||||
|
bashio::log.info "Checking for database '$DATABASE_NAME' on $DATABASE_HOST"
|
||||||
if pg_isready -h "$DATABASE_HOST" -p "$DATABASE_PORT" > /dev/null 2>&1; then
|
if pg_isready -h "$DATABASE_HOST" -p "$DATABASE_PORT" > /dev/null 2>&1; then
|
||||||
bashio::log.info "Creating database '$DATABASE_NAME' on $DATABASE_HOST"
|
# Check the version of PostgreSQL database is v16.7 or later or v17.3 or later
|
||||||
|
pg_version=$(PGPASSWORD="$DATABASE_PASS" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" postgres -Atqc "SELECT regexp_replace(version(), 'PostgreSQL ([^ ]+) .*', '\\1') AS version")
|
||||||
|
major_version=$(echo "$pg_version" | cut -d '.' -f 1)
|
||||||
|
minor_version=$(echo "$pg_version" | cut -d '.' -f 2)
|
||||||
|
compat=true
|
||||||
|
case "$major_version" in
|
||||||
|
16)
|
||||||
|
[[ "$minor_version" -lt 7 ]] && compat=false
|
||||||
|
;;
|
||||||
|
17)
|
||||||
|
[[ "$minor_version" -lt 3 ]] && compat=false
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
[[ "$major_version" -lt 16 ]] && compat=false
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
if [[ -n $(PGPASSWORD="$DATABASE_PASS" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" postgres -Atqc "\list \"$DATABASE_NAME\"") ]]; then
|
if [ $compat = false ]; then
|
||||||
|
bashio::log.error "PostgreSQL version $pg_version is not supported. Please upgrade to v16.7 or later or v17.3 or later."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $(PGPASSWORD="$DATABASE_PASS" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" postgres -Atqc "SELECT datname FROM pg_database WHERE datname = '$DATABASE_NAME'") ]]; then
|
||||||
bashio::log.info "Database $DATABASE_NAME already exists"
|
bashio::log.info "Database $DATABASE_NAME already exists"
|
||||||
else
|
else
|
||||||
if PGPASSWORD="$DATABASE_PASS" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" postgres -qc "CREATE DATABASE \"$DATABASE_NAME\""; then
|
if PGPASSWORD="$DATABASE_PASS" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" postgres -qc "CREATE DATABASE \"$DATABASE_NAME\""; then
|
||||||
@@ -76,7 +92,32 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ulimit -n 1048576
|
# Import dashboards
|
||||||
|
if bashio::config.true 'grafana_import_dashboards'; then
|
||||||
|
bashio::log.info "Importing Grafana dashboards..."
|
||||||
|
/dashboards.sh restore || bashio::log.error "Failed to import dashboards"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $(PGPASSWORD="$DATABASE_PASS" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" "$DATABASE_NAME" -Atqc "SELECT 1 FROM settings WHERE base_url LIKE '%api/hassio_ingress%'" 2> /dev/null ) ]]; then
|
||||||
|
teslamate_ingress=$(bashio::addon.ingress_entry)
|
||||||
|
grafana_ingress=$(bashio::addon.ingress_entry "$(bashio::supervisor.addons | grep grafana)")
|
||||||
|
if [[ "$teslamate_ingress" == "$grafana_ingress" ]]; then
|
||||||
|
grafana_ingress="** Grafana addon not found. Ensure the addon is installed and running. **"
|
||||||
|
fi
|
||||||
|
|
||||||
|
bashio::log.notice ""
|
||||||
|
bashio::log.notice "Configure TeslaMate settings by adding these values"
|
||||||
|
bashio::log.notice "to the URL you use to access your Home Assistant instance:"
|
||||||
|
bashio::log.notice ""
|
||||||
|
bashio::log.notice "=> Web App: $teslamate_ingress"
|
||||||
|
bashio::log.notice "=> Dashboards: $grafana_ingress"
|
||||||
|
bashio::log.notice ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set max open file limit to avoid memory allocation issues
|
||||||
|
if [ "$(ulimit -Hn)" -gt 524288 ]; then
|
||||||
|
ulimit -n 524288
|
||||||
|
fi
|
||||||
|
|
||||||
bashio::log.info "Starting TeslaMate..."
|
bashio::log.info "Starting TeslaMate..."
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user