Skip to content
Docs

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:

  1. Bootstrap empty databases — added in v2.0.45. If organizations table is missing, reads init.sqlite.sql or init.sql from /app/prisma/, ./prisma/, or ./ and executes the statements via prisma.$executeRawUnsafe. This handles dev environments + ad-hoc database swaps where start.sh didn’t run.
  2. Phase 1: Create missing tables — iterates REQUIRED_TABLES (which includes all base tables + Tier 1 + A2A) and creates anything missing via CREATE TABLE IF NOT EXISTS.
  3. Phase 1b: Create missing indexes via REQUIRED_INDEXES.
  4. 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:

Terminal window
docker compose pull # or rebuild the image
docker compose down
docker compose up -d

What happens during up -d:

  1. New container boots → docker-entrypoint.sh runs
  2. Detects DB exists (has users table) → skips the init.sqlite.sql / init.sql apply step
  3. Runs the column-ALTER block — re-applies safely (no-op on existing columns)
  4. Runs the table-CREATE block — re-applies safely (IF NOT EXISTS)
  5. Hands off to the app
  6. 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:

Terminal window
# Drain replica 1 at the LB
docker compose --profile replica1 pull
docker compose --profile replica1 down
docker 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 others

Because 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.
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).