feat: Wire execution engines for schedules, alerts, wipes, and module install
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
- schedules/: Add schedule executor in SchedulesService — polls every 60s for tasks where next_run <= now, dispatches NATS commands per task_type (restart, announcement, command, plugin_reload). Calculates next_run from cron expression on create/update/toggle. Bootstraps missing next_run values on startup. Wire NatsService into SchedulesModule. - alerts/: Add alert evaluator in AlertsService — polls every 90s, loads all alert_config rows, queries latest server_stats per license, evaluates FPS degradation and population drop thresholds. Fires alert_history records on breach. Enforces 10-minute in-memory cooldown per alert type per license to prevent flooding. Wire ServerStats repo into AlertsModule. - wipes/: Replace hardcoded dry-run mock with profile-aware simulation. Resolves actual WipeProfile by ID (cross-tenant protected), builds would_delete/would_preserve lists from wipe_type, factors pre_wipe_config (backup, countdown warnings) and post_wipe_config (health checks, retry attempts) into estimated_duration_seconds. Returns profile_name and notes. - store/: Fix installModule stub — creates a real module_installations record with status='installed' and installed_at timestamp. Idempotent on retry, resets failed installations. Wire ModuleInstallation repo into StoreModule. getMyModules now returns real installation data instead of filtered purchases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -126,19 +126,112 @@ export class WipesService {
|
||||
would_delete: string[];
|
||||
would_preserve: string[];
|
||||
estimated_duration_seconds: number;
|
||||
profile_name: string | null;
|
||||
notes: string[];
|
||||
}> {
|
||||
// Stub implementation - real logic would analyze wipe profile config
|
||||
const mockResult = {
|
||||
would_delete: ['*.sav', '*.db', 'player.deaths.db', 'player.identities.db'],
|
||||
would_preserve: ['oxide/', 'oxide/plugins/', 'oxide/data/', 'backups/'],
|
||||
estimated_duration_seconds: 45,
|
||||
};
|
||||
|
||||
if (dto.wipe_type === 'full') {
|
||||
mockResult.would_delete.push('oxide/data/*');
|
||||
mockResult.estimated_duration_seconds = 120;
|
||||
// Resolve profile config if a profile ID was supplied.
|
||||
let profile: WipeProfile | null = null;
|
||||
if (dto.wipe_profile_id) {
|
||||
profile = await this.wipeProfileRepo.findOne({
|
||||
where: { id: dto.wipe_profile_id, license_id: licenseId },
|
||||
});
|
||||
}
|
||||
|
||||
return mockResult;
|
||||
if (!profile && dto.wipe_profile_id) {
|
||||
throw new NotFoundException(`Wipe profile ${dto.wipe_profile_id} not found`);
|
||||
}
|
||||
|
||||
const notes: string[] = [];
|
||||
|
||||
// Base files affected by all wipe types.
|
||||
const would_delete: string[] = ['*.map', '*.sav'];
|
||||
const would_preserve: string[] = [
|
||||
'oxide/',
|
||||
'oxide/plugins/',
|
||||
'cfg/',
|
||||
'server.cfg',
|
||||
];
|
||||
|
||||
// Blueprint wipe additions.
|
||||
if (dto.wipe_type === 'blueprint' || dto.wipe_type === 'full') {
|
||||
would_delete.push('player.blueprints.db', 'player.tech.db');
|
||||
}
|
||||
|
||||
// Full wipe: also clear player data and oxide data.
|
||||
if (dto.wipe_type === 'full') {
|
||||
would_delete.push(
|
||||
'player.deaths.db',
|
||||
'player.identities.db',
|
||||
'player.states.db',
|
||||
'player.tokens.db',
|
||||
'oxide/data/*',
|
||||
);
|
||||
would_preserve.splice(would_preserve.indexOf('oxide/'), 1);
|
||||
}
|
||||
|
||||
// Factor in pre_wipe_config from the profile (if set).
|
||||
let estimatedSeconds = 45;
|
||||
|
||||
if (profile) {
|
||||
const pre = profile.pre_wipe_config as Record<string, any>;
|
||||
const post = profile.post_wipe_config as Record<string, any>;
|
||||
|
||||
if (pre?.backup_before_wipe) {
|
||||
estimatedSeconds += 60;
|
||||
notes.push('Pre-wipe backup will run before deletion (+60s)');
|
||||
would_preserve.push('backups/');
|
||||
}
|
||||
|
||||
if (pre?.kick_players_before_wipe) {
|
||||
const countdownWarnings: number[] = (pre.countdown_warnings as number[]) ?? [];
|
||||
const maxWarning = countdownWarnings.length > 0 ? Math.max(...countdownWarnings) : 0;
|
||||
if (maxWarning > 0) {
|
||||
estimatedSeconds += maxWarning * 60;
|
||||
notes.push(`Players will be warned ${countdownWarnings.join(', ')} minutes before kick (+${maxWarning * 60}s)`);
|
||||
}
|
||||
}
|
||||
|
||||
if (post?.verify_server_started) {
|
||||
estimatedSeconds += 30;
|
||||
notes.push('Post-wipe: server health check will run (+30s)');
|
||||
}
|
||||
|
||||
if (post?.rollback_on_failure) {
|
||||
notes.push('Rollback on failure is enabled — backup will be preserved if wipe fails');
|
||||
}
|
||||
|
||||
if (post?.max_restart_attempts) {
|
||||
const attempts = post.max_restart_attempts as number;
|
||||
if (attempts > 1) {
|
||||
estimatedSeconds += (attempts - 1) * 15;
|
||||
notes.push(`Up to ${attempts} restart attempts (+${(attempts - 1) * 15}s max)`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
notes.push('No profile selected — using default wipe behavior');
|
||||
}
|
||||
|
||||
// Account for world size in time estimate.
|
||||
// Larger worlds take longer to clear from disk (rough heuristic).
|
||||
// We don't have world_size here without querying server_config,
|
||||
// so apply a static estimate per wipe type.
|
||||
if (dto.wipe_type === 'full') {
|
||||
estimatedSeconds += 75;
|
||||
} else if (dto.wipe_type === 'blueprint') {
|
||||
estimatedSeconds += 10;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Dry-run for license ${licenseId}: type=${dto.wipe_type}, ` +
|
||||
`profile=${profile?.profile_name ?? 'none'}, estimated=${estimatedSeconds}s`,
|
||||
);
|
||||
|
||||
return {
|
||||
would_delete,
|
||||
would_preserve,
|
||||
estimated_duration_seconds: estimatedSeconds,
|
||||
profile_name: profile?.profile_name ?? null,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user