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
+6
-2
@@ -6,6 +6,8 @@ hold secrets.
|
||||
*/
|
||||
import { createHash } from 'node:crypto'
|
||||
import type { HubConfig, HubServer } from './config.ts'
|
||||
import { comparable } from './apps-sync.ts'
|
||||
import { readOverrides } from './store.ts'
|
||||
import { jsonDiff } from './json-diff.ts'
|
||||
import { Upstream, UpstreamError } from './upstream.ts'
|
||||
|
||||
@@ -114,7 +116,7 @@ export async function buildMatrix(config: HubConfig, upstream: Upstream): Promis
|
||||
text = await readConfig(upstream, server, listed.name)
|
||||
if (text !== null && text !== '') {
|
||||
cell.configStatus = 'read'
|
||||
cell.configHash = hash(text)
|
||||
cell.configHash = hash(comparable(text, readOverrides(config.dataDir, listed.name).paths))
|
||||
}
|
||||
} catch (e) {
|
||||
cell.configStatus = 'unreadable'
|
||||
@@ -155,7 +157,9 @@ export async function buildMatrix(config: HubConfig, upstream: Upstream): Promis
|
||||
const versionSet = new Set(copies.map((c) => c.cell.version ?? ''))
|
||||
const versions = copies.length < 2 ? 'single' : versionSet.size === 1 ? 'same' : 'different'
|
||||
|
||||
const readable = copies.filter((c) => c.cell.configStatus !== 'unreadable').map((c) => c.text ?? '')
|
||||
// Fields marked local do not count: two servers that differ only there are the same.
|
||||
const localPaths = readOverrides(config.dataDir, name).paths
|
||||
const readable = copies.filter((c) => c.cell.configStatus !== 'unreadable').map((c) => comparable(c.text ?? '', localPaths))
|
||||
const hasUnknown = copies.some((c) => c.cell.configStatus === 'unreadable') || (warnings.length > 0 && copies.length < 2)
|
||||
const differs = new Set(readable).size > 1
|
||||
const configs: MatrixApp['configs'] = differs ? 'different' : hasUnknown ? 'unknown' : copies.length < 2 ? 'single' : 'same'
|
||||
|
||||
Reference in new issue
Block a user