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:
@@ -26,9 +26,11 @@ import (
|
||||
"github.com/loyaly/behavision-server/internal/assistant"
|
||||
"github.com/loyaly/behavision-server/internal/blob"
|
||||
"github.com/loyaly/behavision-server/internal/ingest"
|
||||
"github.com/loyaly/behavision-server/internal/migrate"
|
||||
"github.com/loyaly/behavision-server/internal/web"
|
||||
"github.com/loyaly/behavision-server/internal/secret"
|
||||
"github.com/loyaly/behavision-server/internal/store"
|
||||
"github.com/loyaly/behavision-server/migrations"
|
||||
)
|
||||
|
||||
var version = "dev"
|
||||
@@ -44,6 +46,13 @@ func main() {
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(os.Args) > 1 && os.Args[1] == "migrate" {
|
||||
if err := runMigrate(os.Args[2:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := run(); err != nil {
|
||||
log.Fatalf("behavision-server: %v", err)
|
||||
}
|
||||
@@ -79,6 +88,29 @@ func run() error {
|
||||
st.UseLogger(logger)
|
||||
logger.Print("database connected")
|
||||
|
||||
// Applied here, not by hand, because an upgrade of this product is "copy
|
||||
// the new binary and restart it". A migration an operator has to remember
|
||||
// to run is a migration that does not get run, and its failure is not a
|
||||
// startup error - it is a query referencing a column that is not there,
|
||||
// surfacing later on whichever endpoint touches it first.
|
||||
//
|
||||
// Refusing to start on failure is deliberate: a server running against a
|
||||
// schema it does not match writes wrong data, and wrong data outlives the
|
||||
// outage that stopping causes.
|
||||
if os.Getenv("BEHAVISION_SKIP_MIGRATE") == "1" {
|
||||
logger.Print("WARN BEHAVISION_SKIP_MIGRATE=1 - schema not checked")
|
||||
} else {
|
||||
applied, err := migrate.Apply(ctx, st.Pool(), migrations.FS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("schema: %w", err)
|
||||
}
|
||||
if len(applied) == 0 {
|
||||
logger.Print("schema up to date")
|
||||
} else {
|
||||
logger.Printf("schema: applied %s", strings.Join(applied, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
// Without the key the server still ingests events and serves reports; only
|
||||
// enrolment fails, and it fails with a message naming the missing variable.
|
||||
// Refusing to start would take a working estate down over a feature that
|
||||
|
||||
Reference in New Issue
Block a user