feat: remove individual snapshot (#26)

This commit is contained in:
Nico
2025-11-16 11:14:18 +01:00
committed by GitHub
parent c0bef7f65e
commit e5435969be
11 changed files with 452 additions and 108 deletions

View File

@@ -4,6 +4,7 @@ import {
createRepositoryBody,
createRepositoryDto,
deleteRepositoryDto,
deleteSnapshotDto,
doctorRepositoryDto,
getRepositoryDto,
getSnapshotDetailsDto,
@@ -16,6 +17,7 @@ import {
restoreSnapshotBody,
restoreSnapshotDto,
type DeleteRepositoryDto,
type DeleteSnapshotDto,
type DoctorRepositoryDto,
type GetRepositoryDto,
type GetSnapshotDetailsDto,
@@ -142,4 +144,11 @@ export const repositoriesController = new Hono()
const result = await repositoriesService.doctorRepository(name);
return c.json<DoctorRepositoryDto>(result, 200);
})
.delete("/:name/snapshots/:snapshotId", deleteSnapshotDto, async (c) => {
const { name, snapshotId } = c.req.param();
await repositoriesService.deleteSnapshot(name, snapshotId);
return c.json<DeleteSnapshotDto>({ message: "Snapshot deleted" }, 200);
});

View File

@@ -326,3 +326,28 @@ export const listRcloneRemotesDto = describeRoute({
},
},
});
/**
* Delete a snapshot
*/
export const deleteSnapshotResponse = type({
message: "string",
});
export type DeleteSnapshotDto = typeof deleteSnapshotResponse.infer;
export const deleteSnapshotDto = describeRoute({
description: "Delete a specific snapshot from a repository",
tags: ["Repositories"],
operationId: "deleteSnapshot",
responses: {
200: {
description: "Snapshot deleted successfully",
content: {
"application/json": {
schema: resolver(deleteSnapshotResponse),
},
},
},
},
});

View File

@@ -327,6 +327,18 @@ const doctorRepository = async (name: string) => {
};
};
const deleteSnapshot = async (name: string, snapshotId: string) => {
const repository = await db.query.repositoriesTable.findFirst({
where: eq(repositoriesTable.name, name),
});
if (!repository) {
throw new NotFoundError("Repository not found");
}
await restic.deleteSnapshot(repository.config, snapshotId);
};
export const repositoriesService = {
listRepositories,
createRepository,
@@ -338,4 +350,5 @@ export const repositoriesService = {
getSnapshotDetails,
checkHealth,
doctorRepository,
deleteSnapshot,
};

View File

@@ -441,6 +441,22 @@ const forget = async (config: RepositoryConfig, options: RetentionPolicy, extra:
return { success: true };
};
const deleteSnapshot = async (config: RepositoryConfig, snapshotId: string) => {
const repoUrl = buildRepoUrl(config);
const env = await buildEnv(config);
const args: string[] = ["--repo", repoUrl, "forget", snapshotId, "--prune"];
const res = await $`restic ${args}`.env(env).nothrow();
if (res.exitCode !== 0) {
logger.error(`Restic snapshot deletion failed: ${res.stderr}`);
throw new Error(`Failed to delete snapshot: ${res.stderr}`);
}
return { success: true };
};
const lsNodeSchema = type({
name: "string",
type: "string",
@@ -601,6 +617,7 @@ export const restic = {
restore,
snapshots,
forget,
deleteSnapshot,
unlock,
ls,
check,