From d9fe4965e6062ee1814ee5099ecd85acaef56cc0 Mon Sep 17 00:00:00 2001 From: Adam Kadlec Date: Sun, 31 May 2026 10:01:06 +0200 Subject: [PATCH 1/7] chore(infra): add Superset workspace setup/teardown scripts Add .superset/config.json with setup, run, and teardown commands for Superset workspaces: - setup: ./.superset/setup.sh installs pnpm workspace deps with a frozen lockfile and seeds .env (root repo .env if present, else .env.example) - run: pnpm dev (builds shared deps, runs server + web in watch mode) - teardown: stops the local Home Assistant dev container if started The HA Docker stack is intentionally left out of setup because its first-run flow is interactive (account creation + long-lived token). Co-Authored-By: Claude Opus 4.8 (1M context) --- .superset/config.json | 5 +++++ .superset/setup.sh | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 .superset/config.json create mode 100755 .superset/setup.sh diff --git a/.superset/config.json b/.superset/config.json new file mode 100644 index 0000000..592b319 --- /dev/null +++ b/.superset/config.json @@ -0,0 +1,5 @@ +{ + "setup": ["./.superset/setup.sh"], + "run": ["pnpm dev"], + "teardown": ["docker compose -f dev/ha-stack.yml down"] +} diff --git a/.superset/setup.sh b/.superset/setup.sh new file mode 100755 index 0000000..749856b --- /dev/null +++ b/.superset/setup.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Superset workspace setup for Lovelacer. +# Installs workspace deps and seeds a local .env. +set -euo pipefail + +# Install all pnpm workspace dependencies (frozen lockfile for reproducibility). +corepack enable >/dev/null 2>&1 || true +pnpm install --frozen-lockfile + +# Seed .env: prefer the root repo's .env (carries HA_TOKEN etc.), else fall back +# to the committed example so the server has sane defaults. +if [ ! -f .env ]; then + if [ -n "${SUPERSET_ROOT_PATH:-}" ] && [ -f "$SUPERSET_ROOT_PATH/.env" ]; then + cp "$SUPERSET_ROOT_PATH/.env" .env + echo "Copied .env from root repo." + else + cp .env.example .env + echo "No root .env found; copied .env.example. Fill in HA_TOKEN before running." + fi +fi From 120f566651652a672cb5d6754c74b7be8ab9b4b5 Mon Sep 17 00:00:00 2001 From: Adam Kadlec Date: Sun, 31 May 2026 10:12:58 +0200 Subject: [PATCH 2/7] chore(infra): gate Superset HA teardown on a workspace-local marker A blanket `docker compose -f dev/ha-stack.yml down` in teardown used the default compose project (`dev`) and the hardcoded `lovelacer-dev-ha` container, so deleting any workspace could stop the shared HA container started by the main repo or another worktree. Teardown now only stops HA when this workspace started it, tracked via a gitignored `.superset/.ha-started` marker. Add `.superset/ha.sh up|down` as the workspace-aware way to control the singleton HA stack and manage the marker. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 3 +++ .superset/config.json | 2 +- .superset/ha.sh | 29 +++++++++++++++++++++++++++++ .superset/teardown.sh | 15 +++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100755 .superset/ha.sh create mode 100755 .superset/teardown.sh diff --git a/.gitignore b/.gitignore index 781efb2..0b54aea 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,9 @@ apps/addon/web-dist/ # Git worktrees (project-local) .worktrees/ +# Superset workspace-local HA ownership marker (see .superset/ha.sh) +.superset/.ha-started + # OS .DS_Store .claude/ diff --git a/.superset/config.json b/.superset/config.json index 592b319..d639164 100644 --- a/.superset/config.json +++ b/.superset/config.json @@ -1,5 +1,5 @@ { "setup": ["./.superset/setup.sh"], "run": ["pnpm dev"], - "teardown": ["docker compose -f dev/ha-stack.yml down"] + "teardown": ["./.superset/teardown.sh"] } diff --git a/.superset/ha.sh b/.superset/ha.sh new file mode 100755 index 0000000..59d3081 --- /dev/null +++ b/.superset/ha.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Workspace-aware control for the local Home Assistant dev stack. +# +# The HA dev container is a singleton: dev/ha-stack.yml hardcodes +# container_name: lovelacer-dev-ha and binds port 8123, so only one instance +# can run across the main repo and all worktrees. Use this wrapper instead of +# `pnpm dev:ha` inside a Superset workspace so teardown only stops the stack +# this workspace actually started (tracked via a workspace-local marker). +# +# ./.superset/ha.sh up # start HA + record ownership +# ./.superset/ha.sh down # stop HA + clear ownership +set -euo pipefail + +marker=".superset/.ha-started" + +case "${1:-}" in + up) + docker compose -f dev/ha-stack.yml up -d + touch "$marker" + ;; + down) + docker compose -f dev/ha-stack.yml down + rm -f "$marker" + ;; + *) + echo "usage: $0 up|down" >&2 + exit 1 + ;; +esac diff --git a/.superset/teardown.sh b/.superset/teardown.sh new file mode 100755 index 0000000..aa8ee31 --- /dev/null +++ b/.superset/teardown.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Superset workspace teardown for Lovelacer. +# +# Only stops the local Home Assistant dev stack if THIS workspace started it +# (via ./.superset/ha.sh up, which leaves a marker). The HA container is shared +# across the main repo and worktrees — a blanket `docker compose down` here +# would otherwise stop a container another checkout is relying on. +set -euo pipefail + +marker=".superset/.ha-started" + +if [ -f "$marker" ]; then + docker compose -f dev/ha-stack.yml down + rm -f "$marker" +fi From 06ae25a321ed39d4b08e9f27a04310119f7c2eda Mon Sep 17 00:00:00 2001 From: Adam Kadlec Date: Sun, 31 May 2026 10:27:17 +0200 Subject: [PATCH 3/7] chore(infra): gate ha.sh down on the ownership marker The manual `./.superset/ha.sh down` path ran `docker compose down` unconditionally, bypassing the `.superset/.ha-started` marker check and risking stopping the shared lovelacer-dev-ha container owned by the main repo or another workspace. It now refuses unless this workspace owns the marker, and points to `pnpm dev:ha:down` as the explicit force path. Co-Authored-By: Claude Opus 4.8 (1M context) --- .superset/ha.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.superset/ha.sh b/.superset/ha.sh index 59d3081..d02ade4 100755 --- a/.superset/ha.sh +++ b/.superset/ha.sh @@ -9,6 +9,10 @@ # # ./.superset/ha.sh up # start HA + record ownership # ./.superset/ha.sh down # stop HA + clear ownership +# +# `down` refuses to act unless this workspace owns the marker, so it can't stop +# a container the main repo or another workspace started. To force-stop the +# shared stack from any checkout, use `pnpm dev:ha:down` directly. set -euo pipefail marker=".superset/.ha-started" @@ -19,6 +23,11 @@ case "${1:-}" in touch "$marker" ;; down) + if [ ! -f "$marker" ]; then + echo "This workspace did not start HA (no $marker); refusing to stop the shared stack." >&2 + echo "Use 'pnpm dev:ha:down' if you really want to stop it." >&2 + exit 0 + fi docker compose -f dev/ha-stack.yml down rm -f "$marker" ;; From 9a8ed4bec0941eb36ccf7f798b8fb497c1a18339 Mon Sep 17 00:00:00 2001 From: Adam Kadlec Date: Sun, 31 May 2026 10:36:38 +0200 Subject: [PATCH 4/7] chore(infra): only claim HA ownership when ha.sh up starts the container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ha.sh up` wrote the ownership marker unconditionally, so running it while the shared lovelacer-dev-ha singleton was already up (from the main repo or another worktree) falsely claimed ownership — and a later guarded down/teardown would then stop that other checkout's container. It now bails out without touching Compose or the marker if the container is already running, so the marker only ever reflects a container this workspace actually started. Co-Authored-By: Claude Opus 4.8 (1M context) --- .superset/ha.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.superset/ha.sh b/.superset/ha.sh index d02ade4..edc5706 100755 --- a/.superset/ha.sh +++ b/.superset/ha.sh @@ -19,6 +19,11 @@ marker=".superset/.ha-started" case "${1:-}" in up) + if docker ps --format '{{.Names}}' | grep -qx lovelacer-dev-ha; then + echo "HA (lovelacer-dev-ha) is already running — started by another checkout." >&2 + echo "Leaving it untouched and not claiming ownership from this workspace." >&2 + exit 0 + fi docker compose -f dev/ha-stack.yml up -d touch "$marker" ;; From a08bd49e92fa512d2e09964e0be43dd543010ea9 Mon Sep 17 00:00:00 2001 From: Adam Kadlec Date: Sun, 31 May 2026 10:41:06 +0200 Subject: [PATCH 5/7] chore(infra): warn when a fresh workspace HA invalidates the copied token setup.sh copies the root .env's HA_TOKEN, which is correct when the workspace reuses the shared HA at localhost:8123 but stale if the workspace starts its own fresh HA (empty per-worktree dev/ha-config). ha.sh up now detects a fresh config and warns that the copied token won't authenticate and must be regenerated; setup.sh documents the same so the stale-token case is surfaced instead of failing silently. Co-Authored-By: Claude Opus 4.8 (1M context) --- .superset/ha.sh | 9 +++++++++ .superset/setup.sh | 7 +++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.superset/ha.sh b/.superset/ha.sh index edc5706..49195d8 100755 --- a/.superset/ha.sh +++ b/.superset/ha.sh @@ -24,8 +24,17 @@ case "${1:-}" in echo "Leaving it untouched and not claiming ownership from this workspace." >&2 exit 0 fi + # A fresh per-worktree dev/ha-config means a brand-new HA with its own + # onboarding — any HA_TOKEN copied from the root .env won't authenticate. + fresh=0 + [ -e dev/ha-config/.storage ] || fresh=1 docker compose -f dev/ha-stack.yml up -d touch "$marker" + if [ "$fresh" -eq 1 ]; then + echo "Started a fresh HA instance (empty dev/ha-config) on http://localhost:8123." >&2 + echo "Any HA_TOKEN copied from the root .env will NOT work against it —" >&2 + echo "onboard, generate a new long-lived token, and set HA_TOKEN in .env." >&2 + fi ;; down) if [ ! -f "$marker" ]; then diff --git a/.superset/setup.sh b/.superset/setup.sh index 749856b..8b86a05 100755 --- a/.superset/setup.sh +++ b/.superset/setup.sh @@ -8,11 +8,14 @@ corepack enable >/dev/null 2>&1 || true pnpm install --frozen-lockfile # Seed .env: prefer the root repo's .env (carries HA_TOKEN etc.), else fall back -# to the committed example so the server has sane defaults. +# to the committed example so the server has sane defaults. The copied HA_TOKEN +# targets the shared HA at localhost:8123; if this workspace later starts its +# own fresh HA via ./.superset/ha.sh up, that token won't match and must be +# regenerated (ha.sh up warns when that happens). if [ ! -f .env ]; then if [ -n "${SUPERSET_ROOT_PATH:-}" ] && [ -f "$SUPERSET_ROOT_PATH/.env" ]; then cp "$SUPERSET_ROOT_PATH/.env" .env - echo "Copied .env from root repo." + echo "Copied .env from root repo (HA_TOKEN targets the existing shared HA)." else cp .env.example .env echo "No root .env found; copied .env.example. Fill in HA_TOKEN before running." From 0a3e71ce8d47e01633c96db2b923d2c2315e74c3 Mon Sep 17 00:00:00 2001 From: Adam Kadlec Date: Sun, 31 May 2026 10:49:49 +0200 Subject: [PATCH 6/7] chore(infra): revalidate HA ownership marker against the container id A bare marker could go stale: if the workspace-owned container was force-stopped (pnpm dev:ha:down) and another checkout later started lovelacer-dev-ha, teardown would stop that other container. The marker now stores the started container id; ha.sh down (and teardown, which delegates to it) compares the stored id against the running container and only stops it on an exact match, clearing the marker otherwise. Co-Authored-By: Claude Opus 4.8 (1M context) --- .superset/ha.sh | 30 ++++++++++++++++++++++++++---- .superset/teardown.sh | 13 +++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/.superset/ha.sh b/.superset/ha.sh index 49195d8..eeefc3e 100755 --- a/.superset/ha.sh +++ b/.superset/ha.sh @@ -10,13 +10,21 @@ # ./.superset/ha.sh up # start HA + record ownership # ./.superset/ha.sh down # stop HA + clear ownership # -# `down` refuses to act unless this workspace owns the marker, so it can't stop -# a container the main repo or another workspace started. To force-stop the -# shared stack from any checkout, use `pnpm dev:ha:down` directly. +# `down` refuses to act unless this workspace owns the *currently running* +# container, so it can't stop a container the main repo or another workspace +# started. The marker stores the started container id and is revalidated before +# every stop, so a stale marker (e.g. after a `pnpm dev:ha:down` force-stop and +# a restart elsewhere) is cleared rather than acted on. To force-stop the shared +# stack from any checkout, use `pnpm dev:ha:down` directly. set -euo pipefail marker=".superset/.ha-started" +# Echo the running lovelacer-dev-ha container id, or nothing if it isn't up. +ha_container_id() { + docker inspect -f '{{.Id}}' lovelacer-dev-ha 2>/dev/null || true +} + case "${1:-}" in up) if docker ps --format '{{.Names}}' | grep -qx lovelacer-dev-ha; then @@ -29,7 +37,8 @@ case "${1:-}" in fresh=0 [ -e dev/ha-config/.storage ] || fresh=1 docker compose -f dev/ha-stack.yml up -d - touch "$marker" + # Record the started container id so later down/teardown can prove ownership. + ha_container_id > "$marker" if [ "$fresh" -eq 1 ]; then echo "Started a fresh HA instance (empty dev/ha-config) on http://localhost:8123." >&2 echo "Any HA_TOKEN copied from the root .env will NOT work against it —" >&2 @@ -42,6 +51,19 @@ case "${1:-}" in echo "Use 'pnpm dev:ha:down' if you really want to stop it." >&2 exit 0 fi + owned_id=$(cat "$marker") + current_id=$(ha_container_id) + if [ -z "$current_id" ]; then + echo "The HA container this workspace started is no longer running; clearing stale marker." >&2 + rm -f "$marker" + exit 0 + fi + if [ "$current_id" != "$owned_id" ]; then + echo "Running lovelacer-dev-ha was started by another checkout; not stopping it." >&2 + echo "Clearing this workspace's stale marker." >&2 + rm -f "$marker" + exit 0 + fi docker compose -f dev/ha-stack.yml down rm -f "$marker" ;; diff --git a/.superset/teardown.sh b/.superset/teardown.sh index aa8ee31..5295bad 100755 --- a/.superset/teardown.sh +++ b/.superset/teardown.sh @@ -1,15 +1,16 @@ #!/usr/bin/env bash # Superset workspace teardown for Lovelacer. # -# Only stops the local Home Assistant dev stack if THIS workspace started it -# (via ./.superset/ha.sh up, which leaves a marker). The HA container is shared -# across the main repo and worktrees — a blanket `docker compose down` here -# would otherwise stop a container another checkout is relying on. +# Only stops the local Home Assistant dev stack if THIS workspace started it and +# still owns the running container. The HA container is shared across the main +# repo and worktrees — a blanket `docker compose down` here would otherwise stop +# a container another checkout is relying on. Ownership (including stale-marker +# revalidation) lives in ha.sh down, which we delegate to so the logic stays in +# one place. set -euo pipefail marker=".superset/.ha-started" if [ -f "$marker" ]; then - docker compose -f dev/ha-stack.yml down - rm -f "$marker" + ./.superset/ha.sh down fi From 44f0f481154055637e60df3972a5e23ee68a67c4 Mon Sep 17 00:00:00 2001 From: Adam Kadlec Date: Sun, 31 May 2026 10:56:32 +0200 Subject: [PATCH 7/7] chore(infra): reject any existing HA singleton before claiming ownership The up guard used `docker ps` (running only), so a stopped/exited lovelacer-dev-ha from another checkout slipped through: `compose up` would restart it and this workspace would claim ownership of another checkout's container. up now inspects for the container in ANY state and only creates + claims ownership when none exists; if one already exists it is left untouched, unless this workspace's marker already names that exact container id (in which case it simply restarts its own). Co-Authored-By: Claude Opus 4.8 (1M context) --- .superset/ha.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.superset/ha.sh b/.superset/ha.sh index eeefc3e..978c781 100755 --- a/.superset/ha.sh +++ b/.superset/ha.sh @@ -27,11 +27,22 @@ ha_container_id() { case "${1:-}" in up) - if docker ps --format '{{.Names}}' | grep -qx lovelacer-dev-ha; then - echo "HA (lovelacer-dev-ha) is already running — started by another checkout." >&2 - echo "Leaving it untouched and not claiming ownership from this workspace." >&2 + # ha_container_id uses `docker inspect`, so this sees an existing + # lovelacer-dev-ha in ANY state (running, stopped, exited) — not just + # running ones — and avoids adopting/restarting another checkout's stopped + # singleton. + existing_id=$(ha_container_id) + if [ -n "$existing_id" ]; then + if [ -f "$marker" ] && [ "$(cat "$marker")" = "$existing_id" ]; then + # This workspace already owns this exact container — (re)start it. + docker compose -f dev/ha-stack.yml up -d + else + echo "lovelacer-dev-ha already exists (started by another checkout)." >&2 + echo "Leaving it untouched and not claiming ownership from this workspace." >&2 + fi exit 0 fi + # No container exists in any state — create one and claim ownership. # A fresh per-worktree dev/ha-config means a brand-new HA with its own # onboarding — any HA_TOKEN copied from the root .env won't authenticate. fresh=0