mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: restore to custom location (#78)
* feat: restore to custom location * refactor: define overwrite mode in shared schema
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import { type } from "arktype";
|
||||
import { describeRoute, resolver } from "hono-openapi";
|
||||
import { COMPRESSION_MODES, REPOSITORY_BACKENDS, REPOSITORY_STATUS, repositoryConfigSchema } from "~/schemas/restic";
|
||||
import {
|
||||
COMPRESSION_MODES,
|
||||
OVERWRITE_MODES,
|
||||
REPOSITORY_BACKENDS,
|
||||
REPOSITORY_STATUS,
|
||||
repositoryConfigSchema,
|
||||
} from "~/schemas/restic";
|
||||
|
||||
export const repositorySchema = type({
|
||||
id: "string",
|
||||
@@ -269,12 +275,16 @@ export const listSnapshotFilesDto = describeRoute({
|
||||
/**
|
||||
* Restore a snapshot
|
||||
*/
|
||||
export const overwriteModeSchema = type.valueOf(OVERWRITE_MODES);
|
||||
|
||||
export const restoreSnapshotBody = type({
|
||||
snapshotId: "string",
|
||||
include: "string[]?",
|
||||
exclude: "string[]?",
|
||||
excludeXattr: "string[]?",
|
||||
delete: "boolean?",
|
||||
targetPath: "string?",
|
||||
overwrite: overwriteModeSchema.optional(),
|
||||
});
|
||||
|
||||
export type RestoreSnapshotBody = typeof restoreSnapshotBody.infer;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { toMessage } from "../../utils/errors";
|
||||
import { generateShortId } from "../../utils/id";
|
||||
import { restic } from "../../utils/restic";
|
||||
import { cryptoUtils } from "../../utils/crypto";
|
||||
import type { CompressionMode, RepositoryConfig } from "~/schemas/restic";
|
||||
import type { CompressionMode, OverwriteMode, RepositoryConfig } from "~/schemas/restic";
|
||||
|
||||
const listRepositories = async () => {
|
||||
const repositories = await db.query.repositoriesTable.findMany({});
|
||||
@@ -201,7 +201,14 @@ const listSnapshotFiles = async (name: string, snapshotId: string, path?: string
|
||||
const restoreSnapshot = async (
|
||||
name: string,
|
||||
snapshotId: string,
|
||||
options?: { include?: string[]; exclude?: string[]; excludeXattr?: string[]; delete?: boolean },
|
||||
options?: {
|
||||
include?: string[];
|
||||
exclude?: string[];
|
||||
excludeXattr?: string[];
|
||||
delete?: boolean;
|
||||
targetPath?: string;
|
||||
overwrite?: OverwriteMode;
|
||||
},
|
||||
) => {
|
||||
const repository = await db.query.repositoriesTable.findFirst({
|
||||
where: eq(repositoriesTable.name, name),
|
||||
@@ -211,7 +218,9 @@ const restoreSnapshot = async (
|
||||
throw new NotFoundError("Repository not found");
|
||||
}
|
||||
|
||||
const result = await restic.restore(repository.config, snapshotId, "/", options);
|
||||
const target = options?.targetPath || "/";
|
||||
|
||||
const result = await restic.restore(repository.config, snapshotId, target, options);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { logger } from "./logger";
|
||||
import { cryptoUtils } from "./crypto";
|
||||
import type { RetentionPolicy } from "../modules/backups/backups.dto";
|
||||
import { safeSpawn } from "./spawn";
|
||||
import type { CompressionMode, RepositoryConfig } from "~/schemas/restic";
|
||||
import type { CompressionMode, RepositoryConfig, OverwriteMode } from "~/schemas/restic";
|
||||
import { ResticError } from "./errors";
|
||||
|
||||
const backupOutputSchema = type({
|
||||
@@ -353,7 +353,7 @@ const backup = async (
|
||||
|
||||
const restoreOutputSchema = type({
|
||||
message_type: "'summary'",
|
||||
total_files: "number",
|
||||
total_files: "number?",
|
||||
files_restored: "number",
|
||||
files_skipped: "number",
|
||||
total_bytes: "number?",
|
||||
@@ -369,8 +369,8 @@ const restore = async (
|
||||
include?: string[];
|
||||
exclude?: string[];
|
||||
excludeXattr?: string[];
|
||||
path?: string;
|
||||
delete?: boolean;
|
||||
overwrite?: OverwriteMode;
|
||||
},
|
||||
) => {
|
||||
const repoUrl = buildRepoUrl(config);
|
||||
@@ -378,8 +378,8 @@ const restore = async (
|
||||
|
||||
const args: string[] = ["--repo", repoUrl, "restore", snapshotId, "--target", target];
|
||||
|
||||
if (options?.path) {
|
||||
args[args.length - 4] = `${snapshotId}:${options.path}`;
|
||||
if (options?.overwrite) {
|
||||
args.push("--overwrite", options.overwrite);
|
||||
}
|
||||
|
||||
if (options?.delete) {
|
||||
@@ -407,6 +407,7 @@ const restore = async (
|
||||
addRepoSpecificArgs(args, config, env);
|
||||
args.push("--json");
|
||||
|
||||
logger.debug(`Executing: restic ${args.join(" ")}`);
|
||||
const res = await $`restic ${args}`.env(env).nothrow();
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
* This removes passwords and credentials from logs and error messages
|
||||
*/
|
||||
export const sanitizeSensitiveData = (text: string): string => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return text;
|
||||
}
|
||||
|
||||
let sanitized = text.replace(/\b(pass|password)=([^\s,]+)/gi, "$1=***");
|
||||
|
||||
sanitized = sanitized.replace(/\/\/([^:@\s]+):([^@\s]+)@/g, "//$1:***@");
|
||||
|
||||
Reference in New Issue
Block a user