mirror of
https://github.com/nicotsx/zerobyte.git
synced 2026-05-19 14:08:24 -04:00
feat: insecure tls & cacert for self-hosted repos (#277)
* feat: insecure tls & cacert for self-hosted repos * fix: extra arg
This commit is contained in:
98
app/client/components/ui/collapsible.tsx
Normal file
98
app/client/components/ui/collapsible.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import * as React from "react";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
|
||||
interface CollapsibleProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
defaultOpen?: boolean;
|
||||
}
|
||||
|
||||
const CollapsibleContext = React.createContext<{
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}>({
|
||||
open: false,
|
||||
setOpen: () => {},
|
||||
});
|
||||
|
||||
const Collapsible = React.forwardRef<HTMLDivElement, CollapsibleProps>(
|
||||
({ className, open: controlledOpen, onOpenChange, defaultOpen = false, children, ...props }, ref) => {
|
||||
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
||||
|
||||
const isControlled = controlledOpen !== undefined;
|
||||
const open = isControlled ? controlledOpen : uncontrolledOpen;
|
||||
|
||||
const setOpen = React.useCallback(
|
||||
(value: React.SetStateAction<boolean>) => {
|
||||
const newValue = typeof value === "function" ? value(open) : value;
|
||||
if (!isControlled) {
|
||||
setUncontrolledOpen(newValue);
|
||||
}
|
||||
onOpenChange?.(newValue);
|
||||
},
|
||||
[isControlled, open, onOpenChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<CollapsibleContext.Provider value={{ open, setOpen }}>
|
||||
<div ref={ref} className={cn(className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
</CollapsibleContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
Collapsible.displayName = "Collapsible";
|
||||
|
||||
interface CollapsibleTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||
|
||||
const CollapsibleTrigger = React.forwardRef<HTMLButtonElement, CollapsibleTriggerProps>(
|
||||
({ className, children, ...props }, ref) => {
|
||||
const { open, setOpen } = React.useContext(CollapsibleContext);
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between py-2 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className,
|
||||
)}
|
||||
data-state={open ? "open" : "closed"}
|
||||
onClick={() => setOpen(!open)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
CollapsibleTrigger.displayName = "CollapsibleTrigger";
|
||||
|
||||
interface CollapsibleContentProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
const CollapsibleContent = React.forwardRef<HTMLDivElement, CollapsibleContentProps>(
|
||||
({ className, children, ...props }, ref) => {
|
||||
const { open } = React.useContext(CollapsibleContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"overflow-hidden transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
|
||||
className,
|
||||
)}
|
||||
data-state={open ? "open" : "closed"}
|
||||
hidden={!open}
|
||||
{...props}
|
||||
>
|
||||
{open && children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
CollapsibleContent.displayName = "CollapsibleContent";
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
RcloneRepositoryForm,
|
||||
RestRepositoryForm,
|
||||
SftpRepositoryForm,
|
||||
AdvancedForm,
|
||||
} from "./repository-forms";
|
||||
|
||||
export const formSchema = type({
|
||||
@@ -268,6 +269,8 @@ export const CreateRepositoryForm = ({
|
||||
{watchedBackend === "rest" && <RestRepositoryForm form={form} />}
|
||||
{watchedBackend === "sftp" && <SftpRepositoryForm form={form} />}
|
||||
|
||||
<AdvancedForm form={form} />
|
||||
|
||||
{mode === "update" && (
|
||||
<Button type="submit" className="w-full" loading={loading}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "../../../../components/ui/form";
|
||||
import { Textarea } from "../../../../components/ui/textarea";
|
||||
import { Checkbox } from "../../../../components/ui/checkbox";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../../components/ui/tooltip";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../../components/ui/collapsible";
|
||||
import type { RepositoryFormValues } from "../create-repository-form";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturn<RepositoryFormValues>;
|
||||
};
|
||||
|
||||
export const AdvancedForm = ({ form }: Props) => {
|
||||
const insecureTls = form.watch("insecureTls");
|
||||
const cacert = form.watch("cacert");
|
||||
|
||||
return (
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger className="w-full text-muted-foreground hover:no-underline">
|
||||
Advanced Settings
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="pb-4 space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="insecureTls"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={field.value ?? false}
|
||||
disabled={!!cacert}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className={cn({ hidden: !cacert })}>
|
||||
<p className="max-w-xs">
|
||||
This option is disabled because a CA certificate is provided. Remove the CA certificate to skip
|
||||
TLS validation instead.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>Skip TLS certificate verification</FormLabel>
|
||||
<FormDescription>
|
||||
Disable TLS certificate verification for HTTPS connections with self-signed certificates. This is
|
||||
insecure and should only be used for testing.
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cacert"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>CA Certificate (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Textarea
|
||||
placeholder={"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"}
|
||||
rows={6}
|
||||
disabled={insecureTls}
|
||||
{...field}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className={cn({ hidden: !insecureTls })}>
|
||||
<p className="max-w-xs">
|
||||
CA certificate is disabled because TLS validation is being skipped. Uncheck "Skip TLS Certificate
|
||||
Verification" to provide a custom CA certificate.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Custom CA certificate for self-signed certificates (PEM format). This applies to HTTPS
|
||||
connections.
|
||||
<a
|
||||
href="https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#rest-server"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
@@ -6,3 +6,4 @@ export { AzureRepositoryForm } from "./azure-repository-form";
|
||||
export { RcloneRepositoryForm } from "./rclone-repository-form";
|
||||
export { RestRepositoryForm } from "./rest-repository-form";
|
||||
export { SftpRepositoryForm } from "./sftp-repository-form";
|
||||
export { AdvancedForm } from "./advanced-tls-form";
|
||||
|
||||
@@ -9,20 +9,13 @@ import {
|
||||
} from "../../../../components/ui/form";
|
||||
import { Input } from "../../../../components/ui/input";
|
||||
import { SecretInput } from "../../../../components/ui/secret-input";
|
||||
import { Textarea } from "../../../../components/ui/textarea";
|
||||
import { Checkbox } from "../../../../components/ui/checkbox";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../../components/ui/tooltip";
|
||||
import type { RepositoryFormValues } from "../create-repository-form";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturn<RepositoryFormValues>;
|
||||
};
|
||||
|
||||
export const RestRepositoryForm = ({ form }: Props) => {
|
||||
const insecureTls = form.watch("insecureTls");
|
||||
const cacert = form.watch("cacert");
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
@@ -39,83 +32,6 @@ export const RestRepositoryForm = ({ form }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="insecureTls"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={field.value ?? false}
|
||||
disabled={!!cacert}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className={cn({ hidden: !cacert })}>
|
||||
<p className="max-w-xs">
|
||||
This option is disabled because a CA certificate is provided. Remove the CA certificate to skip TLS
|
||||
validation instead.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>Skip TLS certificate verification</FormLabel>
|
||||
<FormDescription>
|
||||
Disable TLS certificate verification if rest server is https and uses a self-signed certificate. This is
|
||||
insecure and should only be used for testing.
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cacert"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>CA Certificate (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Textarea
|
||||
placeholder="-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----"
|
||||
rows={6}
|
||||
disabled={insecureTls}
|
||||
{...field}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className={cn({ hidden: !insecureTls })}>
|
||||
<p className="max-w-xs">
|
||||
CA certificate is disabled because TLS validation is being skipped. Uncheck "Skip TLS Certificate
|
||||
Verification" to provide a custom CA certificate.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Custom CA certificate for self-signed certificates (PEM format).
|
||||
<a
|
||||
href="https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#rest-server"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="path"
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "~/client/components/ui/alert-dialog";
|
||||
import type { Repository } from "~/client/lib/types";
|
||||
import { updateRepositoryMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
import type { CompressionMode } from "~/schemas/restic";
|
||||
import type { CompressionMode, RepositoryConfig } from "~/schemas/restic";
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
@@ -59,6 +59,8 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
|
||||
const hasChanges =
|
||||
name !== repository.name || compressionMode !== ((repository.compressionMode as CompressionMode) || "off");
|
||||
|
||||
const config = repository.config as RepositoryConfig;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="p-6">
|
||||
@@ -116,7 +118,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
|
||||
{repository.lastChecked ? new Date(repository.lastChecked).toLocaleString() : "Never"}
|
||||
</p>
|
||||
</div>
|
||||
{repository.config.backend === "rest" && repository.config.cacert && (
|
||||
{config.cacert && (
|
||||
<div>
|
||||
<div className="text-sm font-medium text-muted-foreground">CA Certificate</div>
|
||||
<p className="mt-1 text-sm">
|
||||
@@ -124,11 +126,11 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{repository.config.backend === "rest" && "insecureTls" in repository.config && (
|
||||
{"insecureTls" in config && (
|
||||
<div>
|
||||
<div className="text-sm font-medium text-muted-foreground">TLS Certificate Validation</div>
|
||||
<p className="mt-1 text-sm">
|
||||
{repository.config.insecureTls ? (
|
||||
{config.insecureTls ? (
|
||||
<span className="text-red-500">disabled</span>
|
||||
) : (
|
||||
<span className="text-green-500">enabled</span>
|
||||
|
||||
@@ -17,6 +17,8 @@ export type RepositoryBackend = keyof typeof REPOSITORY_BACKENDS;
|
||||
const baseRepositoryConfigSchema = type({
|
||||
isExistingRepository: "boolean?",
|
||||
customPassword: "string?",
|
||||
cacert: "string?",
|
||||
insecureTls: "boolean?",
|
||||
});
|
||||
|
||||
export const s3RepositoryConfigSchema = type({
|
||||
@@ -68,8 +70,6 @@ export const restRepositoryConfigSchema = type({
|
||||
username: "string?",
|
||||
password: "string?",
|
||||
path: "string?",
|
||||
cacert: "string?",
|
||||
insecureTls: "boolean?",
|
||||
}).and(baseRepositoryConfigSchema);
|
||||
|
||||
export const sftpRepositoryConfigSchema = type({
|
||||
|
||||
@@ -77,7 +77,7 @@ const migrateSnapshotsToShortIdTag = async (): Promise<MigrationResult> => {
|
||||
|
||||
logger.info(`Migrating snapshots for schedule '${schedule.name}' from tag '${oldTag}' to '${newTag}'`);
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(repository.config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic tag failed: ${res.stderr}`);
|
||||
|
||||
@@ -34,6 +34,10 @@ const encryptConfig = async (config: RepositoryConfig): Promise<RepositoryConfig
|
||||
encryptedConfig.customPassword = await cryptoUtils.sealSecret(config.customPassword);
|
||||
}
|
||||
|
||||
if (config.cacert) {
|
||||
encryptedConfig.cacert = await cryptoUtils.sealSecret(config.cacert);
|
||||
}
|
||||
|
||||
switch (config.backend) {
|
||||
case "s3":
|
||||
case "r2":
|
||||
@@ -53,9 +57,6 @@ const encryptConfig = async (config: RepositoryConfig): Promise<RepositoryConfig
|
||||
if (config.password) {
|
||||
encryptedConfig.password = await cryptoUtils.sealSecret(config.password);
|
||||
}
|
||||
if (config.cacert) {
|
||||
encryptedConfig.cacert = await cryptoUtils.sealSecret(config.cacert);
|
||||
}
|
||||
break;
|
||||
case "sftp":
|
||||
encryptedConfig.privateKey = await cryptoUtils.sealSecret(config.privateKey);
|
||||
|
||||
@@ -155,15 +155,6 @@ export const buildEnv = async (config: RepositoryConfig) => {
|
||||
if (config.password) {
|
||||
env.RESTIC_REST_PASSWORD = await cryptoUtils.resolveSecret(config.password);
|
||||
}
|
||||
if (config.cacert) {
|
||||
const decryptedCert = await cryptoUtils.resolveSecret(config.cacert);
|
||||
const certPath = path.join("/tmp", `zerobyte-cacert-${crypto.randomBytes(8).toString("hex")}.pem`);
|
||||
await fs.writeFile(certPath, decryptedCert, { mode: 0o600 });
|
||||
env.RESTIC_CACERT = certPath;
|
||||
}
|
||||
if (config.insecureTls) {
|
||||
env._REST_INSECURE_TLS = "true";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "sftp": {
|
||||
@@ -214,6 +205,17 @@ export const buildEnv = async (config: RepositoryConfig) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.cacert) {
|
||||
const decryptedCert = await cryptoUtils.resolveSecret(config.cacert);
|
||||
const certPath = path.join("/tmp", `zerobyte-cacert-${crypto.randomBytes(8).toString("hex")}.pem`);
|
||||
await fs.writeFile(certPath, decryptedCert, { mode: 0o600 });
|
||||
env.RESTIC_CACERT = certPath;
|
||||
}
|
||||
|
||||
if (config.insecureTls) {
|
||||
env._INSECURE_TLS = "true";
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
@@ -230,7 +232,7 @@ const init = async (config: RepositoryConfig) => {
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic init failed: ${res.stderr}`);
|
||||
@@ -358,7 +360,7 @@ const backup = async (
|
||||
finally: async () => {
|
||||
includeFile && (await fs.unlink(includeFile).catch(() => {}));
|
||||
excludeFile && (await fs.unlink(excludeFile).catch(() => {}));
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -456,7 +458,7 @@ const restore = async (
|
||||
logger.debug(`Executing: restic ${args.join(" ")}`);
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic restore failed: ${res.stderr}`);
|
||||
@@ -517,7 +519,7 @@ const snapshots = async (config: RepositoryConfig, options: { tags?: string[] }
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic snapshots retrieval failed: ${res.stderr}`);
|
||||
@@ -566,7 +568,7 @@ const forget = async (config: RepositoryConfig, options: RetentionPolicy, extra:
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic forget failed: ${res.stderr}`);
|
||||
@@ -588,7 +590,7 @@ const deleteSnapshots = async (config: RepositoryConfig, snapshotIds: string[])
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic snapshot deletion failed: ${res.stderr}`);
|
||||
@@ -637,7 +639,7 @@ const tagSnapshots = async (
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic snapshot tagging failed: ${res.stderr}`);
|
||||
@@ -687,7 +689,7 @@ const ls = async (config: RepositoryConfig, snapshotId: string, path?: string) =
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic ls failed: ${res.stderr}`);
|
||||
@@ -738,7 +740,7 @@ const unlock = async (config: RepositoryConfig) => {
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
logger.error(`Restic unlock failed: ${res.stderr}`);
|
||||
@@ -762,7 +764,7 @@ const check = async (config: RepositoryConfig, options?: { readData?: boolean })
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
const { stdout, stderr } = res;
|
||||
|
||||
@@ -795,7 +797,7 @@ const repairIndex = async (config: RepositoryConfig) => {
|
||||
addCommonArgs(args, env);
|
||||
|
||||
const res = await safeSpawn({ command: "restic", args, env });
|
||||
await cleanupTemporaryKeys(config, env);
|
||||
await cleanupTemporaryKeys(env);
|
||||
|
||||
const { stdout, stderr } = res;
|
||||
|
||||
@@ -881,7 +883,7 @@ export const cleanupTemporaryKeys = async (env: Record<string, string>) => {
|
||||
await fs.unlink(env._SFTP_KNOWN_HOSTS_PATH).catch(() => {});
|
||||
}
|
||||
|
||||
if (env.RESTIC_PASSWORD_FILE) {
|
||||
if (env.RESTIC_PASSWORD_FILE && env.RESTIC_PASSWORD_FILE !== RESTIC_PASS_FILE) {
|
||||
await fs.unlink(env.RESTIC_PASSWORD_FILE).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -901,9 +903,13 @@ export const addCommonArgs = (args: string[], env: Record<string, string>) => {
|
||||
args.push("-o", `sftp.args=${env._SFTP_SSH_ARGS}`);
|
||||
}
|
||||
|
||||
if (env._REST_INSECURE_TLS === "true") {
|
||||
if (env._INSECURE_TLS === "true") {
|
||||
args.push("--insecure-tls");
|
||||
}
|
||||
|
||||
if (env.RESTIC_CACERT) {
|
||||
args.push("--cacert", env.RESTIC_CACERT);
|
||||
}
|
||||
};
|
||||
|
||||
export const restic = {
|
||||
|
||||
3
bun.lock
3
bun.lock
@@ -13,6 +13,7 @@
|
||||
"@inquirer/prompts": "^8.0.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-progress": "^1.1.8",
|
||||
@@ -323,6 +324,8 @@
|
||||
|
||||
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="],
|
||||
|
||||
"@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="],
|
||||
|
||||
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
|
||||
|
||||
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@inquirer/prompts": "^8.0.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-progress": "^1.1.8",
|
||||
|
||||
Reference in New Issue
Block a user