@@ -131,7 +131,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
TLS Certificate Validation
- {repository.config.insecureTls ? (
+ {"insecureTls" in repository.config && repository.config.insecureTls ? (
disabled
) : (
enabled
From 08a19ed38db2de1a096ce02bc1f586b195d59503 Mon Sep 17 00:00:00 2001
From: tvories
Date: Tue, 30 Dec 2025 15:07:22 -0700
Subject: [PATCH 4/5] fix: PR suggestions
---
.../components/create-repository-form.tsx | 1 -
.../repository-forms/rest-repository-form.tsx | 99 +++++--------------
app/schemas/restic.ts | 42 +-------
app/server/utils/restic.ts | 2 +-
4 files changed, 25 insertions(+), 119 deletions(-)
diff --git a/app/client/modules/repositories/components/create-repository-form.tsx b/app/client/modules/repositories/components/create-repository-form.tsx
index 01a2d270..08fa6e0b 100644
--- a/app/client/modules/repositories/components/create-repository-form.tsx
+++ b/app/client/modules/repositories/components/create-repository-form.tsx
@@ -72,7 +72,6 @@ export const CreateRepositoryForm = ({
const form = useForm({
resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema),
defaultValues: initialValues,
- mode: "onTouched",
resetOptions: {
keepDefaultValues: true,
keepDirtyValues: false,
diff --git a/app/client/modules/repositories/components/repository-forms/rest-repository-form.tsx b/app/client/modules/repositories/components/repository-forms/rest-repository-form.tsx
index d0ff8395..4317f98d 100644
--- a/app/client/modules/repositories/components/repository-forms/rest-repository-form.tsx
+++ b/app/client/modules/repositories/components/repository-forms/rest-repository-form.tsx
@@ -1,4 +1,3 @@
-import { useState, useRef } from "react";
import type { UseFormReturn } from "react-hook-form";
import {
FormControl,
@@ -22,10 +21,6 @@ type Props = {
export const RestRepositoryForm = ({ form }: Props) => {
const insecureTls = form.watch("insecureTls");
const cacert = form.watch("cacert");
- const [showCertTooltip, setShowCertTooltip] = useState(false);
- const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });
- const tooltipTimeoutRef = useRef(null);
- const tooltipHideTimeoutRef = useRef(null);
return (
<>
@@ -91,81 +86,33 @@ export const RestRepositoryForm = ({ form }: Props) => {
CA Certificate (Optional)
-
+
Custom CA certificate for self-signed certificates (PEM format).{" "}
diff --git a/app/schemas/restic.ts b/app/schemas/restic.ts
index 96b692a8..1b41a5a7 100644
--- a/app/schemas/restic.ts
+++ b/app/schemas/restic.ts
@@ -62,53 +62,13 @@ export const rcloneRepositoryConfigSchema = type({
path: "string",
}).and(baseRepositoryConfigSchema);
-const pemCertificateType = type("string").narrow((value, ctx) => {
- if (!value || value.trim() === "") {
- return true;
- }
-
- const trimmed = value.trim();
-
- // Check for BEGIN and END markers
- if (!trimmed.includes("-----BEGIN CERTIFICATE-----") || !trimmed.includes("-----END CERTIFICATE-----")) {
- return ctx.error("Certificate must be in PEM format with BEGIN and END markers");
- }
-
- // Extract content between markers
- const beginMarker = "-----BEGIN CERTIFICATE-----";
- const endMarker = "-----END CERTIFICATE-----";
- const beginIndex = trimmed.indexOf(beginMarker);
- const endIndex = trimmed.indexOf(endMarker);
-
- if (beginIndex === -1 || endIndex === -1 || endIndex <= beginIndex) {
- return ctx.error("Invalid PEM certificate structure");
- }
-
- // Extract base64 content
- const base64Content = trimmed.substring(beginIndex + beginMarker.length, endIndex).replace(/\s/g, "");
-
- // Validate base64 format
- const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
- if (!base64Regex.test(base64Content)) {
- return ctx.error("Certificate contains invalid base64 characters");
- }
-
- if (base64Content.length === 0) {
- return ctx.error("Certificate content is empty");
- }
-
- return true;
-});
-
-const optionalPemCertificate = pemCertificateType.optional();
-
export const restRepositoryConfigSchema = type({
backend: "'rest'",
url: "string",
username: "string?",
password: "string?",
path: "string?",
- cacert: optionalPemCertificate as any,
+ cacert: "string?",
insecureTls: "boolean?",
}).and(baseRepositoryConfigSchema);
diff --git a/app/server/utils/restic.ts b/app/server/utils/restic.ts
index 8768c162..b7044c46 100644
--- a/app/server/utils/restic.ts
+++ b/app/server/utils/restic.ts
@@ -886,7 +886,7 @@ export const cleanupTemporaryKeys = async (config: RepositoryConfig, env: Record
await fs.unlink(env.GOOGLE_APPLICATION_CREDENTIALS).catch(() => {});
}
- if (config.backend === "rest" && env.RESTIC_CACERT) {
+ if (env.RESTIC_CACERT) {
await fs.unlink(env.RESTIC_CACERT).catch(() => {});
}
};
From e0027b7668bb9e62b6c70af8507843b3e7f36475 Mon Sep 17 00:00:00 2001
From: Nicolas Meienberger
Date: Fri, 2 Jan 2026 17:37:38 +0100
Subject: [PATCH 5/5] refactor: code style and api client
---
app/client/api-client/types.gen.ts | 26 +++++++++++
.../repository-forms/rest-repository-form.tsx | 43 +++++++------------
app/client/modules/repositories/tabs/info.tsx | 42 ++++++++----------
app/server/utils/restic.ts | 29 +++++++------
4 files changed, 76 insertions(+), 64 deletions(-)
diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts
index 9146e070..44b2b3c5 100644
--- a/app/client/api-client/types.gen.ts
+++ b/app/client/api-client/types.gen.ts
@@ -838,7 +838,9 @@ export type ListRepositoriesResponses = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -917,7 +919,9 @@ export type CreateRepositoryData = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -1058,7 +1062,9 @@ export type GetRepositoryResponses = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -1164,7 +1170,9 @@ export type UpdateRepositoryResponses = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -1487,7 +1495,9 @@ export type ListBackupSchedulesResponses = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -1749,7 +1759,9 @@ export type GetBackupScheduleResponses = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -1992,7 +2004,9 @@ export type GetBackupScheduleForVolumeResponses = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -2211,6 +2225,7 @@ export type GetScheduleNotificationsResponses = {
priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string;
type: 'ntfy';
+ accessToken?: string;
password?: string;
serverUrl?: string;
username?: string;
@@ -2310,6 +2325,7 @@ export type UpdateScheduleNotificationsResponses = {
priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string;
type: 'ntfy';
+ accessToken?: string;
password?: string;
serverUrl?: string;
username?: string;
@@ -2420,7 +2436,9 @@ export type GetScheduleMirrorsResponses = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -2526,7 +2544,9 @@ export type UpdateScheduleMirrorsResponses = {
} | {
backend: 'rest';
url: string;
+ cacert?: string;
customPassword?: string;
+ insecureTls?: boolean;
isExistingRepository?: boolean;
password?: string;
path?: string;
@@ -2646,6 +2666,7 @@ export type ListNotificationDestinationsResponses = {
priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string;
type: 'ntfy';
+ accessToken?: string;
password?: string;
serverUrl?: string;
username?: string;
@@ -2716,6 +2737,7 @@ export type CreateNotificationDestinationData = {
priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string;
type: 'ntfy';
+ accessToken?: string;
password?: string;
serverUrl?: string;
username?: string;
@@ -2785,6 +2807,7 @@ export type CreateNotificationDestinationResponses = {
priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string;
type: 'ntfy';
+ accessToken?: string;
password?: string;
serverUrl?: string;
username?: string;
@@ -2901,6 +2924,7 @@ export type GetNotificationDestinationResponses = {
priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string;
type: 'ntfy';
+ accessToken?: string;
password?: string;
serverUrl?: string;
username?: string;
@@ -2971,6 +2995,7 @@ export type UpdateNotificationDestinationData = {
priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string;
type: 'ntfy';
+ accessToken?: string;
password?: string;
serverUrl?: string;
username?: string;
@@ -3050,6 +3075,7 @@ export type UpdateNotificationDestinationResponses = {
priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string;
type: 'ntfy';
+ accessToken?: string;
password?: string;
serverUrl?: string;
username?: string;
diff --git a/app/client/modules/repositories/components/repository-forms/rest-repository-form.tsx b/app/client/modules/repositories/components/repository-forms/rest-repository-form.tsx
index 4317f98d..287519fb 100644
--- a/app/client/modules/repositories/components/repository-forms/rest-repository-form.tsx
+++ b/app/client/modules/repositories/components/repository-forms/rest-repository-form.tsx
@@ -13,6 +13,7 @@ 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;
@@ -52,25 +53,20 @@ export const RestRepositoryForm = ({ form }: Props) => {
disabled={!!cacert}
onCheckedChange={(checked) => {
field.onChange(checked);
- if (checked) {
- form.setValue("cacert", "");
- }
}}
/>