fix(rclone): pass explicitly the RCLONE_CONF env var (#779)
This commit is contained in:
parent
c25eacad05
commit
4520335ebc
23 changed files with 123 additions and 19 deletions
|
|
@ -105,7 +105,7 @@ Zerobyte can be customized using environment variables. Below are the available
|
||||||
| `TRUSTED_ORIGINS` | Comma-separated list of extra trusted origins for CORS (e.g., `http://localhost:3000,http://example.com`). | (none) |
|
| `TRUSTED_ORIGINS` | Comma-separated list of extra trusted origins for CORS (e.g., `http://localhost:3000,http://example.com`). | (none) |
|
||||||
| `LOG_LEVEL` | Logging verbosity. Options: `debug`, `info`, `warn`, `error`. | `info` |
|
| `LOG_LEVEL` | Logging verbosity. Options: `debug`, `info`, `warn`, `error`. | `info` |
|
||||||
| `SERVER_IDLE_TIMEOUT` | Idle timeout for the server in seconds. | `60` |
|
| `SERVER_IDLE_TIMEOUT` | Idle timeout for the server in seconds. | `60` |
|
||||||
| `RCLONE_CONFIG_DIR` | Path to the rclone config directory inside the container. Change this if running as a non-root user. | `/root/.config/rclone` |
|
| `RCLONE_CONFIG_DIR` | Path to the directory containing `rclone.conf` inside the container. Change this if running as a non-root user. | `/root/.config/rclone` |
|
||||||
| `PROVISIONING_PATH` | Path to a JSON file with operator-managed repositories and volumes to sync at startup. | (none) |
|
| `PROVISIONING_PATH` | Path to a JSON file with operator-managed repositories and volumes to sync at startup. | (none) |
|
||||||
|
|
||||||
### Provisioned Resources
|
### Provisioned Resources
|
||||||
|
|
@ -258,7 +258,7 @@ Zerobyte can use [rclone](https://rclone.org/) to support 40+ cloud storage prov
|
||||||
+ - ~/.config/rclone:/root/.config/rclone:ro
|
+ - ~/.config/rclone:/root/.config/rclone:ro
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note for non-root users:** If your container runs as a different user (e.g., TrueNAS apps), mount your config to the appropriate location and set `RCLONE_CONF_DIR`:
|
> **Note for non-root users:** If your container runs as a different user (e.g., TrueNAS apps), mount your config to the appropriate location and set `RCLONE_CONFIG_DIR`:
|
||||||
>
|
>
|
||||||
> ```yaml
|
> ```yaml
|
||||||
> environment:
|
> environment:
|
||||||
|
|
|
||||||
|
|
@ -283,9 +283,8 @@ If you're experiencing rclone issues, verify all of the following:
|
||||||
**Diagnosis:**
|
**Diagnosis:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check if config is accessible inside container
|
# Check which config file rclone will use inside the container
|
||||||
docker exec zerobyte ls -la /root/.config/rclone/
|
docker exec zerobyte sh -lc 'echo HOME=$HOME; rclone config file'
|
||||||
docker exec zerobyte cat /root/.config/rclone/rclone.conf
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Solutions:**
|
**Solutions:**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as fs from "node:fs/promises";
|
import * as fs from "node:fs/promises";
|
||||||
import { RCLONE_CONFIG_DIR } from "./constants";
|
import { RCLONE_CONFIG_DIR, RCLONE_CONFIG_FILE } from "./constants";
|
||||||
import { logger } from "@zerobyte/core/node";
|
import { logger } from "@zerobyte/core/node";
|
||||||
|
|
||||||
export type SystemCapabilities = {
|
export type SystemCapabilities = {
|
||||||
|
|
@ -35,17 +35,11 @@ async function detectCapabilities(): Promise<SystemCapabilities> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if rclone is available by:
|
* Checks if rclone is available by:
|
||||||
* 1. Checking if the rclone config directory exists and is accessible
|
* 1. Checking if the rclone config file exists and is accessible
|
||||||
*/
|
*/
|
||||||
async function detectRclone(): Promise<boolean> {
|
async function detectRclone(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await fs.access(RCLONE_CONFIG_DIR);
|
await fs.access(RCLONE_CONFIG_FILE);
|
||||||
|
|
||||||
// Make sure the folder is not empty
|
|
||||||
const files = await fs.readdir(RCLONE_CONFIG_DIR);
|
|
||||||
if (files.length === 0) {
|
|
||||||
throw new Error("rclone config directory is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("rclone capability: enabled");
|
logger.info("rclone capability: enabled");
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
export const OPERATION_TIMEOUT = 5000;
|
export const OPERATION_TIMEOUT = 5000;
|
||||||
|
|
||||||
export const VOLUME_MOUNT_BASE = process.env.ZEROBYTE_VOLUMES_DIR || "/var/lib/zerobyte/volumes";
|
export const VOLUME_MOUNT_BASE = process.env.ZEROBYTE_VOLUMES_DIR || "/var/lib/zerobyte/volumes";
|
||||||
|
|
@ -10,6 +12,7 @@ export const RESTIC_PASS_FILE = process.env.RESTIC_PASS_FILE || "/var/lib/zeroby
|
||||||
export const SSH_KEYS_DIR = "/var/lib/zerobyte/ssh";
|
export const SSH_KEYS_DIR = "/var/lib/zerobyte/ssh";
|
||||||
|
|
||||||
export const RCLONE_CONFIG_DIR = process.env.RCLONE_CONFIG_DIR || "/root/.config/rclone";
|
export const RCLONE_CONFIG_DIR = process.env.RCLONE_CONFIG_DIR || "/root/.config/rclone";
|
||||||
|
export const RCLONE_CONFIG_FILE = path.join(RCLONE_CONFIG_DIR, "rclone.conf");
|
||||||
export const RESTORE_BLOCKED_ROOTS = [REPOSITORY_BASE, RESTIC_CACHE_DIR, SSH_KEYS_DIR, RCLONE_CONFIG_DIR, "/app"];
|
export const RESTORE_BLOCKED_ROOTS = [REPOSITORY_BASE, RESTIC_CACHE_DIR, SSH_KEYS_DIR, RCLONE_CONFIG_DIR, "/app"];
|
||||||
|
|
||||||
export const DEFAULT_EXCLUDES = [RESTIC_PASS_FILE, REPOSITORY_BASE];
|
export const DEFAULT_EXCLUDES = [RESTIC_PASS_FILE, REPOSITORY_BASE];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createRestic } from "@zerobyte/core/restic/server";
|
import { createRestic } from "@zerobyte/core/restic/server";
|
||||||
import type { ResticDeps } from "@zerobyte/core/restic";
|
import type { ResticDeps } from "@zerobyte/core/restic";
|
||||||
import { DEFAULT_EXCLUDES, RESTIC_CACHE_DIR, RESTIC_PASS_FILE } from "./constants";
|
import { DEFAULT_EXCLUDES, RCLONE_CONFIG_FILE, RESTIC_CACHE_DIR, RESTIC_PASS_FILE } from "./constants";
|
||||||
import { config } from "./config";
|
import { config } from "./config";
|
||||||
import { cryptoUtils } from "../utils/crypto";
|
import { cryptoUtils } from "../utils/crypto";
|
||||||
import { db } from "../db/db";
|
import { db } from "../db/db";
|
||||||
|
|
@ -26,6 +26,7 @@ export const resticDeps: ResticDeps = {
|
||||||
resticCacheDir: RESTIC_CACHE_DIR,
|
resticCacheDir: RESTIC_CACHE_DIR,
|
||||||
resticPassFile: RESTIC_PASS_FILE,
|
resticPassFile: RESTIC_PASS_FILE,
|
||||||
defaultExcludes: DEFAULT_EXCLUDES,
|
defaultExcludes: DEFAULT_EXCLUDES,
|
||||||
|
rcloneConfigFile: RCLONE_CONFIG_FILE,
|
||||||
hostname: config.resticHostname,
|
hostname: config.resticHostname,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ test("close emits a synthetic backup.cancelled for a queued backup", () => {
|
||||||
cacheDir: "/tmp/cache",
|
cacheDir: "/tmp/cache",
|
||||||
passFile: "/tmp/pass",
|
passFile: "/tmp/pass",
|
||||||
defaultExcludes: [],
|
defaultExcludes: [],
|
||||||
|
rcloneConfigFile: "/tmp/rclone.conf",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import * as fs from "node:fs/promises";
|
import * as fs from "node:fs/promises";
|
||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import { OPERATION_TIMEOUT } from "../../../core/constants";
|
import { OPERATION_TIMEOUT, RCLONE_CONFIG_FILE } from "../../../core/constants";
|
||||||
import { toMessage } from "../../../utils/errors";
|
import { toMessage } from "../../../utils/errors";
|
||||||
import { logger } from "@zerobyte/core/node";
|
import { logger } from "@zerobyte/core/node";
|
||||||
import { getMountForPath } from "../../../utils/mountinfo";
|
import { getMountForPath } from "../../../utils/mountinfo";
|
||||||
|
|
@ -51,7 +51,12 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||||
logger.debug(`Mounting rclone volume ${path}...`);
|
logger.debug(`Mounting rclone volume ${path}...`);
|
||||||
logger.info(`Executing rclone: rclone ${args.join(" ")}`);
|
logger.info(`Executing rclone: rclone ${args.join(" ")}`);
|
||||||
|
|
||||||
const result = await safeExec({ command: "rclone", args, timeout: zbConfig.serverIdleTimeout * 1000 });
|
const result = await safeExec({
|
||||||
|
command: "rclone",
|
||||||
|
args,
|
||||||
|
env: { RCLONE_CONFIG: RCLONE_CONFIG_FILE },
|
||||||
|
timeout: zbConfig.serverIdleTimeout * 1000,
|
||||||
|
});
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
const errorMsg = result.stderr.toString() || result.stdout.toString() || "Unknown error";
|
const errorMsg = result.stderr.toString() || result.stdout.toString() || "Unknown error";
|
||||||
|
|
|
||||||
58
app/server/utils/__tests__/rclone.test.ts
Normal file
58
app/server/utils/__tests__/rclone.test.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("@zerobyte/core/node", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("@zerobyte/core/node")>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
safeExec: vi.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { safeExec } from "@zerobyte/core/node";
|
||||||
|
import { RCLONE_CONFIG_FILE } from "../../core/constants";
|
||||||
|
import { getRcloneRemoteInfo, listRcloneRemotes } from "../rclone";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rclone utilities", () => {
|
||||||
|
test("lists remotes using the configured rclone config file", async () => {
|
||||||
|
vi.mocked(safeExec).mockResolvedValueOnce({
|
||||||
|
exitCode: 0,
|
||||||
|
stdout: "primary:\narchive:\n",
|
||||||
|
stderr: "",
|
||||||
|
timedOut: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await listRcloneRemotes()).toEqual(["primary", "archive"]);
|
||||||
|
expect(safeExec).toHaveBeenCalledWith({
|
||||||
|
command: "rclone",
|
||||||
|
args: ["listremotes"],
|
||||||
|
env: { RCLONE_CONFIG: RCLONE_CONFIG_FILE },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("loads remote details using the configured rclone config file", async () => {
|
||||||
|
vi.mocked(safeExec).mockResolvedValueOnce({
|
||||||
|
exitCode: 0,
|
||||||
|
stdout: "[primary]\ntype = s3\nprovider = AWS\n",
|
||||||
|
stderr: "",
|
||||||
|
timedOut: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await getRcloneRemoteInfo("primary")).toEqual({
|
||||||
|
type: "s3",
|
||||||
|
config: {
|
||||||
|
type: "s3",
|
||||||
|
provider: "AWS",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(safeExec).toHaveBeenCalledWith({
|
||||||
|
command: "rclone",
|
||||||
|
args: ["config", "show", "primary"],
|
||||||
|
env: { RCLONE_CONFIG: RCLONE_CONFIG_FILE },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
import { logger } from "@zerobyte/core/node";
|
import { logger } from "@zerobyte/core/node";
|
||||||
import { toMessage } from "./errors";
|
import { toMessage } from "./errors";
|
||||||
import { safeExec } from "@zerobyte/core/node";
|
import { safeExec } from "@zerobyte/core/node";
|
||||||
|
import { RCLONE_CONFIG_FILE } from "../core/constants";
|
||||||
|
|
||||||
|
const rcloneEnv = {
|
||||||
|
RCLONE_CONFIG: RCLONE_CONFIG_FILE,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all configured rclone remotes
|
* List all configured rclone remotes
|
||||||
* @returns Array of remote names
|
* @returns Array of remote names
|
||||||
*/
|
*/
|
||||||
export async function listRcloneRemotes(): Promise<string[]> {
|
export async function listRcloneRemotes(): Promise<string[]> {
|
||||||
const result = await safeExec({ command: "rclone", args: ["listremotes"] });
|
const result = await safeExec({ command: "rclone", args: ["listremotes"], env: rcloneEnv });
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
logger.error(`Failed to list rclone remotes: ${result.stderr.toString()}`);
|
logger.error(`Failed to list rclone remotes: ${result.stderr.toString()}`);
|
||||||
|
|
@ -34,7 +39,7 @@ export async function getRcloneRemoteInfo(
|
||||||
remote: string,
|
remote: string,
|
||||||
): Promise<{ type: string; config: Record<string, string> } | null> {
|
): Promise<{ type: string; config: Record<string, string> } | null> {
|
||||||
try {
|
try {
|
||||||
const result = await safeExec({ command: "rclone", args: ["config", "show", remote] });
|
const result = await safeExec({ command: "rclone", args: ["config", "show", remote], env: rcloneEnv });
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
logger.error(`Failed to get info for remote ${remote}: ${result.stderr.toString()}`);
|
logger.error(`Failed to get info for remote ${remote}: ${result.stderr.toString()}`);
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ test("emits backup.failed when a backup command hits a restic error", async () =
|
||||||
cacheDir: "/tmp/restic-cache",
|
cacheDir: "/tmp/restic-cache",
|
||||||
passFile: "/tmp/restic-pass",
|
passFile: "/tmp/restic-pass",
|
||||||
defaultExcludes: [],
|
defaultExcludes: [],
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export const handleBackupRunCommand = (context: ControllerCommandContext, payloa
|
||||||
resticPassFile: payload.runtime.passFile,
|
resticPassFile: payload.runtime.passFile,
|
||||||
defaultExcludes: payload.runtime.defaultExcludes,
|
defaultExcludes: payload.runtime.defaultExcludes,
|
||||||
hostname: payload.runtime.hostname,
|
hostname: payload.runtime.hostname,
|
||||||
|
rcloneConfigFile: payload.runtime.rcloneConfigFile,
|
||||||
};
|
};
|
||||||
|
|
||||||
const restic = createRestic(deps);
|
const restic = createRestic(deps);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ const backupRuntimeSchema = z.object({
|
||||||
passFile: z.string(),
|
passFile: z.string(),
|
||||||
defaultExcludes: z.array(z.string()),
|
defaultExcludes: z.array(z.string()),
|
||||||
hostname: z.string().optional(),
|
hostname: z.string().optional(),
|
||||||
|
rcloneConfigFile: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const backupRunSchema = z.object({
|
const backupRunSchema = z.object({
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const mockDeps: ResticDeps = {
|
||||||
resticPassFile: "/tmp/restic.pass",
|
resticPassFile: "/tmp/restic.pass",
|
||||||
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
||||||
hostname: "zerobyte",
|
hostname: "zerobyte",
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
};
|
};
|
||||||
|
|
||||||
const VALID_SUMMARY = JSON.stringify({
|
const VALID_SUMMARY = JSON.stringify({
|
||||||
|
|
@ -61,10 +62,12 @@ type SetupOptions = {
|
||||||
*/
|
*/
|
||||||
const setup = ({ spawnResult = {}, onSpawnCall }: SetupOptions = {}) => {
|
const setup = ({ spawnResult = {}, onSpawnCall }: SetupOptions = {}) => {
|
||||||
let capturedArgs: string[] = [];
|
let capturedArgs: string[] = [];
|
||||||
|
let capturedEnv: SafeSpawnParams["env"];
|
||||||
|
|
||||||
vi.spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve());
|
vi.spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve());
|
||||||
vi.spyOn(spawnModule, "safeSpawn").mockImplementation((params: SafeSpawnParams) => {
|
vi.spyOn(spawnModule, "safeSpawn").mockImplementation((params: SafeSpawnParams) => {
|
||||||
capturedArgs = params.args;
|
capturedArgs = params.args;
|
||||||
|
capturedEnv = params.env;
|
||||||
return Promise.resolve(onSpawnCall?.(params)).then(() => ({
|
return Promise.resolve(onSpawnCall?.(params)).then(() => ({
|
||||||
exitCode: 0,
|
exitCode: 0,
|
||||||
summary: VALID_SUMMARY,
|
summary: VALID_SUMMARY,
|
||||||
|
|
@ -75,6 +78,7 @@ const setup = ({ spawnResult = {}, onSpawnCall }: SetupOptions = {}) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getArgs: () => capturedArgs,
|
getArgs: () => capturedArgs,
|
||||||
|
getEnv: () => capturedEnv,
|
||||||
hasFlag: (flag: string) => capturedArgs.includes(flag),
|
hasFlag: (flag: string) => capturedArgs.includes(flag),
|
||||||
getOptionValues: (option: string): string[] => {
|
getOptionValues: (option: string): string[] => {
|
||||||
const values: string[] = [];
|
const values: string[] = [];
|
||||||
|
|
@ -160,6 +164,25 @@ describe("backup command", () => {
|
||||||
|
|
||||||
expect(getOptionValues("--exclude").length).toBeGreaterThan(0);
|
expect(getOptionValues("--exclude").length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("passes RCLONE_CONFIG when backing up to an rclone repository", async () => {
|
||||||
|
const { getEnv } = setup();
|
||||||
|
|
||||||
|
await runBackup(
|
||||||
|
{
|
||||||
|
backend: "rclone",
|
||||||
|
remote: "remote",
|
||||||
|
path: "/repo",
|
||||||
|
isExistingRepository: true,
|
||||||
|
customPassword: "custom-password",
|
||||||
|
},
|
||||||
|
"/mnt/data",
|
||||||
|
{ organizationId: "org-1" },
|
||||||
|
mockDeps,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getEnv()?.RCLONE_CONFIG).toBe(mockDeps.rcloneConfigFile);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("exit code handling", () => {
|
describe("exit code handling", () => {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const mockDeps: ResticDeps = {
|
||||||
resticCacheDir: "/tmp/restic-cache",
|
resticCacheDir: "/tmp/restic-cache",
|
||||||
resticPassFile: "/tmp/restic.pass",
|
resticPassFile: "/tmp/restic.pass",
|
||||||
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
};
|
};
|
||||||
|
|
||||||
const sourceConfig = {
|
const sourceConfig = {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const mockDeps: ResticDeps = {
|
||||||
resticCacheDir: "/tmp/restic-cache",
|
resticCacheDir: "/tmp/restic-cache",
|
||||||
resticPassFile: "/tmp/restic.pass",
|
resticPassFile: "/tmp/restic.pass",
|
||||||
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const mockDeps: ResticDeps = {
|
||||||
resticCacheDir: "/tmp/restic-cache",
|
resticCacheDir: "/tmp/restic-cache",
|
||||||
resticPassFile: "/tmp/restic.pass",
|
resticPassFile: "/tmp/restic.pass",
|
||||||
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const mockDeps: ResticDeps = {
|
||||||
resticCacheDir: "/tmp/restic-cache",
|
resticCacheDir: "/tmp/restic-cache",
|
||||||
resticPassFile: "/tmp/restic.pass",
|
resticPassFile: "/tmp/restic.pass",
|
||||||
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const mockDeps: ResticDeps = {
|
||||||
resticCacheDir: "/tmp/restic-cache",
|
resticCacheDir: "/tmp/restic-cache",
|
||||||
resticPassFile: "/tmp/restic.pass",
|
resticPassFile: "/tmp/restic.pass",
|
||||||
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
};
|
};
|
||||||
|
|
||||||
const successfulRestoreSummary = JSON.stringify({
|
const successfulRestoreSummary = JSON.stringify({
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const mockDeps: ResticDeps = {
|
||||||
resticCacheDir: "/tmp/restic-cache",
|
resticCacheDir: "/tmp/restic-cache",
|
||||||
resticPassFile: "/tmp/restic.pass",
|
resticPassFile: "/tmp/restic.pass",
|
||||||
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const mockDeps: ResticDeps = {
|
||||||
resticCacheDir: "/tmp/restic-cache",
|
resticCacheDir: "/tmp/restic-cache",
|
||||||
resticPassFile: "/tmp/restic.pass",
|
resticPassFile: "/tmp/restic.pass",
|
||||||
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
defaultExcludes: ["/tmp/restic.pass", "/var/lib/zerobyte/repositories"],
|
||||||
|
rcloneConfigFile: "/root/.config/rclone/rclone.conf",
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const makeDeps = (overrides: Partial<ResticDeps> = {}): ResticDeps => ({
|
||||||
resticCacheDir: RESTIC_CACHE_DIR,
|
resticCacheDir: RESTIC_CACHE_DIR,
|
||||||
resticPassFile: RESTIC_PASS_FILE,
|
resticPassFile: RESTIC_PASS_FILE,
|
||||||
defaultExcludes: [RESTIC_PASS_FILE, "/var/lib/zerobyte/repositories"],
|
defaultExcludes: [RESTIC_PASS_FILE, "/var/lib/zerobyte/repositories"],
|
||||||
|
rcloneConfigFile: "/tmp/rclone.conf",
|
||||||
...overrides,
|
...overrides,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,9 @@ export const buildEnv = async (
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "rclone":
|
||||||
|
env.RCLONE_CONFIG = deps.rcloneConfigFile;
|
||||||
|
break;
|
||||||
case "sftp": {
|
case "sftp": {
|
||||||
const decryptedKey = await deps.resolveSecret(config.privateKey);
|
const decryptedKey = await deps.resolveSecret(config.privateKey);
|
||||||
const keyPath = path.join("/tmp", `zerobyte-ssh-${crypto.randomBytes(8).toString("hex")}`);
|
const keyPath = path.join("/tmp", `zerobyte-ssh-${crypto.randomBytes(8).toString("hex")}`);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export interface ResticDeps {
|
||||||
resticCacheDir: string;
|
resticCacheDir: string;
|
||||||
resticPassFile: string;
|
resticPassFile: string;
|
||||||
defaultExcludes: string[];
|
defaultExcludes: string[];
|
||||||
|
rcloneConfigFile: string;
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue