mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-09 18:32:52 +00:00
feat(security): one-click managed Trivy install (#643)
* feat(security): one-click managed Trivy install Add a Vulnerability Scanner card to Settings, Security with install, update, uninstall, and auto-update controls (Admiral-only). The installer downloads a verified Trivy release into the existing data volume at /app/data/bin/trivy and defaults the cache to /app/data/trivy-cache, so no host mounts or extra env vars are required. Detection probes the managed path, a TRIVY_BIN override, and the host PATH, distinguishing managed vs host installs. A daily scheduled check surfaces available Trivy updates, installs them automatically when opted in, and dedupes notifications per version. * fix(frontend): silence react-hooks/set-state-in-effect in useTrivyStatus The initial status fetch and managed-source update check both call setState from the effect body. Match the existing pattern used in useDashboardData / SSOSection and disable the rule at the call site.
This commit is contained in:
@@ -13,6 +13,10 @@ import { captureLocalNodeFiles, captureRemoteNodeFiles } from '../utils/snapshot
|
||||
import { NodeRegistry } from './NodeRegistry';
|
||||
import { NotificationService } from './NotificationService';
|
||||
import TrivyService from './TrivyService';
|
||||
import TrivyInstaller from './TrivyInstaller';
|
||||
|
||||
const TRIVY_UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
const TRIVY_UPDATE_CHECK_STARTUP_DELAY_MS = 5 * 60 * 1000;
|
||||
|
||||
const TRIVY_REDETECT_INTERVAL_MS = 10 * 60 * 1000;
|
||||
const STALE_SCAN_THRESHOLD_MS = 15 * 60 * 1000;
|
||||
@@ -20,7 +24,10 @@ const STALE_SCAN_THRESHOLD_MS = 15 * 60 * 1000;
|
||||
export class SchedulerService {
|
||||
private static instance: SchedulerService;
|
||||
private intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
private trivyUpdateIntervalId: ReturnType<typeof setInterval> | null = null;
|
||||
private trivyUpdateStartupTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private isProcessing = false;
|
||||
private isCheckingTrivyUpdate = false;
|
||||
private runningTasks = new Set<number>();
|
||||
private lastTrivyRedetect = 0;
|
||||
|
||||
@@ -38,6 +45,8 @@ export class SchedulerService {
|
||||
this.cleanupStaleRuns();
|
||||
this.intervalId = setInterval(() => this.tick(), 60_000);
|
||||
setTimeout(() => this.tick(), 10_000);
|
||||
this.trivyUpdateStartupTimer = setTimeout(() => this.runTrivyUpdateCheck(), TRIVY_UPDATE_CHECK_STARTUP_DELAY_MS);
|
||||
this.trivyUpdateIntervalId = setInterval(() => this.runTrivyUpdateCheck(), TRIVY_UPDATE_CHECK_INTERVAL_MS);
|
||||
console.log('[SchedulerService] Started');
|
||||
}
|
||||
|
||||
@@ -46,9 +55,61 @@ export class SchedulerService {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
if (this.trivyUpdateIntervalId) {
|
||||
clearInterval(this.trivyUpdateIntervalId);
|
||||
this.trivyUpdateIntervalId = null;
|
||||
}
|
||||
if (this.trivyUpdateStartupTimer) {
|
||||
clearTimeout(this.trivyUpdateStartupTimer);
|
||||
this.trivyUpdateStartupTimer = null;
|
||||
}
|
||||
console.log('[SchedulerService] Stopped');
|
||||
}
|
||||
|
||||
private async runTrivyUpdateCheck(): Promise<void> {
|
||||
if (this.isCheckingTrivyUpdate) return;
|
||||
this.isCheckingTrivyUpdate = true;
|
||||
try {
|
||||
const trivy = TrivyService.getInstance();
|
||||
if (!trivy.isTrivyAvailable() || trivy.getSource() !== 'managed') return;
|
||||
const db = DatabaseService.getInstance();
|
||||
const settings = db.getGlobalSettings();
|
||||
const autoUpdate = settings.trivy_auto_update === '1';
|
||||
const installer = TrivyInstaller.getInstance();
|
||||
if (installer.isBusy()) return;
|
||||
const check = await installer.checkForUpdate(trivy.getVersion(), 'managed');
|
||||
if (!check.updateAvailable) return;
|
||||
|
||||
if (autoUpdate) {
|
||||
const previous = trivy.getVersion() ?? 'unknown';
|
||||
console.log(`[SchedulerService] Auto-updating Trivy from ${previous} to ${check.latest}`);
|
||||
try {
|
||||
await installer.update();
|
||||
await trivy.detectTrivy();
|
||||
NotificationService.getInstance().dispatchAlert(
|
||||
'info',
|
||||
`Trivy updated from v${previous} to v${check.latest}`,
|
||||
);
|
||||
db.updateGlobalSetting('trivy_last_notified_version', check.latest);
|
||||
} catch (err) {
|
||||
console.error('[SchedulerService] Trivy auto-update failed:', getErrorMessage(err, 'unknown error'));
|
||||
}
|
||||
} else {
|
||||
const lastNotified = settings.trivy_last_notified_version || '';
|
||||
if (lastNotified === check.latest) return;
|
||||
NotificationService.getInstance().dispatchAlert(
|
||||
'info',
|
||||
`Trivy update available: v${check.latest} (currently v${check.current ?? 'unknown'})`,
|
||||
);
|
||||
db.updateGlobalSetting('trivy_last_notified_version', check.latest);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[SchedulerService] Trivy update check failed:', getErrorMessage(err, 'unknown error'));
|
||||
} finally {
|
||||
this.isCheckingTrivyUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupStaleRuns(): void {
|
||||
try {
|
||||
const count = DatabaseService.getInstance().markStaleRunsAsFailed();
|
||||
|
||||
Reference in New Issue
Block a user