import { Effect } from "effect"; import { restic } from "../../core/restic"; import type { BackupSchedule, Repository, Volume } from "../../db/schema"; import type { ResticBackupOutputDto, ResticBackupProgressDto } from "@zerobyte/core/restic"; import { createBackupOptions } from "./backup.helpers"; import { getVolumePath } from "../volumes/helpers"; type BackupExecutionRequest = { scheduleId: number; schedule: BackupSchedule; volume: Volume; repository: Repository; organizationId: string; signal: AbortSignal; onProgress: (progress: BackupExecutionProgress) => void; }; export type BackupExecutionProgress = ResticBackupProgressDto; export type BackupExecutionResult = | { status: "unavailable"; error: Error; } | { status: "completed"; exitCode: number; result: ResticBackupOutputDto | null; warningDetails: string | null; } | { status: "failed"; error: unknown; } | { status: "cancelled"; message?: string; }; const activeControllersByScheduleId = new Map(); export const backupExecutor = { track: (scheduleId: number) => { const abortController = new AbortController(); activeControllersByScheduleId.set(scheduleId, abortController); return abortController; }, untrack: (scheduleId: number, abortController: AbortController) => { if (activeControllersByScheduleId.get(scheduleId) === abortController) { activeControllersByScheduleId.delete(scheduleId); } }, execute: async (params: BackupExecutionRequest): Promise => { const { schedule, volume, repository, organizationId, signal, onProgress } = params; try { const volumePath = getVolumePath(volume); const backupOptions = createBackupOptions(schedule, volumePath, signal); const execution = await Effect.runPromise( restic .backup(repository.config, volumePath, { ...backupOptions, compressionMode: repository.compressionMode ?? "auto", organizationId, onProgress, }) .pipe( Effect.map((result) => ({ success: true as const, result })), Effect.catchAll((error) => Effect.succeed({ success: false as const, error })), ), ); if (!execution.success) { throw execution.error; } const { exitCode, result, warningDetails } = execution.result; return { status: "completed", exitCode, result, warningDetails }; } catch (error) { return { status: "failed", error }; } }, cancel: (scheduleId: number) => { const abortController = activeControllersByScheduleId.get(scheduleId); if (!abortController) { return false; } abortController.abort(); return true; }, };