automc: pg LISTEN/NOTIFY route source + HTTP waker
Adds opt-in extension package internal/automc/ that: - Subscribes to Postgres notifications on a 'servers' table and pushes route changes into server.Routes (no file I/O, no fsnotify). - Provides a WakerFunc that POSTs to a configurable HTTP control plane (server-manager) and polls until state=running. When AUTOMC_DSN is unset, Wire() is a no-op and the binary behaves exactly like upstream itzg/mc-router. Single patch site in main.go (import + 4-line call) keeps upstream rebases trivial. See docs/AUTOMC.md for env vars and the expected DB schema/trigger.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# automc extensions
|
||||
|
||||
Soft fork of `itzg/mc-router` that adds Postgres-driven route management and an HTTP waker, without touching upstream behavior by default.
|
||||
|
||||
The `internal/automc` package is opt-in via env vars: with `AUTOMC_DSN` unset, the binary behaves exactly like upstream.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Var | Required | Purpose |
|
||||
|---|---|---|
|
||||
| `AUTOMC_DSN` | yes (to enable) | Postgres DSN, e.g. `postgres://user:pass@host:5432/automc?sslmode=disable`. When unset, automc is a no-op. |
|
||||
| `AUTOMC_WAKER_URL` | no | Base URL of the waker control plane (server-manager). When set, stopped backends are auto-started on login. |
|
||||
| `AUTOMC_WAKER_TOKEN` | no | Sent as `X-API-Key` header on every waker request. |
|
||||
|
||||
Recommended companion upstream flags:
|
||||
|
||||
- `--use-asleep-motd` / `--use-loading-motd` — supplies friendly MOTD to clients during the wake window. Already implemented upstream; automc does not duplicate this.
|
||||
|
||||
## Postgres schema
|
||||
|
||||
Apply this once to the database referenced by `AUTOMC_DSN`. mc-router only reads; the trigger is what tells it to re-read.
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS servers (
|
||||
name TEXT PRIMARY KEY,
|
||||
domain TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
state TEXT NOT NULL DEFAULT 'stopped',
|
||||
UNIQUE(domain)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION automc_notify_routes_changed() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('automc_routes_changed', '');
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS automc_servers_route_notify ON servers;
|
||||
CREATE TRIGGER automc_servers_route_notify
|
||||
AFTER INSERT OR UPDATE OF domain, address, state OR DELETE ON servers
|
||||
FOR EACH ROW EXECUTE FUNCTION automc_notify_routes_changed();
|
||||
```
|
||||
|
||||
The trigger fires on every mutation to a route-relevant column. mc-router holds a persistent `LISTEN automc_routes_changed` and re-runs `SELECT name, domain, address FROM servers WHERE domain != '' AND address != ''`, diffing against its in-memory map. Adds/removes/changes call `server.Routes.CreateMapping` and `DeleteMapping` directly — no file I/O.
|
||||
|
||||
State column is not read by mc-router; it exists to drive the trigger and for the waker's own ready-check.
|
||||
|
||||
## Waker contract
|
||||
|
||||
When `AUTOMC_WAKER_URL` is set, every route is registered with a `WakerFunc` that the upstream connector calls only when a client tries to LOGIN (not on status pings — those are answered locally via `--use-asleep-motd`).
|
||||
|
||||
The waker:
|
||||
|
||||
1. `POST {AUTOMC_WAKER_URL}/servers/{name}/start` — fire-and-forget start signal. `409 Conflict` is treated as success (already starting).
|
||||
2. Polls `GET {AUTOMC_WAKER_URL}/servers/{name}` every 2 s, expecting JSON `{"state":"running","address":"host:port"}`.
|
||||
3. Returns the polled `address` once `state == "running"`, or errors after 90 s.
|
||||
|
||||
The polled address overrides the route's static address for that connection only — useful when the backend's IP is allocated lazily.
|
||||
|
||||
## Upstream sync
|
||||
|
||||
```
|
||||
make sync-upstream
|
||||
```
|
||||
|
||||
Fetches `upstream/main`, rebases the `automc` branch onto it, builds, and runs the automc tests. The patch surface is intentionally tiny so rebase conflicts are rare:
|
||||
|
||||
```
|
||||
cmd/mc-router/main.go — 1 import line + 4-line Wire call
|
||||
internal/automc/ — new directory; no upstream conflicts possible
|
||||
docs/AUTOMC.md — new doc; no upstream conflicts
|
||||
Makefile — appended targets only
|
||||
go.mod / go.sum — pgx dep added; mergeable
|
||||
```
|
||||
|
||||
If upstream renames `server.Routes.CreateMapping` or changes its signature, only `pgsync.go:apply` needs adjustment.
|
||||
|
||||
## Verification
|
||||
|
||||
```
|
||||
go build ./...
|
||||
go test ./internal/automc/...
|
||||
go vet ./...
|
||||
```
|
||||
Reference in New Issue
Block a user