fix(backups): default ignore-inode for fuse volumes
This commit is contained in:
parent
35dd49d7ac
commit
d19d827496
3 changed files with 96 additions and 1 deletions
|
|
@ -311,6 +311,94 @@ describe("backup execution - validation failures", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("adds ignore-inode by default for FUSE-backed volumes", async () => {
|
||||||
|
const { runBackupMock } = setup();
|
||||||
|
const volume = await createTestVolume({
|
||||||
|
type: "sftp",
|
||||||
|
config: {
|
||||||
|
backend: "sftp",
|
||||||
|
host: "storage.example.com",
|
||||||
|
port: 22,
|
||||||
|
username: "backup",
|
||||||
|
privateKey: "key",
|
||||||
|
path: "/data",
|
||||||
|
skipHostKeyCheck: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const repository = await createTestRepository();
|
||||||
|
const schedule = await createTestBackupSchedule({
|
||||||
|
volumeId: volume.id,
|
||||||
|
repositoryId: repository.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await backupsService.executeBackup(schedule.id);
|
||||||
|
|
||||||
|
expect(runBackupMock).toHaveBeenCalledWith(
|
||||||
|
"local",
|
||||||
|
expect.objectContaining({
|
||||||
|
payload: expect.objectContaining({
|
||||||
|
options: expect.objectContaining({
|
||||||
|
customResticParams: ["--ignore-inode"],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not add ignore-inode by default for directory volumes", async () => {
|
||||||
|
const { runBackupMock } = setup();
|
||||||
|
const volume = await createTestVolume();
|
||||||
|
const repository = await createTestRepository();
|
||||||
|
const schedule = await createTestBackupSchedule({
|
||||||
|
volumeId: volume.id,
|
||||||
|
repositoryId: repository.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await backupsService.executeBackup(schedule.id);
|
||||||
|
|
||||||
|
expect(runBackupMock).toHaveBeenCalledWith(
|
||||||
|
"local",
|
||||||
|
expect.objectContaining({
|
||||||
|
payload: expect.objectContaining({
|
||||||
|
options: expect.objectContaining({
|
||||||
|
customResticParams: [],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not duplicate ignore-inode when already configured", async () => {
|
||||||
|
const { runBackupMock } = setup();
|
||||||
|
const volume = await createTestVolume({
|
||||||
|
type: "rclone",
|
||||||
|
config: {
|
||||||
|
backend: "rclone",
|
||||||
|
remote: "remote",
|
||||||
|
path: "/data",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const repository = await createTestRepository();
|
||||||
|
const schedule = await createTestBackupSchedule({
|
||||||
|
volumeId: volume.id,
|
||||||
|
repositoryId: repository.id,
|
||||||
|
customResticParams: ["--ignore-inode"],
|
||||||
|
});
|
||||||
|
|
||||||
|
await backupsService.executeBackup(schedule.id);
|
||||||
|
|
||||||
|
expect(runBackupMock).toHaveBeenCalledWith(
|
||||||
|
"local",
|
||||||
|
expect.objectContaining({
|
||||||
|
payload: expect.objectContaining({
|
||||||
|
options: expect.objectContaining({
|
||||||
|
customResticParams: ["--ignore-inode"],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("should fail backup when the local agent is unavailable", async () => {
|
test("should fail backup when the local agent is unavailable", async () => {
|
||||||
const { runBackupMock } = setup();
|
const { runBackupMock } = setup();
|
||||||
const volume = await createTestVolume();
|
const volume = await createTestVolume();
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import { createBackupOptions } from "./backup.helpers";
|
||||||
import { toErrorDetails } from "../../utils/errors";
|
import { toErrorDetails } from "../../utils/errors";
|
||||||
|
|
||||||
const LOCAL_AGENT_ID = "local";
|
const LOCAL_AGENT_ID = "local";
|
||||||
|
const FUSE_VOLUME_BACKENDS = new Set<Volume["type"]>(["rclone", "sftp", "webdav"]);
|
||||||
|
const IGNORE_INODE_FLAG = "--ignore-inode";
|
||||||
|
|
||||||
type BackupExecutionRequest = {
|
type BackupExecutionRequest = {
|
||||||
scheduleId: number;
|
scheduleId: number;
|
||||||
|
|
@ -33,6 +35,11 @@ const createBackupRunPayload = async ({
|
||||||
}: BackupExecutionRequest & { jobId: string }): Promise<BackupRunPayload> => {
|
}: BackupExecutionRequest & { jobId: string }): Promise<BackupRunPayload> => {
|
||||||
const sourcePath = getVolumePath(volume);
|
const sourcePath = getVolumePath(volume);
|
||||||
const { signal: _, ...options } = createBackupOptions(schedule, sourcePath);
|
const { signal: _, ...options } = createBackupOptions(schedule, sourcePath);
|
||||||
|
|
||||||
|
if (FUSE_VOLUME_BACKENDS.has(volume.type) && !options.customResticParams.includes(IGNORE_INODE_FLAG)) {
|
||||||
|
options.customResticParams = [...options.customResticParams, IGNORE_INODE_FLAG];
|
||||||
|
}
|
||||||
|
|
||||||
const repositoryConfig = await decryptRepositoryConfig(repository.config);
|
const repositoryConfig = await decryptRepositoryConfig(repository.config);
|
||||||
const encryptedResticPassword = await resticDeps.getOrganizationResticPassword(organizationId);
|
const encryptedResticPassword = await resticDeps.getOrganizationResticPassword(organizationId);
|
||||||
const resticPassword = await resticDeps.resolveSecret(encryptedResticPassword);
|
const resticPassword = await resticDeps.resolveSecret(encryptedResticPassword);
|
||||||
|
|
|
||||||
|
|
@ -68,5 +68,5 @@ export const createBackupOptions = (schedule: BackupSchedule, volumePath: string
|
||||||
includePatterns: schedule.includePatterns
|
includePatterns: schedule.includePatterns
|
||||||
? schedule.includePatterns.map((p) => processPattern(p, volumePath, true))
|
? schedule.includePatterns.map((p) => processPattern(p, volumePath, true))
|
||||||
: undefined,
|
: undefined,
|
||||||
customResticParams: schedule.customResticParams ?? undefined,
|
customResticParams: schedule.customResticParams ?? [],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue