mirror of
https://github.com/nicotsx/zerobyte.git
synced 2026-04-21 07:22:47 -04:00
* feat(agent): add standalone agent runtime * fix(backups): bridge local executor to Effect restic API * fix(agent): add Bun and DOM types to agent tsconfig * refactor: wrap backup error in a tagged effect error * fix: pr feedbacks
93 lines
2.6 KiB
TypeScript
93 lines
2.6 KiB
TypeScript
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<number, AbortController>();
|
|
|
|
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<BackupExecutionResult> => {
|
|
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;
|
|
},
|
|
};
|