fix(fleet): harden remote node updates with admin enforcement, expiry fix, and diagnostics (#542)

- Fix completed-entry auto-expiry using resolvedAt instead of startedAt
- Add admin role enforcement to update trigger and update-all endpoints
- Add missing error field in rejected-promise fallback for update-status
- Fix rejected promises in update-all losing node names
- Harden frontend recheck button with try/catch/finally error handling
- Align frontend isValidVersion with stricter regex validation
- Add diagnostic logging gated behind developer_mode
- Extract resolveTracker helper to centralize terminal state transitions
- Add 15 new fleet test cases covering auth, tier gating, input validation, and admin roles
- Document admin requirement and troubleshooting in fleet-view docs
This commit is contained in:
Anso
2026-04-12 22:07:41 -04:00
committed by GitHub
parent a0fe84e84b
commit d23c6779af
6 changed files with 217 additions and 30 deletions
@@ -3,6 +3,7 @@ import { promisify } from 'util';
import * as fs from 'fs';
import DockerController from './DockerController';
import { disableCapability } from './CapabilityRegistry';
import { isDebugEnabled } from '../utils/debug';
const execFileAsync = promisify(execFile);
@@ -148,12 +149,16 @@ class SelfUpdateService {
// Async pull: a sync execFileSync blocks the event loop, which lets the frontend
// overlay see a false "online" response between the pull finishing and the restart.
const debug = isDebugEnabled();
const pullStart = Date.now();
console.log(`[SelfUpdate] Pulling latest image: ${imageName}...`);
if (debug) console.debug('[SelfUpdate:debug] Pull context:', { workingDir, configFiles, serviceName, dataDirHost, mountCount: hostBindMounts.length });
try {
await execFileAsync('docker', ['pull', imageName], {
env,
timeout: 300_000, // 5 min max for pull
});
if (debug) console.debug('[SelfUpdate:debug] Pull completed in', Math.round((Date.now() - pullStart) / 1000) + 's');
} catch (error) {
const stderr = (error as { stderr?: Buffer | string })?.stderr?.toString().trim();
this.lastUpdateError = stderr || (error as Error).message;