From 3cbb9c6cd81afcc2734665517ce06c72f8376de5 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 12:48:33 -0700 Subject: [PATCH 1/2] fix: sanitize address input in cert generation to prevent SyntaxError Shell variables interpolated directly into inline Python source caused unterminated string literals when the address contained control characters. Fixes #20. Changes: - Use os.environ instead of shell interpolation in all three entrypoint scripts (run.sh, entrypoint.sh, run-local.sh) - Strip control characters from ip route output with tr -d '[:cntrl:]' --- scripts/entrypoint.sh | 7 +++++-- scripts/run-local.sh | 6 ++++-- span_panel_simulator/run.sh | 9 ++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index fe07f57..8d244e2 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -9,11 +9,14 @@ BROKER_PASSWORD="${BROKER_PASSWORD:-sim-password}" ADVERTISE_ADDRESS="${ADVERTISE_ADDRESS:-}" echo "==> Generating TLS certificates..." +export CERT_DIR +export ADVERTISE_ADDRESS python -c " +import os from span_panel_simulator.certs import generate_certificates from pathlib import Path -addr = '${ADVERTISE_ADDRESS}' or None -generate_certificates(Path('${CERT_DIR}'), advertise_address=addr) +addr = os.environ.get('ADVERTISE_ADDRESS') or None +generate_certificates(Path(os.environ['CERT_DIR']), advertise_address=addr) " # Mosquitto runs as the 'mosquitto' user — ensure it can read certs diff --git a/scripts/run-local.sh b/scripts/run-local.sh index a19483d..1ba8814 100755 --- a/scripts/run-local.sh +++ b/scripts/run-local.sh @@ -75,10 +75,12 @@ ensure_venv() { generate_certs() { echo "==> Checking TLS certificates..." mkdir -p "${CERT_DIR}" - python3 -c " + ADVERTISE_ADDRESS="${ADVERTISE_ADDR}" CERT_DIR="${CERT_DIR}" python3 -c " +import os from span_panel_simulator.certs import generate_certificates from pathlib import Path -generate_certificates(Path('${CERT_DIR}'), advertise_address='${ADVERTISE_ADDR}') +addr = os.environ.get('ADVERTISE_ADDRESS') or None +generate_certificates(Path(os.environ['CERT_DIR']), advertise_address=addr) " } diff --git a/span_panel_simulator/run.sh b/span_panel_simulator/run.sh index 2348834..2bf5a3a 100755 --- a/span_panel_simulator/run.sh +++ b/span_panel_simulator/run.sh @@ -21,7 +21,9 @@ BASE_HTTP_PORT=$(jq -r '.base_http_port // 8081' "${OPTIONS_FILE}") # Auto-detect host IP for TLS cert SAN. # Inside a bridge-networked container the default gateway is the host. -ADVERTISE_ADDRESS=$(ip route | awk '/default/ { print $3 }' || true) +# Strip control characters — some container ip implementations emit trailing +# non-printables that would break Python string literals or cert generation. +ADVERTISE_ADDRESS=$(ip route | awk '/default/ { print $3 }' | tr -d '[:cntrl:]' || true) export ADVERTISE_ADDRESS export CERT_DIR="/data/certs" export BROKER_USERNAME="span" @@ -43,10 +45,11 @@ mkdir -p "${CERT_DIR}" # Generate TLS certs python3 -c " +import os from span_panel_simulator.certs import generate_certificates from pathlib import Path -addr = '${ADVERTISE_ADDRESS}' or None -generate_certificates(Path('${CERT_DIR}'), advertise_address=addr) +addr = os.environ.get('ADVERTISE_ADDRESS') or None +generate_certificates(Path(os.environ['CERT_DIR']), advertise_address=addr) " chmod 644 "${CERT_DIR}"/*.crt "${CERT_DIR}"/*.key From 8296d8b4ac2ae47bb4f5c8f51337be7b45bbc92c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Mon, 30 Mar 2026 12:50:45 -0700 Subject: [PATCH 2/2] chore: bump version to 1.0.8 and ignore rates cache --- .gitignore | 1 + pyproject.toml | 2 +- span_panel_simulator/Dockerfile | 2 +- span_panel_simulator/config.yaml | 2 +- src/span_panel_simulator/__init__.py | 2 +- uv.lock | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index dbc06c9..a83d584 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ venv/ .envrc .superpowers/ .last_config +configs/rates/rates_cache.yaml # Roadmap and planning docs (not published yet) span_panel_simulator/docs/roadmap/ diff --git a/pyproject.toml b/pyproject.toml index 8e26378..1433a9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "span-panel-simulator" -version = "1.0.7" +version = "1.0.8" description = "Standalone eBus simulator for SPAN panels" requires-python = ">=3.12" dependencies = [ diff --git a/span_panel_simulator/Dockerfile b/span_panel_simulator/Dockerfile index 087fda9..5d8398d 100644 --- a/span_panel_simulator/Dockerfile +++ b/span_panel_simulator/Dockerfile @@ -32,7 +32,7 @@ EXPOSE 18883 8081 18080 LABEL io.hass.name="SPAN Panel Simulator" \ io.hass.description="Simulates a SPAN electrical panel for testing and upgrade modeling" \ io.hass.type="addon" \ - io.hass.version="1.0.7" \ + io.hass.version="1.0.8" \ io.hass.arch="aarch64|amd64" CMD ["/run.sh"] diff --git a/span_panel_simulator/config.yaml b/span_panel_simulator/config.yaml index cae4fc7..fe9af5e 100644 --- a/span_panel_simulator/config.yaml +++ b/span_panel_simulator/config.yaml @@ -1,6 +1,6 @@ name: "SPAN Panel Simulator" description: "Simulates a SPAN electrical panel for testing and upgrade modeling" -version: "1.0.7" +version: "1.0.8" slug: "span_panel_simulator" url: "https://github.com/SpanPanel/simulator" image: "ghcr.io/spanpanel/simulator/{arch}" diff --git a/src/span_panel_simulator/__init__.py b/src/span_panel_simulator/__init__.py index d6ccfcc..26f9804 100644 --- a/src/span_panel_simulator/__init__.py +++ b/src/span_panel_simulator/__init__.py @@ -1,3 +1,3 @@ """Standalone eBus simulator for SPAN panels.""" -__version__ = "1.0.7" +__version__ = "1.0.8" diff --git a/uv.lock b/uv.lock index f31901e..ce8ca8a 100644 --- a/uv.lock +++ b/uv.lock @@ -1173,7 +1173,7 @@ wheels = [ [[package]] name = "span-panel-simulator" -version = "1.0.7" +version = "1.0.8" source = { editable = "." } dependencies = [ { name = "aiohttp" },