Boot Lifecycle & DB Migration Mechanisms
This appendix documents the fresh-install and upgrade boot paths that v2.0.45+ guarantees. The design principle: every common deployment topology must boot cleanly without operator intervention, and docker compose down && docker compose up -d must apply any pending schema changes idempotently.
51.1 Fresh-install path (new container, empty DB)
Section titled “51.1 Fresh-install path (new container, empty DB)”The entry script (docker-entrypoint.sh or start.sh) runs before the app process:
| Step | Action | Notes |
|---|---|---|
| 1 | Set DATABASE_URL if unset — defaults to file:/app/data/agentwatch.db (SQLite) |
The data dir is bind-mountable for persistence across container restarts |
| 2 | Detect SQLite vs. PostgreSQL by URL prefix | file: → SQLite; anything else → PG |
| 3 (SQLite) | Detect empty DB (no users table) → run sqlite3 < /app/prisma/init.sqlite.sql |
init.sqlite.sql is generated at Docker-build time via prisma migrate diff --from-empty --to-schema-datamodel=schema.sqlite.prisma --script. Includes all 55 tables as of v2.0.45 (44 base + 11 Tier 1/A2A). |
| 3 (PostgreSQL) | Run psql -f /app/prisma/init.sql |
init.sql is built by concatenating every prisma/migrations/*/migration.sql file in order. The Tier 1/A2A migration (20260520_tier1_a2a_tables) is the most recent. |
| 4 | Apply incremental ALTER blocks for column-level patches | Idempotent — every ALTER is guarded by ` |
| 5 | Apply new-table CREATE blocks (incl. the TIER1_TABLES heredoc for v2.0.45) |
All CREATE TABLE IF NOT EXISTS — safe to re-run |
| 6 | Hand off to the app process (node dist/enhanced-index.js or bun run dev) |
The app then runs its own runtime schema-sync as a final safety net |
51.2 Runtime schema-sync (final safety net for any boot path)
Section titled “51.2 Runtime schema-sync (final safety net for any boot path)”src/enhanced/database/schema-sync.ts runs as Phase 0 of DatabaseManager.initialize(), before any model query. It has three sub-phases:
- Bootstrap empty databases — added in v2.0.45. If
organizationstable is missing, readsinit.sqlite.sqlorinit.sqlfrom/app/prisma/,./prisma/, or./and executes the statements viaprisma.$executeRawUnsafe. This handles dev environments + ad-hoc database swaps wherestart.shdidn’t run. - Phase 1: Create missing tables — iterates
REQUIRED_TABLES(which includes all base tables + Tier 1 + A2A) and creates anything missing viaCREATE TABLE IF NOT EXISTS. - Phase 1b: Create missing indexes via
REQUIRED_INDEXES. - Phase 2: Add missing columns via
PATCHES—ALTER TABLE … ADD COLUMN IF NOT EXISTS …(or the SQLite equivalent: tries the ALTER, ignores “duplicate column” errors).
All four sub-phases are idempotent. They can be re-run any number of times without data loss. The schema-sync runs every time the app starts.
51.3 Upgrade path (existing DB, new code version)
Section titled “51.3 Upgrade path (existing DB, new code version)”The standard upgrade is:
docker compose pull # or rebuild the imagedocker compose downdocker compose up -dWhat happens during up -d:
- New container boots →
docker-entrypoint.shruns - Detects DB exists (has
userstable) → skips theinit.sqlite.sql/init.sqlapply step - Runs the column-ALTER block — re-applies safely (no-op on existing columns)
- Runs the table-CREATE block — re-applies safely (
IF NOT EXISTS) - Hands off to the app
- App’s runtime schema-sync also runs — catches anything the entrypoint missed
Triple-redundant migration coverage for v2.0.45 Tier 1 + A2A tables:
| Mechanism | Where | When |
|---|---|---|
| Prisma migration file | prisma/migrations/20260520_tier1_a2a_tables/migration.sql |
Concatenated into /app/prisma/init.sql at Docker build → applied on first PG run |
docker-entrypoint.sh TIER1_TABLES heredoc |
docker-entrypoint.sh (SQLite branch) |
On every container start, idempotent |
start.sh TIER1_TABLES heredoc |
start.sh (SQLite branch) |
When using ./start.sh directly |
Runtime REQUIRED_TABLES |
src/enhanced/database/schema-sync.ts |
On every app start, idempotent |
Any one of the four is sufficient to upgrade an existing install. Three serve as backstops in case one fails.
Additional data-shape migrations in start.sh (idempotent, no-op on clean DBs):
| Step | Purpose | What it does |
|---|---|---|
| Model catalog reconciliation | Fixes models.provider string drift after Provider renames or cross-org clones (see Section 35.2) |
Logs FK-set mismatches; auto-repairs orphan rows when the row’s org has exactly one openai-compatible provider |
| Usage body archive backfill | Migrates legacy inline-stored request/response bodies from usage.metadata to the on-disk encrypted archive (see Section 35.1) |
Walks rows with LENGTH(metadata) > 50000 carrying fullRequest / fullResponse / fullMessages; writes each body to {basePath}/{org}/bodies/.../<reqId>.json.gz.enc (AES-256-GCM, gzip), strips inline fields, then VACUUMs. Sets PRAGMA busy_timeout=60000 so VACUUM waits up to a minute if anything else holds the lock |
Both run before the app starts accepting traffic, so VACUUM’s EXCLUSIVE write lock never conflicts with live requests.
51.4 Zero-downtime upgrade (HA deployments)
Section titled “51.4 Zero-downtime upgrade (HA deployments)”For multi-instance deployments behind HAProxy / ALB:
# Drain replica 1 at the LBdocker compose --profile replica1 pulldocker compose --profile replica1 downdocker compose --profile replica1 up -d # migration runs here on the shared PG# Wait for health check on replica 1 — schema is now upgraded# Return replica 1 to the LB; rinse and repeat for the othersBecause Postgres schema changes are forward-compatible across one minor version (the rule from §48.1), the still-running replica 2 keeps serving traffic while replica 1 boots with the new schema. No downtime.
51.5 New in v2.0.45 — zero-failure boot sequence
Section titled “51.5 New in v2.0.45 — zero-failure boot sequence”Three additions in v2.0.45 close every “fails silently” gap operators kept hitting:
| Subsystem | What changed |
|---|---|
| WebSocket-upgrade routes | /agent/ws-proxy (dynamic upstream) and /ws (MCP bridge) now serve natively under Bun.serve via the WebSocket-client API (new WebSocket(url, { headers })). The 426 Upgrade Required fallback is gone. AGENTWATCH_BUNSERVE=0 is no longer required for any standard deployment. |
| Self-signed TLS auto-gen (opt-in) | src/enhanced/tls-cert.ts resolves TLS material via env-PEM → env-file → opt-in auto-generate. By default the central server runs plain HTTP (matches legacy behaviour — required for HAProxy / ALB / NGINX upstreams that terminate TLS and forward plain HTTP to backend). Set AGENTWATCH_TLS_AUTOGEN=true (or supply AGENTWATCH_TLS_KEY + AGENTWATCH_TLS_CERT env vars) to enable TLS + HTTP/3 on the central server. Auto-generated cert is RSA 2048 / SHA-256 / 825-day validity with SAN covering localhost + 127.0.0.1 + ::1 + HOSTNAME + CONTAINER_IP + user-extended. Default storage /app/data/tls/. Auto-rotates if expiry < 30 days. |
| Bootstrap empty DB at runtime | If the dev / staging / ad-hoc dataset has no organizations table, the app’s schema-sync runs init.sqlite.sql (or init.sql) on first boot, before any model query fires. This makes bun run dev against an empty dev.db work without manual prisma db push. |
| WebSocket header filter | The headers stripped before forwarding to the upstream WS are: connection, upgrade, sec-websocket-*, x-forward-url (metadata), x-forwarded-*, x-original-method, x-agentwatch-token (internal auth). Everything else (cookies, custom headers, sub-protocols) passes through. |
| Newman runner auto-install | ./newman-runner.sh --html now auto-installs newman and newman-reporter-htmlextra via npm install --global if missing. Both the main collection and the Tier 1 companion suite run; an index_<TS>.html file links to both reports. The old silent-degrade-to-JSON-only mode is gone. |
51.6 Operator quick-reference
Section titled “51.6 Operator quick-reference”| Want to … | Do |
|---|---|
| Boot a fresh installation | docker compose up -d — entry script creates the schema, app starts |
| Upgrade an existing installation | docker compose pull && docker compose down && docker compose up -d — migrations apply idempotently |
| Force a fresh DB | docker compose down -v (removes volumes) + docker compose up -d |
| Skip the runtime schema-sync | DB_AUTO_SCHEMA_SYNC=false |
| Disable Bun.serve (legacy Node listener) | AGENTWATCH_BUNSERVE=0 |
| Supply TLS material explicitly (enables HTTP/3) | AGENTWATCH_TLS_KEY=$(cat key.pem) AGENTWATCH_TLS_CERT=$(cat cert.pem) — or AGENTWATCH_TLS_KEY_FILE + AGENTWATCH_TLS_CERT_FILE paths |
| Enable self-signed cert auto-gen + HTTP/3 (opt-in) | AGENTWATCH_TLS_AUTOGEN=true — needed when there’s no reverse-proxy in front. Behind HAProxy/ALB/NGINX leave this UNSET so backend stays plain HTTP. |
| Customize self-signed cert SAN | AGENTWATCH_TLS_SAN_HOSTS=agentwatch.example.com,api.example.com + AGENTWATCH_TLS_SAN_IPS=10.0.0.42 |
| Change TLS storage path | AGENTWATCH_TLS_DIR=/var/lib/agentwatch/tls |
| Run the full test suite + HTML reports | ./newman-runner.sh --html (auto-installs newman + newman-reporter-htmlextra if missing) |
| Run only the Tier 1 + A2A tests | ./newman-runner.sh --tier1 --html |
| Skip the Tier 1 companion suite | ./newman-runner.sh --no-tier1 |
Document Maintenance: Verified against AgentWatch v2.0.45 source code (~85 TypeScript modules in
src/enhanced/,src/enhanced/tier1/,src/enhanced/a2a/; Prisma schemas with 55 tables; 540+ Express route handlers; React UI components; deployment configs; and AgentWatch Connect v1.0.77). Section 43 verified via exhaustive grep searches. Section 45 cross-referenced with the AgentWatch vs. Datadog Feature Comparison Matrix (Iterate.ai, May 2026). Section 46 derived from a representative enterprise AI-NOC RFI requirement set. Section 47 cross-referenced with the AgentWatch Reference Architecture (long-form deployment guide + partner deck, Iterate.ai, May 2026). Section 48 reflects the standard rolling-upgrade procedure + the 2.0.20 → 2.0.45 schema-migration set. Section 49 covers terminology used across all sections. Section 50 captures the forward roadmap (Tier 2 / 3 / 4 / out-of-scope) — Tier 1 + A2A are shipped in v2.0.45. Section 51 documents the boot lifecycle — fresh-install, upgrade, zero-downtime, and the v2.0.45 zero-failure additions (WS-native + auto-TLS + bootstrap-empty-DB + newman auto-install).