omnigateway 0.7.3 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +107 -7
  2. package/bin/omni.js +7938 -5697
  3. package/gateway.js +17363 -14714
  4. package/package.json +1 -1
  5. package/public/assets/{Chip-BOyBT5TP.js → Chip-BZWa7onX.js} +1 -1
  6. package/public/assets/{CopyValue-DRHHQlN_.js → CopyValue-Cs2JUZQD.js} +1 -1
  7. package/public/assets/Lamp-BHYJpauE.js +25 -0
  8. package/public/assets/{Rack-D798XvPQ.js → Rack-CRuJZY00.js} +5 -5
  9. package/public/assets/{RequestTable-CEBhwDs7.js → RequestTable-D9hFf4o5.js} +1 -1
  10. package/public/assets/{SummaryDeck-DBCSHihY.js → SummaryDeck-D29h0rFx.js} +1 -1
  11. package/public/assets/{Toggle-CdCCoDEo.js → Toggle-BIHUR4pS.js} +1 -1
  12. package/public/assets/{TokenBreakdown-DDI9uTPW.js → TokenBreakdown-D5mQFSfo.js} +1 -1
  13. package/public/assets/{WindowChart-C-Y6U3WF.js → WindowChart-DVl7kwqb.js} +1 -1
  14. package/public/assets/_app.accounts-C3Yv991L.js +59 -0
  15. package/public/assets/_app.console-Bvo2fugd.js +44 -0
  16. package/public/assets/_app.database-CfWNOIl-.js +24 -0
  17. package/public/assets/{_app.index-CyxnfhVX.js → _app.index-CgI1OrZR.js} +5 -5
  18. package/public/assets/{_app.keys-D9qmw9Tj.js → _app.keys-CcfPhvCD.js} +2 -2
  19. package/public/assets/{_app.logs-DtXUZETW.js → _app.logs-C8ZiArCn.js} +1 -1
  20. package/public/assets/{_app.models-CNxwXyFl.js → _app.models-nfMWzVb4.js} +4 -4
  21. package/public/assets/_app.plugins._pluginId-ByykviWs.js +2 -0
  22. package/public/assets/{_app.plugins._pluginId-87Ox_hkW.js → _app.plugins._pluginId-CE8Jknhs.js} +1 -1
  23. package/public/assets/{_app.settings-DjiMSl1Y.js → _app.settings-C45FJLeX.js} +4 -4
  24. package/public/assets/{_app.usage-CZz9raYR.js → _app.usage-C5s-DfBV.js} +3 -3
  25. package/public/assets/{chevron-right-CPoaXYHw.js → chevron-right-B7JOx5ra.js} +1 -1
  26. package/public/assets/{client-CZnyYbay.js → client-DvN2dbYV.js} +1 -1
  27. package/public/assets/index-BW1O9p6N.js +13 -0
  28. package/public/assets/{login-dV42ZQIA.js → login-9Ovj_4DV.js} +2 -2
  29. package/public/assets/{login-BtlMmyKj.js → login-DvRXHEGv.js} +2 -2
  30. package/public/assets/plus-64jUPIDJ.js +1 -0
  31. package/public/assets/{preload-helper-4wSmu2-E.js → preload-helper-D2sCuvIY.js} +1 -1
  32. package/public/assets/{shared-OJcoRCqT.js → shared-BKWNmsjH.js} +1 -1
  33. package/public/assets/{stream-CEb81tzM.js → stream-BUWwtwme.js} +1 -1
  34. package/public/assets/{trash-2-BjpqTubo.js → trash-2-rvGFhAn8.js} +1 -1
  35. package/public/index.html +7 -7
  36. package/public/shared/dashboard-sdk.js +1 -1
  37. package/public/assets/Lamp-ZlMzNs5N.js +0 -25
  38. package/public/assets/_app.accounts-CLLLwN77.js +0 -59
  39. package/public/assets/_app.console-Dr9qxU7J.js +0 -44
  40. package/public/assets/_app.database-D0W0pcxC.js +0 -24
  41. package/public/assets/_app.plugins._pluginId-CZWipH1n.js +0 -2
  42. package/public/assets/index-DNSPtYpM.js +0 -13
  43. package/public/assets/plus-_zo43Lvf.js +0 -1
package/README.md CHANGED
@@ -487,6 +487,9 @@ Configuration is environment variables, read from the installation's `.env`:
487
487
  | `OMNI_BODY_LOGGING_ALLOWED` | No | unset | Permits request/response body capture on this installation. Read at boot. Capture also needs the runtime setting; see [Recording bodies](#recording-bodies) |
488
488
  | `OMNI_ROOT` | No | the installation in the current directory, else `~/.config/omnigateway` | Which installation the CLI acts on, when `--root` is not passed |
489
489
  | `OMNI_PLUGIN_REGISTRY` | No | the public npm registry | Registry `omni plugin install <name>` resolves through; must be `https://` |
490
+ | `OMNI_CLUSTER_MODE` | No | unset | `true` selects [cluster mode](#running-more-than-one-gateway) and requires the two URLs below; unset is one process on SQLite, and then the URLs must be unset too |
491
+ | `OMNI_DATABASE_URL` | In cluster mode | — | The shared Postgres store |
492
+ | `OMNI_REDIS_URL` | In cluster mode | — | The coordinator every process of a cluster shares: rate-limit counters, sessions, leases, push fan-out |
490
493
 
491
494
  `OMNI_ROOT` is the one variable read from your shell and never from a root's `.env`, for the
492
495
  reason it has to be: a variable that selects the installation cannot live inside the installation
@@ -513,6 +516,15 @@ bun apps/gateway/src/index.ts >> /var/log/omni.log 2>&1
513
516
  `omni start` does both for the gateway it supervises, and under systemd the journal needs no
514
517
  setup.
515
518
 
519
+ In a fleet, capture is per process. Each replica can capture its own — tee its stdout to a file
520
+ inside the container and point `OMNI_LOG_FILE` at the same path — and the Console screen then
521
+ merges every process's tail and lets you pick one. That is worth having for an incident on a
522
+ running pod, and it is not a log stack: the file dies with the container, nothing rotates it, and
523
+ the screen reads one process at a time. Ship stdout to a collector for anything beyond that —
524
+ Elasticsearch and Kibana, Loki and Grafana, or whatever already reads your containers — where the
525
+ lines outlive the process that wrote them and can be searched across all of them at once. The
526
+ Console screen says so itself when it finds a fleet capturing nothing.
527
+
516
528
  Everything else lives in the database rather than the environment, so it can be
517
529
  changed without a restart: the six routing weights, `maxAttempts`,
518
530
  `requestDeadlineMs`, the circuit breaker's `breakerThreshold` and
@@ -525,6 +537,70 @@ boot, so a change to it takes a restart. Snapshot retention —
525
537
  deliberately edited on the Database screen instead; see
526
538
  [Snapshots and restore](#snapshots-and-restore) for why.
527
539
 
540
+ ## Running more than one gateway
541
+
542
+ One process on SQLite is the default and is what every command in this document assumes. A
543
+ fleet — several replicas behind a load balancer, on Kubernetes or otherwise — is **cluster
544
+ mode**, switched on by `OMNI_CLUSTER_MODE=true` and needing two things beside the gateway:
545
+
546
+ - **Postgres** as the store, named by `OMNI_DATABASE_URL`. Every replica reads and writes one
547
+ database; there is no SQLite file, no snapshot, no restore, and no `omni db vacuum` — those are
548
+ `pg_dump`'s job now, and the Database screen says so.
549
+ - **Redis** (or Valkey) as the coordinator, named by `OMNI_REDIS_URL`. It holds what a fleet must
550
+ agree on and a database is the wrong shape for: the per-minute request ring and the concurrency
551
+ gauge, the long-window counters, admin sessions, pending OAuth flows, quota-probe cooldowns, the
552
+ leases that make the background loops run once rather than N times, and the fan-out that lets a
553
+ console on one replica hear a change made on another.
554
+
555
+ ```bash
556
+ OMNI_CLUSTER_MODE=true
557
+ OMNI_DATABASE_URL=postgres://omni:secret@db.internal:5432/omni
558
+ OMNI_REDIS_URL=redis://cache.internal:6379
559
+ OMNI_ENCRYPTION_KEY=… # the same on every replica
560
+ ```
561
+
562
+ Boot refuses the switch without both URLs, and refuses either URL without the switch: a
563
+ replica that believes it is clustered and is not is the failure this variable exists to make
564
+ loud.
565
+
566
+ What holds across the fleet, exactly: every API-key limit at every window and dimension; token
567
+ refresh, which one replica performs while the others wait and reuse the result; a cookie issued
568
+ by one replica, which every other verifies and a password change ends everywhere. What is
569
+ per-replica and says so: the console's **Console** screen shows one process's stdout, so it grows
570
+ a selector when there is more than one, and its default view is every process merged by time.
571
+ What is per-replica and does not say so: the routing `load` weight is one round trip stale
572
+ between replicas, so a burst arriving at once on two of them can stack for that long.
573
+
574
+ **No sticky sessions are needed.** The WebSocket the console holds may land on any replica; the
575
+ ingress only needs to pass upgrades and hold an idle timeout above ten seconds. A rolling deploy
576
+ closes each replica's sockets with `1001`, the console reconnects to a live one and refetches
577
+ once.
578
+
579
+ **When Redis is unreachable**, the request path keeps serving: each replica falls back to its
580
+ own in-memory counters, so limits degrade to N-fold until Redis returns, and one line per thirty
581
+ seconds says so (`coord=redis coordFallback=true`). The console does not: a session that cannot
582
+ be checked against the shared store is refused with `503`, because a session verified locally is
583
+ one a password change on another replica cannot end. `GET /health` reports `mode`, `nodeId` and
584
+ `coord` (`ok` or `fallback`) for a readiness probe to read.
585
+
586
+ Plugins are loaded from each replica's own `<root>/plugins/`; bake them into the image so every
587
+ replica holds the same set. Plugin storage is Postgres in cluster mode, so a plugin's SQL is
588
+ written for it. `POST /api/restart` refuses in cluster mode — roll the deployment instead.
589
+
590
+ Moving an existing SQLite installation onto Postgres:
591
+
592
+ ```bash
593
+ omni stop
594
+ omni db migrate --to postgres://omni:secret@db.internal:5432/omni
595
+ ```
596
+
597
+ It copies credentials (re-encrypted with the same `OMNI_ENCRYPTION_KEY`), API keys, virtual
598
+ models, settings, both passwords and every completed request log into an **empty** Postgres
599
+ database, rebuilds the rollups, and prints what it did not carry: request bodies, `usage_daily`
600
+ older than the retained logs, quota readings and breaker state (re-measured within a poll
601
+ interval), sessions, and `plugin_*` tables, whose SQL is the source dialect's. It refuses while a
602
+ gateway is running and refuses a target that holds anything.
603
+
528
604
  ## Recording bodies
529
605
 
530
606
  By default the gateway records no prompts and no responses. For incident
@@ -574,9 +650,12 @@ format, structural bounds, and masking rules.
574
650
  ## Snapshots and restore
575
651
 
576
652
  The console's Database screen reports what this installation occupies — the
577
- database file, its write-ahead log, the captured-body tree, and the free pages a
578
- compaction would give back and takes snapshots. `omni db stats` prints the same
579
- figures.
653
+ database file, its write-ahead log, the captured-body tree, the free pages a
654
+ compaction would give back, and every table by size and takes snapshots.
655
+ `omni db stats` prints the same figures. On Postgres both show the server's own
656
+ size, the `request_bodies` table and the per-table listing instead; there is no
657
+ file, so nothing here compacts, snapshots or restores it — `pg_dump` is the
658
+ backup.
580
659
 
581
660
  **What a snapshot is.** One self-contained SQLite file, written into a
582
661
  `snapshots/` directory beside the database. The write-ahead log is folded in, so
@@ -648,10 +727,31 @@ docker run --rm \
648
727
  omnigateway
649
728
  ```
650
729
 
651
- The container listens on `0.0.0.0:9000` and keeps its database at
652
- `/data/omnigateway.db`. Note that **the image builds the gateway only**: it
653
- serves the APIs and returns 404 for the console. Use the CLI or the control API
654
- against it, or install the npm package if you want the console.
730
+ The container listens on `0.0.0.0:9000`, serves the console, and keeps its
731
+ database and plugins under `/data`. It runs as the unprivileged `bun` user and
732
+ carries a `HEALTHCHECK` on `/health`.
733
+
734
+ For a fleet, set `OMNI_CLUSTER_MODE=true` with `OMNI_DATABASE_URL` and `OMNI_REDIS_URL` and
735
+ drop the volume;
736
+ see [Running more than one gateway](#running-more-than-one-gateway). A
737
+ Kubernetes deployment — Deployment, Service, Ingress with the timeouts streaming
738
+ needs, HPA, and an example Secret — is under `k8s/` as a kustomize
739
+ base:
740
+
741
+ ```bash
742
+ cp k8s/secret.example.yaml k8s/secret.yaml # edit it
743
+ kubectl apply -f k8s/secret.yaml
744
+ kubectl apply -k k8s
745
+ ```
746
+
747
+ Releases deploy by GitOps: a `v*` tag publishes `ghcr.io/harismawan/omnigateway:<version>`
748
+ and the workflow commits that version into `k8s/kustomization.yaml` on `main`, which Argo
749
+ CD syncs. Rolling back is editing `newTag` by hand.
750
+
751
+ Plugins in a fleet are baked into the image so every replica holds the same
752
+ set: `COPY plugins/ /data/plugins/` in a derived Dockerfile. The image is
753
+ public, so no pull secret is configured; a private fork adds
754
+ `imagePullSecrets` to the Deployment.
655
755
 
656
756
  Give the container a restart policy — `--restart unless-stopped` — if you want a
657
757
  restart request to bring it back. A container cannot read its own policy, so