100 lines
2.8 KiB
TypeScript
100 lines
2.8 KiB
TypeScript
import { z } from "zod";
|
|
import { addCommonArgs } from "../helpers/add-common-args";
|
|
import { buildEnv } from "../helpers/build-env";
|
|
import { buildRepoUrl } from "../helpers/build-repo-url";
|
|
import { cleanupTemporaryKeys } from "../helpers/cleanup-temporary-keys";
|
|
import { resticSnapshotSummarySchema } from "../restic-dto";
|
|
import type { RepositoryConfig } from "../schemas";
|
|
import { logger, safeSpawn } from "../../node";
|
|
import type { ResticDeps } from "../types";
|
|
import { Data, Effect } from "effect";
|
|
import { toMessage } from "../../utils";
|
|
import { isResticError } from "../error";
|
|
|
|
class ResticSnapshotsCommandError extends Data.TaggedError("ResticSnapshotsCommandError")<{
|
|
cause: unknown;
|
|
message: string;
|
|
}> {}
|
|
|
|
const snapshotInfoSchema = z.object({
|
|
gid: z.number().optional(),
|
|
hostname: z.string(),
|
|
id: z.string(),
|
|
parent: z.string().optional(),
|
|
paths: z.array(z.string()),
|
|
program_version: z.string().optional(),
|
|
short_id: z.string(),
|
|
time: z.string(),
|
|
uid: z.number().optional(),
|
|
username: z.string().optional(),
|
|
tags: z.array(z.string()).optional(),
|
|
summary: resticSnapshotSummarySchema.optional(),
|
|
});
|
|
|
|
export const snapshots = (
|
|
config: RepositoryConfig,
|
|
options: { tags?: string[]; organizationId: string; signal?: AbortSignal },
|
|
deps: ResticDeps,
|
|
) => {
|
|
return Effect.tryPromise({
|
|
try: async () => {
|
|
const { tags, organizationId } = options;
|
|
|
|
const repoUrl = buildRepoUrl(config);
|
|
const env = await buildEnv(config, organizationId, deps);
|
|
|
|
const args = ["--repo", repoUrl, "snapshots"];
|
|
|
|
if (tags && tags.length > 0) {
|
|
for (const tag of tags) {
|
|
args.push("--tag", tag);
|
|
}
|
|
}
|
|
|
|
addCommonArgs(args, env, config);
|
|
|
|
const stdoutLines: string[] = [];
|
|
const res = await safeSpawn({
|
|
command: "restic",
|
|
args,
|
|
env,
|
|
signal: options.signal,
|
|
onStdout: (line) => {
|
|
stdoutLines.push(line);
|
|
},
|
|
});
|
|
await cleanupTemporaryKeys(env, deps);
|
|
|
|
if (options.signal?.aborted) {
|
|
logger.warn("Restic snapshots was aborted by signal.");
|
|
throw new Error("Operation aborted");
|
|
}
|
|
|
|
if (res.exitCode !== 0) {
|
|
const errorMessage = res.stderr || res.error;
|
|
logger.error(`Restic snapshots retrieval failed: ${errorMessage}`);
|
|
throw new Error(`Restic snapshots retrieval failed: ${errorMessage}`);
|
|
}
|
|
|
|
const result = snapshotInfoSchema.array().safeParse(JSON.parse(stdoutLines.join("\n")));
|
|
|
|
if (!result.success) {
|
|
logger.error(`Restic snapshots output validation failed: ${result.error.message}`);
|
|
throw new Error(`Restic snapshots output validation failed: ${result.error.message}`);
|
|
}
|
|
|
|
return result.data;
|
|
},
|
|
|
|
catch: (error) => {
|
|
if (isResticError(error)) {
|
|
return error;
|
|
}
|
|
|
|
return new ResticSnapshotsCommandError({
|
|
cause: error,
|
|
message: toMessage(error),
|
|
});
|
|
},
|
|
});
|
|
};
|