134 lines
4.3 KiB
TypeScript
134 lines
4.3 KiB
TypeScript
import type { BackupConfig, MinioSyncAutomationConfig, MinioSyncTaskConfig } from '../config/schema.js';
|
|
import type { MemoryStore } from '../memory/store.js';
|
|
import type { OutboundMessage } from '../channels/types.js';
|
|
import { parseInterval } from './heartbeat.js';
|
|
import { createMinioSyncTool } from '../tools/builtin/minio-sync.js';
|
|
import { auditLogger } from '../audit/index.js';
|
|
import type { Tool } from '../tools/types.js';
|
|
|
|
interface ChannelLookup {
|
|
get(name: string): { send(peerId: string, message: OutboundMessage): Promise<void> } | undefined;
|
|
}
|
|
|
|
export interface MinioSyncSchedulerDeps {
|
|
config: MinioSyncAutomationConfig;
|
|
backupConfig: BackupConfig;
|
|
memoryStore?: MemoryStore;
|
|
channelLookup: ChannelLookup;
|
|
createSyncTool?: (backupConfig: BackupConfig, memoryStore: MemoryStore) => Tool;
|
|
}
|
|
|
|
export class MinioSyncScheduler {
|
|
private readonly deps: MinioSyncSchedulerDeps;
|
|
private timer: ReturnType<typeof setInterval> | undefined;
|
|
private running = false;
|
|
|
|
constructor(deps: MinioSyncSchedulerDeps) {
|
|
this.deps = deps;
|
|
}
|
|
|
|
start(): void {
|
|
if (!this.deps.config.enabled) {return;}
|
|
if (!this.deps.memoryStore) {
|
|
console.warn('MinioSyncScheduler: automation.minio_sync is enabled but memory is disabled; skipping scheduler');
|
|
return;
|
|
}
|
|
if (this.deps.config.tasks.length === 0) {
|
|
console.warn('MinioSyncScheduler: automation.minio_sync is enabled but no tasks are configured; skipping scheduler');
|
|
return;
|
|
}
|
|
|
|
const intervalMs = parseInterval(this.deps.config.interval);
|
|
console.log(`MinioSyncScheduler: starting (interval=${this.deps.config.interval}, tasks=${this.deps.config.tasks.length})`);
|
|
auditLogger?.systemStart('MinioSyncScheduler', {
|
|
interval: this.deps.config.interval,
|
|
tasks: this.deps.config.tasks.length,
|
|
});
|
|
|
|
this.timer = setInterval(() => {
|
|
this.runOnce().catch((err) => {
|
|
console.error('MinioSyncScheduler: unexpected error during sync cycle:', err);
|
|
});
|
|
}, intervalMs);
|
|
|
|
if (this.deps.config.run_on_start) {
|
|
this.runOnce().catch((err) => {
|
|
console.error('MinioSyncScheduler: unexpected error during startup sync:', err);
|
|
});
|
|
}
|
|
}
|
|
|
|
stop(): void {
|
|
if (this.timer) {
|
|
clearInterval(this.timer);
|
|
this.timer = undefined;
|
|
}
|
|
auditLogger?.systemStop('MinioSyncScheduler');
|
|
}
|
|
|
|
async runOnce(): Promise<{ succeeded: number; failed: number; details: string[] }> {
|
|
if (!this.deps.memoryStore || this.running) {
|
|
return { succeeded: 0, failed: 0, details: [] };
|
|
}
|
|
|
|
this.running = true;
|
|
try {
|
|
const toolFactory = this.deps.createSyncTool ?? createMinioSyncTool;
|
|
const tool = toolFactory(this.deps.backupConfig, this.deps.memoryStore);
|
|
let succeeded = 0;
|
|
let failed = 0;
|
|
const details: string[] = [];
|
|
|
|
for (const task of this.deps.config.tasks) {
|
|
const result = await tool.execute(this.toToolArgs(task));
|
|
if (result.success) {
|
|
succeeded++;
|
|
details.push(`OK ${task.prefix}`);
|
|
} else {
|
|
failed++;
|
|
details.push(`FAIL ${task.prefix}: ${result.error ?? 'Unknown error'}`);
|
|
}
|
|
}
|
|
|
|
if (failed > 0) {
|
|
await this.notify(`MinIO sync completed with failures.\n${details.join('\n')}`);
|
|
} else if (this.deps.config.notify_on_success) {
|
|
await this.notify(`MinIO sync completed successfully.\n${details.join('\n')}`);
|
|
}
|
|
|
|
return { succeeded, failed, details };
|
|
} finally {
|
|
this.running = false;
|
|
}
|
|
}
|
|
|
|
private toToolArgs(task: MinioSyncTaskConfig): Record<string, unknown> {
|
|
return {
|
|
prefix: task.prefix,
|
|
bucket: task.bucket,
|
|
namespace_base: task.namespace_base,
|
|
mode: task.mode,
|
|
max_objects: task.max_objects,
|
|
max_chars_per_object: task.max_chars_per_object,
|
|
force: task.force,
|
|
};
|
|
}
|
|
|
|
private async notify(text: string): Promise<void> {
|
|
const notifyConfig = this.deps.config.notify;
|
|
if (!notifyConfig) {return;}
|
|
|
|
const channel = this.deps.channelLookup.get(notifyConfig.channel);
|
|
if (!channel) {
|
|
console.warn(`MinioSyncScheduler: notification channel '${notifyConfig.channel}' not found`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await channel.send(notifyConfig.peer, { text });
|
|
} catch (err) {
|
|
console.error('MinioSyncScheduler: failed to send notification:', err);
|
|
}
|
|
}
|
|
}
|