Protect a database

WAL & triggers

These are the other paths for a database — used when you can't put the wire proxy in front of it, or when you also need to catch writes from non-agent sources.

They capture, they don't prevent. Both sit out of the write path, so a statement is already committed by the time they see it. You get a drift-aware undo, but nothing is refused before it runs. Prefer the proxy where you can run one.

One command picks the strategy

bash
export GMONK_DATABASE_URL="…gmonk's own store…"   # set by the Docker stack
export GMONK_TARGET_URL="…the database to protect…"

gmonk protect-db

protect-db detects whether the target is the same Postgres instance as gmonk's store or a separate one, and chooses:

TargetMechanismNotes
Co-located
(owned dev, self-managed, same instance)
WAL if logical decoding is on, otherwise in-database triggers Triggers are the most precise — joins, cascades and range updates all reverse correctly — and are agent-agnostic: psql, an ORM and app code are all captured.
Separate
(managed RDS, Cloud SQL, any other instance)
WAL only gmonk touches the target for the replication slot and REPLICA IDENTITY only — nothing in the write path, no gmonk objects in your database. If WAL can't be activated it reports the target NOT protected rather than installing anything.

Exit status: 0 protected · 2 protected, but some tables have no primary key and can't be reversed row-by-row (reported by name) · 1 not protected or refused.

Enabling logical decoding

gmonk never calls a cloud API, needs no cloud credentials, and never reboots anything. It detects, then instructs. protect-db connects to the target read-only and checks wal_level and whether it can create a replication slot:

  • Ready — it sets capture up and registers the target.
  • Something's missing — it prints the exact change for your provider (an RDS parameter group, a gcloud flag, a postgresql.conf line, a GRANT) and exits non-zero. Apply it with your own tooling, then re-run.
  • Can't tell — it says so and asks you to verify, rather than assuming.

Draining

Co-located targets drain automatically as the agent works. For a separate target the slot lives on the target, so run the drainer — it reads the registry and services every protected target:

bash
gmonk wal-drain              # one pass
gmonk wal-drain --watch 15   # continuous; bounds WAL retention (run as a sidecar)

The drain is crash-safe (peek-then-advance: a failed write is retried, never lost). Add tables later by re-running protect-db, which is idempotent.

Stop protecting a database

bash
gmonk unprotect-db --target-url "$GMONK_TARGET_URL"
# drops the slot, resets REPLICA IDENTITY, removes triggers (co-located), clears the registry

Lower-level primitives

Behind protect-db, still available directly:

bash
gmonk sql-install accounts     # add a capture trigger to one owned table
gmonk wal-protect accounts     # REPLICA IDENTITY FULL on one table (metadata only)
gmonk wal-setup                # create the replication slot
gmonk sql-exec "UPDATE accounts SET balance = balance - 30 WHERE id = 1"
Run one capture mechanism per table — proxy or triggers, not both.