Verify Plutonium revisions during provisioning
This commit is contained in:
@@ -35,20 +35,30 @@ export function runningVersions(): PlutoniumVersion[] {
|
||||
});
|
||||
}
|
||||
export async function probeVersion(server: string): Promise<number | null> {
|
||||
const original = getServer(server);
|
||||
if (!original) return null;
|
||||
const epoch = latestGameSubmission(server);
|
||||
const recovering = restartRecoveryPending(server);
|
||||
const health = !recovering && getServer(server)?.desired_state !== 'stopped'
|
||||
const health = !recovering && original.desired_state !== 'stopped'
|
||||
? await komodoInspectHealthy(server).catch(() => ({ ok: false })) : { ok: false };
|
||||
let result = !health.ok ? { revision: null, error: 'Waiting for a healthy running server.' }
|
||||
: await rconPlutoniumVersion(server).catch(() => ({ revision: null, error: 'Version check failed.' }));
|
||||
if (!getServer(server)) return null;
|
||||
if (epoch !== latestGameSubmission(server) || restartRecoveryPending(server)) {
|
||||
const current = getServer(server);
|
||||
// An older probe must not clear evidence already recorded for a new process
|
||||
// or replacement registration, including replacements with no submission yet.
|
||||
if (!current || current.created_at !== original.created_at || epoch !== latestGameSubmission(server)) return null;
|
||||
const checkedAt = nowIso();
|
||||
if (restartRecoveryPending(server)) {
|
||||
result = { revision: null, error: 'Waiting for restart recovery.' };
|
||||
} else if (current.desired_state === 'stopped') {
|
||||
result = { revision: null, error: 'Server is intentionally stopped.' };
|
||||
} else if (epoch && checkedAt <= epoch) {
|
||||
result = { revision: null, error: 'Waiting for a version observation after startup.' };
|
||||
}
|
||||
// Clear the old revision on failure: it is no longer evidence of what is running.
|
||||
db().prepare(`INSERT INTO plutonium_versions (server, revision, checked_at, error) VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(server) DO UPDATE SET revision = excluded.revision, checked_at = excluded.checked_at, error = excluded.error`)
|
||||
.run(server, result.revision, nowIso(), result.error);
|
||||
.run(server, result.revision, checkedAt, result.error);
|
||||
return result.revision;
|
||||
}
|
||||
|
||||
@@ -59,6 +69,23 @@ export function checkPlutonium(): Promise<void> {
|
||||
return checking;
|
||||
}
|
||||
async function check(): Promise<void> {
|
||||
await checkRelease();
|
||||
const servers = listServers();
|
||||
for (let i = 0; i < servers.length; i += 4) {
|
||||
await Promise.all(servers.slice(i, i + 4).map(s => probeVersion(s.name)));
|
||||
}
|
||||
writeSetting('plutonium_versions_checked_at', nowIso());
|
||||
}
|
||||
|
||||
let checkingRelease: Promise<void> | null = null;
|
||||
/** Refresh the published target without probing unrelated fleet servers. */
|
||||
export function checkRelease(): Promise<void> {
|
||||
if (checkingRelease) return checkingRelease;
|
||||
checkingRelease = fetchRelease().finally(() => { checkingRelease = null; });
|
||||
return checkingRelease;
|
||||
}
|
||||
|
||||
async function fetchRelease(): Promise<void> {
|
||||
const previous = release();
|
||||
try {
|
||||
const response = await fetch(PLUTONIUM_MANIFEST_URL, { signal: AbortSignal.timeout(config.httpTimeoutMs), redirect: 'error' });
|
||||
@@ -69,8 +96,4 @@ async function check(): Promise<void> {
|
||||
} catch (err) {
|
||||
writeSetting('plutonium_release', { ...previous, checkedAt: nowIso(), error: (err as Error).message });
|
||||
}
|
||||
const servers = listServers();
|
||||
for (let i = 0; i < servers.length; i += 4) {
|
||||
await Promise.all(servers.slice(i, i + 4).map(s => probeVersion(s.name)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user