furtka-apps/apps/mosquitto/scripts/ensure-client.sh
Daniel Maksymilian Syrnicki 3408e1aad8
All checks were successful
CI / validate (pull_request) Successful in 6s
CI / shellcheck (pull_request) Successful in 13s
feat: add mosquitto + zigbee2mqtt dependency pair
First catalog apps to exercise core 26.17's app-to-app dependency
feature — until now every app was standalone.

- mosquitto: MQTT broker, first dependency *provider*. Mandatory auth;
  per-consumer accounts created by on_install/on_start hooks
  (scripts/provision-client.sh, scripts/ensure-client.sh) that run inside
  the broker container via `docker compose exec`. Provider-side password
  stash so on_start can restore an account after a volume wipe.
- zigbee2mqtt: first dependency *consumer*. `requires` mosquitto; MQTT
  creds wired in from the provisioning hook via ZIGBEE2MQTT_CONFIG_* env.
  Serial coordinator path as a text setting + devices mapping.

Supporting changes:
- Bump vendored furtka_manifest.py (26.10-era -> 26.17) so the validator
  actually validates the `requires` schema instead of ignoring it.
- Document `requires`/hooks in apps/README.md (was undocumented), including
  the three framework gaps building this pair surfaced.
- CI now shellchecks app hook scripts (apps/*/scripts/*.sh), not just
  repo-root scripts/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:45:27 +02:00

47 lines
1.9 KiB
Bash
Executable file

#!/bin/sh
# on_start hook (provider: mosquitto).
#
# Runs INSIDE the mosquitto container on EVERY boot, before the consumer's
# container starts (reconcile visits providers first). Must be idempotent.
#
# Job: make sure the consumer's MQTT account still exists. The common path is
# a no-op — the passwd file lives on a persistent volume and survives reboots.
# This only does work if the account went missing (e.g. the data volume was
# wiped), in which case it restores the SAME password the consumer holds. It
# never rotates a live password, so the consumer's stored MQTT_PASS keeps
# working.
#
# Where the password comes from:
# - Core that injects consumer .env into on_start hooks hands it to us as
# $FURTKA_CONSUMER_ENV_MQTT_PASS — the clean path.
# - Older core gives on_start no consumer context, so we fall back to the
# copy provision-client.sh stashed on our own volume at install time.
# This hook's stdout is discarded by the reconciler (unlike on_install), so
# restoring the password is the only way to keep the consumer connectable.
# Errors go to stderr and fail the hook, which makes reconcile skip the
# consumer's `compose up` rather than start it with a broken account.
set -eu
user="${FURTKA_CONSUMER_APP:?ensure-client: FURTKA_CONSUMER_APP not set}"
passwd_file=/mosquitto/data/passwd
stash="/mosquitto/data/furtka-clients/${user}.pw"
# Account already present → nothing to do.
if [ -f "$passwd_file" ] && grep -q "^${user}:" "$passwd_file"; then
exit 0
fi
pass="${FURTKA_CONSUMER_ENV_MQTT_PASS:-}"
if [ -z "$pass" ] && [ -f "$stash" ]; then
pass="$(cat "$stash")"
fi
if [ -z "$pass" ]; then
echo "ensure-client: account '${user}' is missing and no password is" \
"available to restore it (no FURTKA_CONSUMER_ENV_MQTT_PASS, no stash);" \
"reinstall the app to re-provision." >&2
exit 1
fi
mosquitto_passwd -b "$passwd_file" "$user" "$pass"
kill -HUP 1 2>/dev/null || true