Hub stage 3: compare and sync app configs across servers
The hub keeps a baseline per app in /data/apps/<app>/baseline.json. Local fields (overrides.json) are left out of the sync and out of the matrix comparison. Each server gets the baseline plus its own values. Applying goes target picker, preview with per-server diffs and warnings, then apply. Apply refuses a server whose config changed since the preview, saves the old config to history, then reads the new one back to check it. Large config bodies are read only for signed-in callers. The App config dialog has Compare, Baseline and Local fields tabs and an empty state that offers an import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6acd2d656d
commit
0e05039202
22 files changed
+2434
-58
No files matched your search
+32
-1
@@ -83,7 +83,7 @@ Public: `status`, `user/login`. After signing in: `user/session/get`, `user/logo
|
||||
`logs/query`, `logs/list`, `settings/get`, `admin/cluster/state` (synthesized), and the hub's own `hub/audit/list` (role with
|
||||
`audit.read`). They are listed in `routes.ts` with a kind (`read`, `write-one`, `write-all`) and the capability they need.
|
||||
|
||||
Everything else answers `403 The hub is read-only.` and is audited. `apps/config/get` and `apps/config/set` are not available yet.
|
||||
Everything else answers `403 The hub is read-only.` and is audited.
|
||||
|
||||
## App writes
|
||||
|
||||
@@ -99,6 +99,37 @@ Every write goes to a server with its `<ID>_ADMIN_TOKEN`; a server without one a
|
||||
the console's own single-server calls, forwarded to that server (store URLs checked as above). Refused without a node, and
|
||||
with `node=cluster`.
|
||||
- `apps/listStoreApps`: the first server's store, each app tagged with `installedOn`.
|
||||
- `apps/config/get` (read, `node` required) and `apps/config/set` (`apps.write`, `node` required; the old config is saved to the
|
||||
history first and the call is refused if that fails): the console's own per-server Config dialog.
|
||||
|
||||
## Config sync
|
||||
|
||||
Keeps one app's config the same on every server, except for the fields that must differ. Per app the hub stores, under
|
||||
`${HUB_DATA}/apps/<app>/`:
|
||||
|
||||
- `baseline.json`: `{ app, text, importedFrom?, updatedAt, updatedBy }`. The config as TEXT, so non-JSON configs work too.
|
||||
- `overrides.json`: `{ paths: [...], values: { <server>: { <path>: <value> } } }`. Paths are dotted (`db.path`,
|
||||
`networks.0.cidr`); at most 50; only for a JSON baseline.
|
||||
- `history/<time>__<server>__before-apply.txt` (and `before-uninstall`): the config a server had before the hub changed it
|
||||
(50 files per app kept).
|
||||
|
||||
A server's effective config is the baseline with that server's local values put in (JSON, pretty-printed with 2 spaces). A local
|
||||
path with no value for a server keeps the baseline value and is reported as a warning. Comparisons are by meaning (key order and
|
||||
spacing do not count) and leave the local fields out, so two servers that differ only there are "Same" in the matrix, and the
|
||||
matrix hash ignores them too.
|
||||
|
||||
| Endpoint | Method | Needs | Does |
|
||||
|---|---|---|---|
|
||||
| `hub/apps/config/compare?name=` | GET | read | Each server's live config, the baseline, the local fields, `inSync`, and the differences against the baseline and against the other servers. Config texts are returned here, to roles that can read. |
|
||||
| `hub/apps/overrides/current` | POST `{name, paths}` | `apps.write` | The servers' current values of those paths. |
|
||||
| `hub/apps/baseline/import` | POST `{name, server}` | `apps.write` | Baseline := that server's live config. |
|
||||
| `hub/apps/baseline/save` | POST `{name, text}` | `apps.write` | Saves the baseline (1 MB at most). Text that starts with `{` or `[` must be valid JSON; the parse error is returned. |
|
||||
| `hub/apps/overrides/save` | POST `{name, paths, values}` | `apps.write` | Saves the local fields. |
|
||||
| `hub/apps/config/preview` | POST `{name, targets}` | `apps.write` | Per target: current and effective text, the diff, warnings (a missing local value, a version older than the newest installed, no admin token) and `currentHash`. |
|
||||
| `hub/apps/config/apply` | POST `{name, targets, expect?}` | `apps.write` | Per target: re-reads the config; refuses if its hash differs from `expect[server]`; saves the old config to the history; `apps/config/set` with the admin token; reads it back and checks it. A server that rejects the config keeps its old one. |
|
||||
|
||||
Reading configs needs View on Apps for the `hub-reader` user (and `*_ADMIN_TOKEN` is used as a fallback if a server refuses the
|
||||
read token). The audit log records who did what, with the size and a short hash of a config, never the text.
|
||||
|
||||
Before an uninstall the app's config is saved to `${HUB_DATA}/apps/<app>/history/<time>__<server>__before-uninstall.txt`
|
||||
(50 files per app are kept). If that cannot be done the uninstall does not happen. Every target's result is written to the
|
||||
|
||||
Reference in new issue
Block a user