The schema applies itself, and the setup script stops hiding failures
Migrations were run by hand and nothing recorded which had run, so re-running the setup script against an existing database failed on the first CREATE TABLE, and shipping a new migration gave an operator no way to know whether an estate had it. A missed migration is not a startup error - it is a query referencing a column that is not there, surfacing later on whichever endpoint touches it first. server/internal/migrate applies pending migrations at boot and refuses to start against a schema it does not match. One transaction per file holding both the DDL and the row that records it; an advisory lock so two servers starting at once cannot both apply 008; checksums so an edited migration is refused by name rather than silently skipped; numeric ordering so 010 does not run before 009. `migrate -baseline N` adopts a database built before any of this existed, because "the clients table exists" does not say whether 007's index does. Verified on the live database: adopted 001-007, applied 008. 008 adds two indexes on `purchases`, found by asking the database which foreign keys had nothing behind them and then checking what queries the table. The conversion report filters client_id + occurred_at, which is exactly the estate-wide case with no site to narrow it. run-local.sh had two bugs, both found by running it rather than reading it: it reused a broker container whose bind mount pointed at a directory that no longer existed, and it discarded stderr on the mosquitto_passwd call, so under `set -e` it exited at step 5 with no output at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HViLj9gYNRtSr7YVZmW5sn
This commit is contained in:
59
run-local.sh
59
run-local.sh
@@ -21,16 +21,23 @@ mkdir -p "$STATE/mosquitto"
|
||||
|
||||
step() { printf '\n\033[1m%s\033[0m\n' "$*"; }
|
||||
|
||||
# Checked up front rather than discovered in the middle of step 2, where the
|
||||
# failure is a bare "go: command not found" after two minutes of npm install.
|
||||
for tool in docker go npm; do
|
||||
command -v "$tool" >/dev/null 2>&1 || {
|
||||
printf 'need %s on PATH.\n' "$tool" >&2
|
||||
[ "$tool" = go ] && printf 'go is often installed outside the default PATH; try: export PATH="$HOME/go/bin:$PATH"\n' >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
step "1. Postgres (pgvector - migration 001 needs the extension)"
|
||||
docker inspect bv-pg >/dev/null 2>&1 || docker run -d --name bv-pg \
|
||||
-p "${PG_PORT}:5432" -e POSTGRES_PASSWORD=test -e POSTGRES_DB=behavision \
|
||||
pgvector/pgvector:pg16 >/dev/null
|
||||
docker start bv-pg >/dev/null 2>&1 || true
|
||||
until docker exec bv-pg pg_isready -U postgres >/dev/null 2>&1; do sleep 1; done
|
||||
for f in server/migrations/*.sql; do
|
||||
docker exec -i bv-pg psql -U postgres -d behavision -v ON_ERROR_STOP=1 -q < "$f"
|
||||
done
|
||||
echo " migrations applied"
|
||||
echo " ready (the server applies the schema itself on start)"
|
||||
|
||||
step "2. Build (the web app builds INTO the Go module, so it goes first)"
|
||||
(cd web && npm install --silent && npm run build >/dev/null)
|
||||
@@ -58,6 +65,12 @@ fi
|
||||
# shellcheck disable=SC1090
|
||||
. "$STATE/env.sh"
|
||||
|
||||
step "3b. Schema"
|
||||
# The server would do this itself on start, but provisioning below runs BEFORE
|
||||
# it does and needs the tables to exist. One command either way, and it is the
|
||||
# same code path the server uses.
|
||||
"./$STATE/bv-server" migrate
|
||||
|
||||
step "4. Mosquitto"
|
||||
if [ ! -f "$STATE/mosquitto/mosquitto.conf" ]; then
|
||||
cat > "$STATE/mosquitto/mosquitto.conf" <<EOF
|
||||
@@ -69,13 +82,43 @@ EOF
|
||||
printf 'user behavision-server\ntopic read bv/#\n' > "$STATE/mosquitto/acl"
|
||||
: > "$STATE/mosquitto/passwd"
|
||||
fi
|
||||
# A container is reused only if its config mount still points HERE. The bind
|
||||
# source is baked in when the container is created, so one made while the
|
||||
# checkout lived somewhere else - or by a run from another directory - comes
|
||||
# back up with an empty /mosquitto/config and dies with "Unable to open config
|
||||
# file", which the old `|| true` below then hid completely.
|
||||
MQTT_CONF="$PWD/$STATE/mosquitto"
|
||||
if docker inspect bv-mqtt >/dev/null 2>&1; then
|
||||
MOUNTED=$(docker inspect bv-mqtt \
|
||||
--format '{{range .Mounts}}{{if eq .Destination "/mosquitto/config"}}{{.Source}}{{end}}{{end}}')
|
||||
if [ "$MOUNTED" != "$MQTT_CONF" ]; then
|
||||
echo " recreating bv-mqtt (its config was mounted from ${MOUNTED:-nowhere})"
|
||||
docker rm -f bv-mqtt >/dev/null
|
||||
fi
|
||||
fi
|
||||
docker inspect bv-mqtt >/dev/null 2>&1 || docker run -d --name bv-mqtt \
|
||||
-p "${MQTT_PORT}:1883" -v "$PWD/$STATE/mosquitto:/mosquitto/config" \
|
||||
-p "${MQTT_PORT}:1883" -v "$MQTT_CONF:/mosquitto/config" \
|
||||
eclipse-mosquitto:2 >/dev/null
|
||||
docker start bv-mqtt >/dev/null 2>&1 || true
|
||||
sleep 1
|
||||
|
||||
# Wait for it, and say so if it never arrives. `docker start` returning 0 only
|
||||
# means the container was launched; mosquitto exits a moment later if it cannot
|
||||
# read its config, and every `docker exec` after that fails for a reason that
|
||||
# has nothing to do with what it was asked to do.
|
||||
for _ in $(seq 1 20); do
|
||||
docker exec bv-mqtt sh -c 'exit 0' >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
if ! docker exec bv-mqtt sh -c 'exit 0' >/dev/null 2>&1; then
|
||||
echo " broker will not stay up:" >&2
|
||||
docker logs --tail 5 bv-mqtt >&2
|
||||
exit 1
|
||||
fi
|
||||
# stderr is NOT discarded here. A failure means the server cannot authenticate
|
||||
# to its own broker, and the whole point of this script is that you find that
|
||||
# out now rather than from an empty arrivals feed.
|
||||
docker exec bv-mqtt mosquitto_passwd -b /mosquitto/config/passwd \
|
||||
behavision-server "$MQTT_PASSWORD" >/dev/null 2>&1 || true
|
||||
behavision-server "$MQTT_PASSWORD" >/dev/null
|
||||
docker restart bv-mqtt >/dev/null
|
||||
echo " broker on ${MQTT_PORT}"
|
||||
|
||||
@@ -101,7 +144,7 @@ SITE_OUT=$("./$STATE/bv-server" provision site -client tenext-retail -slug chenn
|
||||
-name "TeNext Chennai" -tz Asia/Kolkata)
|
||||
BUSER=$(printf '%s' "$SITE_OUT" | sed -n "s/.*passwd \([^ ]*\) .*/\1/p")
|
||||
BPASS=$(printf '%s' "$SITE_OUT" | sed -n "s/.*passwd [^ ]* '\(.*\)'.*/\1/p")
|
||||
docker exec bv-mqtt mosquitto_passwd -b /mosquitto/config/passwd "$BUSER" "$BPASS" >/dev/null 2>&1
|
||||
docker exec bv-mqtt mosquitto_passwd -b /mosquitto/config/passwd "$BUSER" "$BPASS" >/dev/null
|
||||
grep -q "^user $BUSER$" "$STATE/mosquitto/acl" || \
|
||||
printf '\nuser %s\ntopic write bv/%s/#\n' "$BUSER" "$BUSER" >> "$STATE/mosquitto/acl"
|
||||
docker restart bv-mqtt >/dev/null
|
||||
|
||||
Reference in New Issue
Block a user