(null);
diff --git a/app/client/modules/notifications/routes/notification-details.tsx b/app/client/modules/notifications/routes/notification-details.tsx
index 2cba46de..303cea4c 100644
--- a/app/client/modules/notifications/routes/notification-details.tsx
+++ b/app/client/modules/notifications/routes/notification-details.tsx
@@ -97,7 +97,7 @@ export function NotificationDetailsPage({ notificationId }: { notificationId: st
diff --git a/app/client/modules/repositories/components/doctor-report.tsx b/app/client/modules/repositories/components/doctor-report.tsx
index 71008035..3cb9e7a5 100644
--- a/app/client/modules/repositories/components/doctor-report.tsx
+++ b/app/client/modules/repositories/components/doctor-report.tsx
@@ -41,7 +41,7 @@ export const DoctorReport = ({ result, repositoryStatus }: Props) => {
{step.step.replaceAll("_", " ")}
{step.success ? (
-
+
) : (
)}
diff --git a/app/client/modules/repositories/routes/repositories.tsx b/app/client/modules/repositories/routes/repositories.tsx
index 51a9f40d..9e821b14 100644
--- a/app/client/modules/repositories/routes/repositories.tsx
+++ b/app/client/modules/repositories/routes/repositories.tsx
@@ -143,7 +143,7 @@ export function RepositoriesPage() {
className={cn(
"inline-flex items-center gap-2 px-2 py-1 rounded-md text-xs bg-gray-500/10 text-gray-500",
{
- "bg-green-500/10 text-emerald-500": repository.status === "healthy",
+ "bg-success/10 text-success": repository.status === "healthy",
"bg-red-500/10 text-red-500": repository.status === "error",
},
)}
diff --git a/app/client/modules/repositories/tabs/info.tsx b/app/client/modules/repositories/tabs/info.tsx
index 1d37f140..b0b2ef8e 100644
--- a/app/client/modules/repositories/tabs/info.tsx
+++ b/app/client/modules/repositories/tabs/info.tsx
@@ -187,7 +187,7 @@ export const RepositoryInfoTabContent = ({ repository, initialStats }: Props) =>
{hasCaCert && (
CA Certificate
-
Configured
+
Configured
)}
{hasInsecureTlsConfig && (
@@ -217,7 +217,7 @@ export const RepositoryInfoTabContent = ({ repository, initialStats }: Props) =>
TLS Validation
Disabled
- Enabled
+ Enabled
)}
diff --git a/app/client/modules/settings/components/two-factor-section.tsx b/app/client/modules/settings/components/two-factor-section.tsx
index 7f8b5c8f..d506b681 100644
--- a/app/client/modules/settings/components/two-factor-section.tsx
+++ b/app/client/modules/settings/components/two-factor-section.tsx
@@ -34,7 +34,7 @@ export const TwoFactorSection = ({ twoFactorEnabled }: TwoFactorSectionProps) =>
Status:
{twoFactorEnabled ? (
- Enabled
+ Enabled
) : (
Disabled
)}
diff --git a/app/client/modules/settings/components/user-management.tsx b/app/client/modules/settings/components/user-management.tsx
index e1c24730..3300c230 100644
--- a/app/client/modules/settings/components/user-management.tsx
+++ b/app/client/modules/settings/components/user-management.tsx
@@ -154,7 +154,7 @@ export function UserManagement({ currentUser }: { currentUser: { id: string } |
Banned
-
+
Active
diff --git a/app/client/modules/volumes/components/create-volume-form.tsx b/app/client/modules/volumes/components/create-volume-form.tsx
index 8963314e..598d47c8 100644
--- a/app/client/modules/volumes/components/create-volume-form.tsx
+++ b/app/client/modules/volumes/components/create-volume-form.tsx
@@ -237,7 +237,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
>
{testBackendConnection.isPending && }
{!testBackendConnection.isPending && testMessage?.success && (
-
+
)}
{!testBackendConnection.isPending && testMessage && !testMessage.success && (
@@ -255,7 +255,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
{testMessage && (
diff --git a/app/client/modules/volumes/components/healthchecks-card.tsx b/app/client/modules/volumes/components/healthchecks-card.tsx
index 6047984d..36ead717 100644
--- a/app/client/modules/volumes/components/healthchecks-card.tsx
+++ b/app/client/modules/volumes/components/healthchecks-card.tsx
@@ -51,7 +51,7 @@ export const HealthchecksCard = ({ volume }: Props) => {
{volume.lastError &&
{volume.lastError}}
- {volume.status === "mounted" &&
Healthy}
+ {volume.status === "mounted" &&
Healthy}
{volume.status !== "unmounted" && (
Checked {formatTimeAgo(volume.lastHealthCheck)}
)}
diff --git a/app/client/modules/volumes/routes/volumes.tsx b/app/client/modules/volumes/routes/volumes.tsx
index 3db76de7..cce8a554 100644
--- a/app/client/modules/volumes/routes/volumes.tsx
+++ b/app/client/modules/volumes/routes/volumes.tsx
@@ -134,7 +134,7 @@ export function VolumesPage() {
{filteredVolumes.map((volume) => (
navigate({ to: `/volumes/${volume.shortId}` })}
>
{volume.name}
diff --git a/app/routes/__root.tsx b/app/routes/__root.tsx
index 7b56550b..c0875ab7 100644
--- a/app/routes/__root.tsx
+++ b/app/routes/__root.tsx
@@ -6,11 +6,23 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { Toaster } from "~/client/components/ui/sonner";
import { useServerEvents } from "~/client/hooks/use-server-events";
import { useEffect } from "react";
+import { ThemeProvider, THEME_COOKIE_NAME } from "~/client/components/theme-provider";
+import { createServerFn } from "@tanstack/react-start";
+import { getCookie } from "@tanstack/react-start/server";
+
+const fetchTheme = createServerFn({ method: "GET" }).handler(async () => {
+ const themeCookie = getCookie(THEME_COOKIE_NAME);
+ return (themeCookie === "light" ? "light" : "dark") as "light" | "dark";
+});
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
server: {
middleware: [apiClientMiddleware],
},
+ loader: async () => {
+ const theme = await fetchTheme();
+ return { theme };
+ },
head: () => ({
meta: [{ title: "Zerobyte - Open Source Backup Solution" }],
links: [
@@ -35,6 +47,7 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
});
function RootLayout() {
+ const { theme } = Route.useLoaderData();
useServerEvents();
useEffect(() => {
document.body.setAttribute("data-app-ready", "true");
@@ -44,7 +57,7 @@ function RootLayout() {
}, []);
return (
-
+
@@ -56,10 +69,12 @@ function RootLayout() {
-
-
-
-
+
+
+
+
+
+
diff --git a/app/server/core/__tests__/scheduler.test.ts b/app/server/core/__tests__/scheduler.test.ts
index 9d63869e..7b06ddd7 100644
--- a/app/server/core/__tests__/scheduler.test.ts
+++ b/app/server/core/__tests__/scheduler.test.ts
@@ -7,6 +7,7 @@ const flushMicrotasks = async () => {
};
const mockTimeZone = (timeZone: string) => {
+ // oxlint-disable-next-line typescript/unbound-method
const resolvedOptions = Intl.DateTimeFormat.prototype.resolvedOptions;
return spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions").mockImplementation(
function (this: Intl.DateTimeFormat) {
From 55dbcf0eb369b89dfb78eeb8660edc0d8d97ac3c Mon Sep 17 00:00:00 2001
From: Nico <47644445+nicotsx@users.noreply.github.com>
Date: Mon, 9 Mar 2026 18:27:03 +0100
Subject: [PATCH 12/15] refactor: move from arktype to zod (#637)
chore: gen api-client
---
.oxlintrc.json | 1 +
.../api-client/@tanstack/react-query.gen.ts | 4 +-
app/client/api-client/client/client.gen.ts | 8 +-
app/client/api-client/client/types.gen.ts | 2 +-
.../api-client/core/bodySerializer.gen.ts | 14 +-
app/client/api-client/types.gen.ts | 5063 ++++++++---------
.../file-browsers/snapshot-tree-browser.tsx | 12 +-
.../file-browsers/volume-file-browser.tsx | 2 +-
app/client/modules/auth/routes/login.tsx | 17 +-
app/client/modules/auth/routes/onboarding.tsx | 21 +-
.../components/create-schedule-form/index.tsx | 6 +-
.../retention-section.tsx | 12 +-
.../components/create-schedule-form/types.ts | 47 +-
.../components/create-notification-form.tsx | 38 +-
.../components/create-repository-form.tsx | 41 +-
.../repositories/routes/create-repository.tsx | 9 +-
.../repositories/routes/edit-repository.tsx | 15 +-
.../components/create-user-dialog.tsx | 20 +-
.../sso/routes/create-sso-provider.tsx | 24 +-
.../volumes/components/create-volume-form.tsx | 48 +-
.../modules/volumes/routes/create-volume.tsx | 8 +-
app/client/modules/volumes/tabs/info.tsx | 6 +-
app/routes/(auth)/login.error.tsx | 4 +-
app/routes/(auth)/login.tsx | 4 +-
app/routes/(dashboard)/admin/index.tsx | 4 +-
.../(dashboard)/backups/$backupId/index.tsx | 4 +-
.../repositories/$repositoryId/index.tsx | 4 +-
app/routes/(dashboard)/settings/index.tsx | 6 +-
app/routes/(dashboard)/volumes/$volumeId.tsx | 4 +-
app/schemas/events-dto.ts | 113 +-
app/schemas/notifications.ts | 144 +-
app/schemas/restic-dto.ts | 135 +-
app/schemas/restic.ts | 194 +-
app/schemas/volumes.ts | 130 +-
app/server/core/config.ts | 77 +-
app/server/db/schema.ts | 12 +-
app/server/modules/auth/auth.dto.ts | 94 +-
app/server/modules/backends/backend.ts | 3 +
app/server/modules/backups/backups.dto.ts | 253 +-
.../migrations/00004-concat-path-name.ts | 8 +-
.../notifications/notifications.dto.ts | 120 +-
.../notifications/notifications.service.ts | 6 +-
.../modules/repositories/helpers/doctor.ts | 15 +-
.../repositories/repositories.controller.ts | 4 +-
.../modules/repositories/repositories.dto.ts | 325 +-
.../repositories/repositories.service.ts | 13 +-
app/server/modules/sso/sso.dto.ts | 58 +-
app/server/modules/system/system.dto.ts | 52 +-
.../modules/volumes/volume.controller.ts | 4 +-
app/server/modules/volumes/volume.dto.ts | 167 +-
app/server/modules/volumes/volume.service.ts | 6 +-
app/server/utils/restic/commands/backup.ts | 17 +-
app/server/utils/restic/commands/ls.ts | 84 +-
app/server/utils/restic/commands/restore.ts | 38 +-
app/server/utils/restic/commands/snapshots.ts | 36 +-
app/server/utils/restic/commands/stats.ts | 11 +-
bun.lock | 2 +-
package.json | 4 +-
58 files changed, 3776 insertions(+), 3797 deletions(-)
diff --git a/.oxlintrc.json b/.oxlintrc.json
index f2e5a583..0bb6f8ba 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -50,6 +50,7 @@
"no-unused-vars": [
"warn",
{
+ "varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_",
"argsIgnorePattern": "^_"
}
diff --git a/app/client/api-client/@tanstack/react-query.gen.ts b/app/client/api-client/@tanstack/react-query.gen.ts
index db975ee0..d6510ffe 100644
--- a/app/client/api-client/@tanstack/react-query.gen.ts
+++ b/app/client/api-client/@tanstack/react-query.gen.ts
@@ -457,7 +457,7 @@ export const listFilesInfiniteQueryKey = (options: Options): Quer
/**
* List files in a volume directory
*/
-export const listFilesInfiniteOptions = (options: Options) => infiniteQueryOptions, QueryKey>, string | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>(
+export const listFilesInfiniteOptions = (options: Options) => infiniteQueryOptions, QueryKey>, number | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>(
// @ts-ignore
{
queryFn: async ({ pageParam, queryKey, signal }) => {
@@ -747,7 +747,7 @@ export const listSnapshotFilesInfiniteQueryKey = (options: Options) => infiniteQueryOptions, QueryKey>, string | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>(
+export const listSnapshotFilesInfiniteOptions = (options: Options) => infiniteQueryOptions, QueryKey>, number | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>(
// @ts-ignore
{
queryFn: async ({ pageParam, queryKey, signal }) => {
diff --git a/app/client/api-client/client/client.gen.ts b/app/client/api-client/client/client.gen.ts
index d9a078b1..7bddcbe1 100644
--- a/app/client/api-client/client/client.gen.ts
+++ b/app/client/api-client/client/client.gen.ts
@@ -38,7 +38,7 @@ export const createClient = (config: Config = {}): Client => {
...options,
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
headers: mergeHeaders(_config.headers, options.headers),
- serializedBody: undefined,
+ serializedBody: undefined as string | undefined,
};
if (opts.security) {
@@ -53,7 +53,7 @@ export const createClient = (config: Config = {}): Client => {
}
if (opts.body !== undefined && opts.bodySerializer) {
- opts.serializedBody = opts.bodySerializer(opts.body);
+ opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined;
}
// remove Content-Type header if body is empty to avoid sending invalid requests
@@ -259,8 +259,10 @@ export const createClient = (config: Config = {}): Client => {
});
};
+ const _buildUrl: Client['buildUrl'] = (options) => buildUrl({ ..._config, ...options });
+
return {
- buildUrl,
+ buildUrl: _buildUrl,
connect: makeMethodFn('CONNECT'),
delete: makeMethodFn('DELETE'),
get: makeMethodFn('GET'),
diff --git a/app/client/api-client/client/types.gen.ts b/app/client/api-client/client/types.gen.ts
index 701c3630..92f86372 100644
--- a/app/client/api-client/client/types.gen.ts
+++ b/app/client/api-client/client/types.gen.ts
@@ -191,7 +191,7 @@ export type Client = CoreClient
*/
export type CreateClientConfig = (
override?: Config,
-) => Config & T> | Promise & T>>;
+) => Config & T>;
export interface TDataShape {
body?: unknown;
diff --git a/app/client/api-client/core/bodySerializer.gen.ts b/app/client/api-client/core/bodySerializer.gen.ts
index bbfd8727..2c7177c5 100644
--- a/app/client/api-client/core/bodySerializer.gen.ts
+++ b/app/client/api-client/core/bodySerializer.gen.ts
@@ -5,7 +5,7 @@ import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerialize
export type QuerySerializer = (query: Record) => string;
-export type BodySerializer = (body: any) => any;
+export type BodySerializer = (body: unknown) => unknown;
type QuerySerializerOptionsObject = {
allowReserved?: boolean;
@@ -40,12 +40,10 @@ const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value:
};
export const formDataBodySerializer = {
- bodySerializer: | Array>>(
- body: T,
- ): FormData => {
+ bodySerializer: (body: unknown): FormData => {
const data = new FormData();
- Object.entries(body).forEach(([key, value]) => {
+ Object.entries(body as Record).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
@@ -61,15 +59,15 @@ export const formDataBodySerializer = {
};
export const jsonBodySerializer = {
- bodySerializer: (body: T): string =>
+ bodySerializer: (body: unknown): string =>
JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)),
};
export const urlSearchParamsBodySerializer = {
- bodySerializer: | Array>>(body: T): string => {
+ bodySerializer: (body: unknown): string => {
const data = new URLSearchParams();
- Object.entries(body).forEach(([key, value]) => {
+ Object.entries(body as Record).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts
index d319c8f7..1198641c 100644
--- a/app/client/api-client/types.gen.ts
+++ b/app/client/api-client/types.gen.ts
@@ -35,18 +35,18 @@ export type GetAdminUsersResponses = {
* List of users with roles and status
*/
200: {
- total: number;
users: Array<{
+ id: string;
+ name: string | null;
+ email: string;
+ role: string;
+ banned: boolean;
accounts: Array<{
id: string;
providerId: string;
}>;
- banned: boolean;
- email: string;
- id: string;
- name: string | null;
- role: string;
}>;
+ total: number;
};
};
@@ -98,9 +98,9 @@ export type GetUserDeletionImpactResponses = {
id: string;
name: string;
resources: {
- backupSchedulesCount: number;
- repositoriesCount: number;
volumesCount: number;
+ repositoriesCount: number;
+ backupSchedulesCount: number;
};
}>;
};
@@ -121,14 +121,14 @@ export type GetOrgMembersResponses = {
*/
200: {
members: Array<{
- createdAt: string;
id: string;
- role: string;
- user: {
- email: string;
- name: string | null;
- };
userId: string;
+ role: string;
+ createdAt: string;
+ user: {
+ name: string | null;
+ email: string;
+ };
}>;
};
};
@@ -137,7 +137,7 @@ export type GetOrgMembersResponse = GetOrgMembersResponses[keyof GetOrgMembersRe
export type UpdateMemberRoleData = {
body: {
- role: 'admin' | 'member';
+ role: 'member' | 'admin';
};
path: {
memberId: string;
@@ -335,64 +335,64 @@ export type ListVolumesResponses = {
* A list of volumes
*/
200: Array<{
- autoRemount: boolean;
+ id: number;
+ shortId: string;
+ name: string;
+ type: 'nfs' | 'smb' | 'directory' | 'webdav' | 'rclone' | 'sftp';
+ status: 'mounted' | 'unmounted' | 'error';
+ lastError: string | null;
+ createdAt: number;
+ updatedAt: number;
+ lastHealthCheck: number;
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- createdAt: number;
- id: number;
- lastError: string | null;
- lastHealthCheck: number;
- name: string;
- shortId: string;
- status: 'error' | 'mounted' | 'unmounted';
- type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
- updatedAt: number;
+ autoRemount: boolean;
}>;
};
@@ -400,55 +400,55 @@ export type ListVolumesResponse = ListVolumesResponses[keyof ListVolumesResponse
export type CreateVolumeData = {
body: {
+ name: string;
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- name: string;
};
path?: never;
query?: never;
@@ -460,64 +460,64 @@ export type CreateVolumeResponses = {
* Volume created successfully
*/
201: {
- autoRemount: boolean;
+ id: number;
+ shortId: string;
+ name: string;
+ type: 'nfs' | 'smb' | 'directory' | 'webdav' | 'rclone' | 'sftp';
+ status: 'mounted' | 'unmounted' | 'error';
+ lastError: string | null;
+ createdAt: number;
+ updatedAt: number;
+ lastHealthCheck: number;
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- createdAt: number;
- id: number;
- lastError: string | null;
- lastHealthCheck: number;
- name: string;
- shortId: string;
- status: 'error' | 'mounted' | 'unmounted';
- type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
- updatedAt: number;
+ autoRemount: boolean;
};
};
@@ -526,52 +526,52 @@ export type CreateVolumeResponse = CreateVolumeResponses[keyof CreateVolumeRespo
export type TestConnectionData = {
body: {
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
};
path?: never;
@@ -584,8 +584,8 @@ export type TestConnectionResponses = {
* Connection test result
*/
200: {
- message: string;
success: boolean;
+ message: string;
};
};
@@ -632,70 +632,70 @@ export type GetVolumeResponses = {
* Volume details
*/
200: {
- statfs: {
- free: number;
- total: number;
- used: number;
- };
volume: {
- autoRemount: boolean;
+ id: number;
+ shortId: string;
+ name: string;
+ type: 'nfs' | 'smb' | 'directory' | 'webdav' | 'rclone' | 'sftp';
+ status: 'mounted' | 'unmounted' | 'error';
+ lastError: string | null;
+ createdAt: number;
+ updatedAt: number;
+ lastHealthCheck: number;
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- createdAt: number;
- id: number;
- lastError: string | null;
- lastHealthCheck: number;
- name: string;
- shortId: string;
- status: 'error' | 'mounted' | 'unmounted';
- type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
- updatedAt: number;
+ autoRemount: boolean;
+ };
+ statfs: {
+ total: number;
+ used: number;
+ free: number;
};
};
};
@@ -704,56 +704,56 @@ export type GetVolumeResponse = GetVolumeResponses[keyof GetVolumeResponses];
export type UpdateVolumeData = {
body: {
+ name?: string;
autoRemount?: boolean;
config?: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- name?: string;
};
path: {
shortId: string;
@@ -774,64 +774,64 @@ export type UpdateVolumeResponses = {
* Volume updated successfully
*/
200: {
- autoRemount: boolean;
+ id: number;
+ shortId: string;
+ name: string;
+ type: 'nfs' | 'smb' | 'directory' | 'webdav' | 'rclone' | 'sftp';
+ status: 'mounted' | 'unmounted' | 'error';
+ lastError: string | null;
+ createdAt: number;
+ updatedAt: number;
+ lastHealthCheck: number;
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- createdAt: number;
- id: number;
- lastError: string | null;
- lastHealthCheck: number;
- name: string;
- shortId: string;
- status: 'error' | 'mounted' | 'unmounted';
- type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
- updatedAt: number;
+ autoRemount: boolean;
};
};
@@ -851,8 +851,8 @@ export type MountVolumeResponses = {
* Volume mounted successfully
*/
200: {
- status: 'error' | 'mounted' | 'unmounted';
error?: string;
+ status: 'mounted' | 'unmounted' | 'error';
};
};
@@ -872,8 +872,8 @@ export type UnmountVolumeResponses = {
* Volume unmounted successfully
*/
200: {
- status: 'error' | 'mounted' | 'unmounted';
error?: string;
+ status: 'mounted' | 'unmounted' | 'error';
};
};
@@ -900,8 +900,8 @@ export type HealthCheckVolumeResponses = {
* Volume health check result
*/
200: {
- status: 'error' | 'mounted' | 'unmounted';
error?: string;
+ status: 'mounted' | 'unmounted' | 'error';
};
};
@@ -913,9 +913,9 @@ export type ListFilesData = {
shortId: string;
};
query?: {
- limit?: string;
- offset?: string;
path?: string;
+ offset?: number;
+ limit?: number;
};
url: '/api/v1/volumes/{shortId}/files';
};
@@ -928,15 +928,15 @@ export type ListFilesResponses = {
files: Array<{
name: string;
path: string;
- type: 'directory' | 'file';
- modifiedAt?: number;
+ type: 'file' | 'directory';
size?: number;
+ modifiedAt?: number;
}>;
- hasMore: boolean;
- limit: number;
- offset: number;
path: string;
+ offset: number;
+ limit: number;
total: number;
+ hasMore: boolean;
};
};
@@ -962,9 +962,9 @@ export type BrowseFilesystemResponses = {
directories: Array<{
name: string;
path: string;
- type: 'directory' | 'file';
- modifiedAt?: number;
+ type: 'file' | 'directory';
size?: number;
+ modifiedAt?: number;
}>;
path: string;
};
@@ -984,183 +984,183 @@ export type ListRepositoriesResponses = {
* List of repositories
*/
200: Array<{
- compressionMode: 'auto' | 'max' | 'off' | null;
+ id: string;
+ shortId: string;
+ name: string;
+ type: 'local' | 's3' | 'r2' | 'gcs' | 'azure' | 'rclone' | 'rest' | 'sftp';
config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
backend: 's3';
- bucket: string;
endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ accessKeyId: string;
+ secretAccessKey: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'local';
path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rclone';
- path: string;
remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ path: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rest';
url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
+ username?: string;
password?: string;
path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
- username?: string;
} | {
backend: 'sftp';
host: string;
+ port?: string | number;
+ user: string;
path: string;
privateKey: string;
- user: string;
- port?: number;
skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
};
- createdAt: number;
- doctorResult: {
- completedAt: number;
- steps: Array<{
- error: string | null;
- output: string | null;
- step: string;
- success: boolean;
- }>;
- success: boolean;
- } | null;
- id: string;
+ compressionMode: 'off' | 'auto' | 'max' | null;
+ status: 'healthy' | 'error' | 'unknown' | 'doctor' | 'cancelled' | null;
lastChecked: number | null;
lastError: string | null;
- name: string;
- shortId: string;
- status: 'cancelled' | 'doctor' | 'error' | 'healthy' | 'unknown' | null;
- type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
+ doctorResult: {
+ success: boolean;
+ steps: Array<{
+ step: string;
+ success: boolean;
+ output: string | null;
+ error: string | null;
+ }>;
+ completedAt: number;
+ } | null;
+ createdAt: number;
updatedAt: number;
}>;
};
@@ -1169,166 +1169,166 @@ export type ListRepositoriesResponse = ListRepositoriesResponses[keyof ListRepos
export type CreateRepositoryData = {
body: {
+ name: string;
+ compressionMode?: 'off' | 'auto' | 'max';
config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
backend: 's3';
- bucket: string;
endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ accessKeyId: string;
+ secretAccessKey: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'local';
path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rclone';
- path: string;
remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ path: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rest';
url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
+ username?: string;
password?: string;
path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
- username?: string;
} | {
backend: 'sftp';
host: string;
+ port?: string | number;
+ user: string;
path: string;
privateKey: string;
- user: string;
- port?: number;
skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
};
- name: string;
- compressionMode?: 'auto' | 'max' | 'off';
};
path?: never;
query?: never;
@@ -1343,8 +1343,8 @@ export type CreateRepositoryResponses = {
message: string;
repository: {
id: string;
- name: string;
shortId: string;
+ name: string;
};
};
};
@@ -1404,183 +1404,183 @@ export type GetRepositoryResponses = {
* Repository details
*/
200: {
- compressionMode: 'auto' | 'max' | 'off' | null;
+ id: string;
+ shortId: string;
+ name: string;
+ type: 'local' | 's3' | 'r2' | 'gcs' | 'azure' | 'rclone' | 'rest' | 'sftp';
config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
backend: 's3';
- bucket: string;
endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ accessKeyId: string;
+ secretAccessKey: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'local';
path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rclone';
- path: string;
remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ path: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rest';
url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
+ username?: string;
password?: string;
path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
- username?: string;
} | {
backend: 'sftp';
host: string;
+ port?: string | number;
+ user: string;
path: string;
privateKey: string;
- user: string;
- port?: number;
skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
};
- createdAt: number;
- doctorResult: {
- completedAt: number;
- steps: Array<{
- error: string | null;
- output: string | null;
- step: string;
- success: boolean;
- }>;
- success: boolean;
- } | null;
- id: string;
+ compressionMode: 'off' | 'auto' | 'max' | null;
+ status: 'healthy' | 'error' | 'unknown' | 'doctor' | 'cancelled' | null;
lastChecked: number | null;
lastError: string | null;
- name: string;
- shortId: string;
- status: 'cancelled' | 'doctor' | 'error' | 'healthy' | 'unknown' | null;
- type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
+ doctorResult: {
+ success: boolean;
+ steps: Array<{
+ step: string;
+ success: boolean;
+ output: string | null;
+ error: string | null;
+ }>;
+ completedAt: number;
+ } | null;
+ createdAt: number;
updatedAt: number;
};
};
@@ -1589,166 +1589,166 @@ export type GetRepositoryResponse = GetRepositoryResponses[keyof GetRepositoryRe
export type UpdateRepositoryData = {
body: {
- compressionMode?: 'auto' | 'max' | 'off';
+ name?: string;
+ compressionMode?: 'off' | 'auto' | 'max';
config?: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
backend: 's3';
- bucket: string;
endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ accessKeyId: string;
+ secretAccessKey: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'local';
path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rclone';
- path: string;
remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ path: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rest';
url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
+ username?: string;
password?: string;
path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
- username?: string;
} | {
backend: 'sftp';
host: string;
+ port?: string | number;
+ user: string;
path: string;
privateKey: string;
- user: string;
- port?: number;
skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
};
- name?: string;
};
path: {
shortId: string;
@@ -1777,183 +1777,183 @@ export type UpdateRepositoryResponses = {
* Repository updated successfully
*/
200: {
- compressionMode: 'auto' | 'max' | 'off' | null;
+ id: string;
+ shortId: string;
+ name: string;
+ type: 'local' | 's3' | 'r2' | 'gcs' | 'azure' | 'rclone' | 'rest' | 'sftp';
config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
backend: 's3';
- bucket: string;
endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ accessKeyId: string;
+ secretAccessKey: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'local';
path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rclone';
- path: string;
remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ path: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rest';
url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
+ username?: string;
password?: string;
path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
- username?: string;
} | {
backend: 'sftp';
host: string;
+ port?: string | number;
+ user: string;
path: string;
privateKey: string;
- user: string;
- port?: number;
skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
};
- createdAt: number;
- doctorResult: {
- completedAt: number;
- steps: Array<{
- error: string | null;
- output: string | null;
- step: string;
- success: boolean;
- }>;
- success: boolean;
- } | null;
- id: string;
+ compressionMode: 'off' | 'auto' | 'max' | null;
+ status: 'healthy' | 'error' | 'unknown' | 'doctor' | 'cancelled' | null;
lastChecked: number | null;
lastError: string | null;
- name: string;
- shortId: string;
- status: 'cancelled' | 'doctor' | 'error' | 'healthy' | 'unknown' | null;
- type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
+ doctorResult: {
+ success: boolean;
+ steps: Array<{
+ step: string;
+ success: boolean;
+ output: string | null;
+ error: string | null;
+ }>;
+ completedAt: number;
+ } | null;
+ createdAt: number;
updatedAt: number;
};
};
@@ -1974,12 +1974,12 @@ export type GetRepositoryStatsResponses = {
* Repository statistics
*/
200: {
- compression_progress?: number;
- compression_ratio?: number;
- compression_space_saving?: number;
- snapshots_count?: number;
total_size?: number;
total_uncompressed_size?: number;
+ compression_ratio?: number;
+ compression_progress?: number;
+ compression_space_saving?: number;
+ snapshots_count?: number;
};
};
@@ -1999,12 +1999,12 @@ export type RefreshRepositoryStatsResponses = {
* Refreshed repository statistics
*/
200: {
- compression_progress?: number;
- compression_ratio?: number;
- compression_space_saving?: number;
- snapshots_count?: number;
total_size?: number;
total_uncompressed_size?: number;
+ compression_ratio?: number;
+ compression_progress?: number;
+ compression_space_saving?: number;
+ snapshots_count?: number;
};
};
@@ -2048,29 +2048,29 @@ export type ListSnapshotsResponses = {
* List of snapshots
*/
200: Array<{
- duration: number;
- paths: Array;
- retentionCategories: Array;
short_id: string;
- size: number;
- tags: Array;
time: number;
+ paths: Array;
+ size: number;
+ duration: number;
+ tags: Array;
+ retentionCategories: Array;
hostname?: string;
summary?: {
- backup_end: string;
- backup_start: string;
- data_added: number;
- data_blobs: number;
- dirs_changed: number;
- dirs_new: number;
- dirs_unmodified: number;
- files_changed: number;
files_new: number;
+ files_changed: number;
files_unmodified: number;
- total_bytes_processed: number;
- total_files_processed: number;
+ dirs_new: number;
+ dirs_changed: number;
+ dirs_unmodified: number;
+ data_blobs: number;
tree_blobs: number;
+ data_added: number;
data_added_packed?: number;
+ total_files_processed: number;
+ total_bytes_processed: number;
+ backup_start: string;
+ backup_end: string;
};
}>;
};
@@ -2091,8 +2091,8 @@ export type RefreshSnapshotsResponses = {
* Snapshot cache cleared and refreshed
*/
200: {
- count: number;
message: string;
+ count: number;
};
};
@@ -2134,29 +2134,29 @@ export type GetSnapshotDetailsResponses = {
* Snapshot details
*/
200: {
- duration: number;
- paths: Array;
- retentionCategories: Array;
short_id: string;
- size: number;
- tags: Array;
time: number;
+ paths: Array;
+ size: number;
+ duration: number;
+ tags: Array;
+ retentionCategories: Array;
hostname?: string;
summary?: {
- backup_end: string;
- backup_start: string;
- data_added: number;
- data_blobs: number;
- dirs_changed: number;
- dirs_new: number;
- dirs_unmodified: number;
- files_changed: number;
files_new: number;
+ files_changed: number;
files_unmodified: number;
- total_bytes_processed: number;
- total_files_processed: number;
+ dirs_new: number;
+ dirs_changed: number;
+ dirs_unmodified: number;
+ data_blobs: number;
tree_blobs: number;
+ data_added: number;
data_added_packed?: number;
+ total_files_processed: number;
+ total_bytes_processed: number;
+ backup_start: string;
+ backup_end: string;
};
};
};
@@ -2170,9 +2170,9 @@ export type ListSnapshotFilesData = {
snapshotId: string;
};
query?: {
- limit?: string;
- offset?: string;
path?: string;
+ offset?: number;
+ limit?: number;
};
url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}/files';
};
@@ -2182,29 +2182,29 @@ export type ListSnapshotFilesResponses = {
* List of files and directories in the snapshot
*/
200: {
- files: Array<{
- name: string;
- path: string;
- type: string;
- atime?: string;
- ctime?: string;
- gid?: number;
- mode?: number;
- mtime?: string;
- size?: number;
- uid?: number;
- }>;
- hasMore: boolean;
- limit: number;
- offset: number;
snapshot: {
- hostname: string;
id: string;
- paths: Array;
short_id: string;
time: string;
+ hostname: string;
+ paths: Array;
};
+ files: Array<{
+ name: string;
+ type: string;
+ path: string;
+ uid?: number;
+ gid?: number;
+ size?: number;
+ mode?: number;
+ mtime?: string;
+ atime?: string;
+ ctime?: string;
+ }>;
+ offset: number;
+ limit: number;
total: number;
+ hasMore: boolean;
};
};
@@ -2217,8 +2217,8 @@ export type DumpSnapshotData = {
snapshotId: string;
};
query?: {
- kind?: 'dir' | 'file';
path?: string;
+ kind?: 'file' | 'dir';
};
url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}/dump';
};
@@ -2235,13 +2235,13 @@ export type DumpSnapshotResponse = DumpSnapshotResponses[keyof DumpSnapshotRespo
export type RestoreSnapshotData = {
body: {
snapshotId: string;
- delete?: boolean;
+ include?: Array;
+ selectedItemKind?: 'file' | 'dir';
exclude?: Array;
excludeXattr?: Array;
- include?: Array;
- overwrite?: 'always' | 'if-changed' | 'if-newer' | 'never';
- selectedItemKind?: 'dir' | 'file';
+ delete?: boolean;
targetPath?: string;
+ overwrite?: 'always' | 'if-changed' | 'if-newer' | 'never';
};
path: {
shortId: string;
@@ -2255,10 +2255,10 @@ export type RestoreSnapshotResponses = {
* Snapshot restored successfully
*/
200: {
+ success: boolean;
+ message: string;
filesRestored: number;
filesSkipped: number;
- message: string;
- success: boolean;
};
};
@@ -2333,8 +2333,8 @@ export type UnlockRepositoryResponses = {
* Repository unlocked successfully
*/
200: {
- message: string;
success: boolean;
+ message: string;
};
};
@@ -2405,273 +2405,273 @@ export type ListBackupSchedulesResponses = {
* List of backup schedules
*/
200: Array<{
- createdAt: number;
- cronExpression: string;
- customResticParams: Array | null;
- enabled: boolean;
- excludeIfPresent: Array | null;
- excludePatterns: Array | null;
id: number;
- includePatterns: Array | null;
- lastBackupAt: number | null;
- lastBackupError: string | null;
- lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
- name: string;
- nextBackupAt: number | null;
- oneFileSystem: boolean;
- repository: {
- compressionMode: 'auto' | 'max' | 'off' | null;
- config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
- backend: 's3';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
- bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'local';
- path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'rest';
- url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- password?: string;
- path?: string;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- username?: string;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- privateKey: string;
- user: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- knownHosts?: string;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- };
- createdAt: number;
- doctorResult: {
- completedAt: number;
- steps: Array<{
- error: string | null;
- output: string | null;
- step: string;
- success: boolean;
- }>;
- success: boolean;
- } | null;
- id: string;
- lastChecked: number | null;
- lastError: string | null;
- name: string;
- shortId: string;
- status: 'cancelled' | 'doctor' | 'error' | 'healthy' | 'unknown' | null;
- type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
- updatedAt: number;
- };
- repositoryId: string;
- retentionPolicy: {
- keepDaily?: number;
- keepHourly?: number;
- keepLast?: number;
- keepMonthly?: number;
- keepWeekly?: number;
- keepWithinDuration?: string;
- keepYearly?: number;
- } | null;
shortId: string;
+ name: string;
+ volumeId: number;
+ repositoryId: string;
+ enabled: boolean;
+ cronExpression: string;
+ retentionPolicy: {
+ keepLast?: number;
+ keepHourly?: number;
+ keepDaily?: number;
+ keepWeekly?: number;
+ keepMonthly?: number;
+ keepYearly?: number;
+ keepWithinDuration?: string;
+ } | null;
+ excludePatterns: Array | null;
+ excludeIfPresent: Array | null;
+ includePatterns: Array | null;
+ oneFileSystem: boolean;
+ customResticParams: Array | null;
+ lastBackupAt: number | null;
+ lastBackupStatus: 'success' | 'error' | 'in_progress' | 'warning' | null;
+ lastBackupError: string | null;
+ nextBackupAt: number | null;
+ createdAt: number;
updatedAt: number;
volume: {
- autoRemount: boolean;
+ id: number;
+ shortId: string;
+ name: string;
+ type: 'nfs' | 'smb' | 'directory' | 'webdav' | 'rclone' | 'sftp';
+ status: 'mounted' | 'unmounted' | 'error';
+ lastError: string | null;
+ createdAt: number;
+ updatedAt: number;
+ lastHealthCheck: number;
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- createdAt: number;
- id: number;
- lastError: string | null;
- lastHealthCheck: number;
- name: string;
+ autoRemount: boolean;
+ };
+ repository: {
+ id: string;
shortId: string;
- status: 'error' | 'mounted' | 'unmounted';
- type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
+ name: string;
+ type: 'local' | 's3' | 'r2' | 'gcs' | 'azure' | 'rclone' | 'rest' | 'sftp';
+ config: {
+ backend: 's3';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'local';
+ path: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'rest';
+ url: string;
+ username?: string;
+ password?: string;
+ path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ user: string;
+ path: string;
+ privateKey: string;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ };
+ compressionMode: 'off' | 'auto' | 'max' | null;
+ status: 'healthy' | 'error' | 'unknown' | 'doctor' | 'cancelled' | null;
+ lastChecked: number | null;
+ lastError: string | null;
+ doctorResult: {
+ success: boolean;
+ steps: Array<{
+ step: string;
+ success: boolean;
+ output: string | null;
+ error: string | null;
+ }>;
+ completedAt: number;
+ } | null;
+ createdAt: number;
updatedAt: number;
};
- volumeId: number;
}>;
};
@@ -2679,26 +2679,26 @@ export type ListBackupSchedulesResponse = ListBackupSchedulesResponses[keyof Lis
export type CreateBackupScheduleData = {
body: {
- cronExpression: string;
- enabled: boolean;
name: string;
+ volumeId: string | number;
repositoryId: string;
- volumeId: number | string;
- customResticParams?: Array;
- excludeIfPresent?: Array;
+ enabled: boolean;
+ cronExpression: string;
+ retentionPolicy?: {
+ keepLast?: number;
+ keepHourly?: number;
+ keepDaily?: number;
+ keepWeekly?: number;
+ keepMonthly?: number;
+ keepYearly?: number;
+ keepWithinDuration?: string;
+ };
excludePatterns?: Array;
+ excludeIfPresent?: Array;
includePatterns?: Array;
oneFileSystem?: boolean;
- retentionPolicy?: {
- keepDaily?: number;
- keepHourly?: number;
- keepLast?: number;
- keepMonthly?: number;
- keepWeekly?: number;
- keepWithinDuration?: string;
- keepYearly?: number;
- };
tags?: Array;
+ customResticParams?: Array;
};
path?: never;
query?: never;
@@ -2710,33 +2710,33 @@ export type CreateBackupScheduleResponses = {
* Backup schedule created successfully
*/
201: {
- createdAt: number;
- cronExpression: string;
- customResticParams: Array | null;
- enabled: boolean;
- excludeIfPresent: Array | null;
- excludePatterns: Array | null;
id: number;
- includePatterns: Array | null;
- lastBackupAt: number | null;
- lastBackupError: string | null;
- lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
- name: string;
- nextBackupAt: number | null;
- oneFileSystem: boolean;
- repositoryId: string;
- retentionPolicy: {
- keepDaily?: number;
- keepHourly?: number;
- keepLast?: number;
- keepMonthly?: number;
- keepWeekly?: number;
- keepWithinDuration?: string;
- keepYearly?: number;
- } | null;
shortId: string;
- updatedAt: number;
+ name: string;
volumeId: number;
+ repositoryId: string;
+ enabled: boolean;
+ cronExpression: string;
+ retentionPolicy: {
+ keepLast?: number;
+ keepHourly?: number;
+ keepDaily?: number;
+ keepWeekly?: number;
+ keepMonthly?: number;
+ keepYearly?: number;
+ keepWithinDuration?: string;
+ } | null;
+ excludePatterns: Array | null;
+ excludeIfPresent: Array | null;
+ includePatterns: Array | null;
+ oneFileSystem: boolean;
+ customResticParams: Array | null;
+ lastBackupAt: number | null;
+ lastBackupStatus: 'success' | 'error' | 'in_progress' | 'warning' | null;
+ lastBackupError: string | null;
+ nextBackupAt: number | null;
+ createdAt: number;
+ updatedAt: number;
};
};
@@ -2776,273 +2776,273 @@ export type GetBackupScheduleResponses = {
* Backup schedule details
*/
200: {
- createdAt: number;
- cronExpression: string;
- customResticParams: Array | null;
- enabled: boolean;
- excludeIfPresent: Array | null;
- excludePatterns: Array | null;
id: number;
- includePatterns: Array | null;
- lastBackupAt: number | null;
- lastBackupError: string | null;
- lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
- name: string;
- nextBackupAt: number | null;
- oneFileSystem: boolean;
- repository: {
- compressionMode: 'auto' | 'max' | 'off' | null;
- config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
- backend: 's3';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
- bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'local';
- path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'rest';
- url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- password?: string;
- path?: string;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- username?: string;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- privateKey: string;
- user: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- knownHosts?: string;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- };
- createdAt: number;
- doctorResult: {
- completedAt: number;
- steps: Array<{
- error: string | null;
- output: string | null;
- step: string;
- success: boolean;
- }>;
- success: boolean;
- } | null;
- id: string;
- lastChecked: number | null;
- lastError: string | null;
- name: string;
- shortId: string;
- status: 'cancelled' | 'doctor' | 'error' | 'healthy' | 'unknown' | null;
- type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
- updatedAt: number;
- };
- repositoryId: string;
- retentionPolicy: {
- keepDaily?: number;
- keepHourly?: number;
- keepLast?: number;
- keepMonthly?: number;
- keepWeekly?: number;
- keepWithinDuration?: string;
- keepYearly?: number;
- } | null;
shortId: string;
+ name: string;
+ volumeId: number;
+ repositoryId: string;
+ enabled: boolean;
+ cronExpression: string;
+ retentionPolicy: {
+ keepLast?: number;
+ keepHourly?: number;
+ keepDaily?: number;
+ keepWeekly?: number;
+ keepMonthly?: number;
+ keepYearly?: number;
+ keepWithinDuration?: string;
+ } | null;
+ excludePatterns: Array | null;
+ excludeIfPresent: Array | null;
+ includePatterns: Array | null;
+ oneFileSystem: boolean;
+ customResticParams: Array | null;
+ lastBackupAt: number | null;
+ lastBackupStatus: 'success' | 'error' | 'in_progress' | 'warning' | null;
+ lastBackupError: string | null;
+ nextBackupAt: number | null;
+ createdAt: number;
updatedAt: number;
volume: {
- autoRemount: boolean;
+ id: number;
+ shortId: string;
+ name: string;
+ type: 'nfs' | 'smb' | 'directory' | 'webdav' | 'rclone' | 'sftp';
+ status: 'mounted' | 'unmounted' | 'error';
+ lastError: string | null;
+ createdAt: number;
+ updatedAt: number;
+ lastHealthCheck: number;
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- createdAt: number;
- id: number;
- lastError: string | null;
- lastHealthCheck: number;
- name: string;
+ autoRemount: boolean;
+ };
+ repository: {
+ id: string;
shortId: string;
- status: 'error' | 'mounted' | 'unmounted';
- type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
+ name: string;
+ type: 'local' | 's3' | 'r2' | 'gcs' | 'azure' | 'rclone' | 'rest' | 'sftp';
+ config: {
+ backend: 's3';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'local';
+ path: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'rest';
+ url: string;
+ username?: string;
+ password?: string;
+ path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ user: string;
+ path: string;
+ privateKey: string;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ };
+ compressionMode: 'off' | 'auto' | 'max' | null;
+ status: 'healthy' | 'error' | 'unknown' | 'doctor' | 'cancelled' | null;
+ lastChecked: number | null;
+ lastError: string | null;
+ doctorResult: {
+ success: boolean;
+ steps: Array<{
+ step: string;
+ success: boolean;
+ output: string | null;
+ error: string | null;
+ }>;
+ completedAt: number;
+ } | null;
+ createdAt: number;
updatedAt: number;
};
- volumeId: number;
};
};
@@ -3050,25 +3050,25 @@ export type GetBackupScheduleResponse = GetBackupScheduleResponses[keyof GetBack
export type UpdateBackupScheduleData = {
body: {
- cronExpression: string;
- repositoryId: string;
- customResticParams?: Array;
- enabled?: boolean;
- excludeIfPresent?: Array;
- excludePatterns?: Array;
- includePatterns?: Array;
name?: string;
- oneFileSystem?: boolean;
+ repositoryId: string;
+ enabled?: boolean;
+ cronExpression: string;
retentionPolicy?: {
- keepDaily?: number;
- keepHourly?: number;
keepLast?: number;
- keepMonthly?: number;
+ keepHourly?: number;
+ keepDaily?: number;
keepWeekly?: number;
- keepWithinDuration?: string;
+ keepMonthly?: number;
keepYearly?: number;
+ keepWithinDuration?: string;
};
+ excludePatterns?: Array;
+ excludeIfPresent?: Array;
+ includePatterns?: Array;
+ oneFileSystem?: boolean;
tags?: Array;
+ customResticParams?: Array;
};
path: {
shortId: string;
@@ -3082,33 +3082,33 @@ export type UpdateBackupScheduleResponses = {
* Backup schedule updated successfully
*/
200: {
- createdAt: number;
- cronExpression: string;
- customResticParams: Array | null;
- enabled: boolean;
- excludeIfPresent: Array | null;
- excludePatterns: Array | null;
id: number;
- includePatterns: Array | null;
- lastBackupAt: number | null;
- lastBackupError: string | null;
- lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
- name: string;
- nextBackupAt: number | null;
- oneFileSystem: boolean;
- repositoryId: string;
- retentionPolicy: {
- keepDaily?: number;
- keepHourly?: number;
- keepLast?: number;
- keepMonthly?: number;
- keepWeekly?: number;
- keepWithinDuration?: string;
- keepYearly?: number;
- } | null;
shortId: string;
- updatedAt: number;
+ name: string;
volumeId: number;
+ repositoryId: string;
+ enabled: boolean;
+ cronExpression: string;
+ retentionPolicy: {
+ keepLast?: number;
+ keepHourly?: number;
+ keepDaily?: number;
+ keepWeekly?: number;
+ keepMonthly?: number;
+ keepYearly?: number;
+ keepWithinDuration?: string;
+ } | null;
+ excludePatterns: Array | null;
+ excludeIfPresent: Array | null;
+ includePatterns: Array | null;
+ oneFileSystem: boolean;
+ customResticParams: Array | null;
+ lastBackupAt: number | null;
+ lastBackupStatus: 'success' | 'error' | 'in_progress' | 'warning' | null;
+ lastBackupError: string | null;
+ nextBackupAt: number | null;
+ createdAt: number;
+ updatedAt: number;
};
};
@@ -3128,273 +3128,273 @@ export type GetBackupScheduleForVolumeResponses = {
* Backup schedule details for the volume
*/
200: {
- createdAt: number;
- cronExpression: string;
- customResticParams: Array | null;
- enabled: boolean;
- excludeIfPresent: Array | null;
- excludePatterns: Array | null;
id: number;
- includePatterns: Array | null;
- lastBackupAt: number | null;
- lastBackupError: string | null;
- lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
- name: string;
- nextBackupAt: number | null;
- oneFileSystem: boolean;
- repository: {
- compressionMode: 'auto' | 'max' | 'off' | null;
- config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
- backend: 's3';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
- bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'local';
- path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'rest';
- url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- password?: string;
- path?: string;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- username?: string;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- privateKey: string;
- user: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- knownHosts?: string;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- };
- createdAt: number;
- doctorResult: {
- completedAt: number;
- steps: Array<{
- error: string | null;
- output: string | null;
- step: string;
- success: boolean;
- }>;
- success: boolean;
- } | null;
- id: string;
- lastChecked: number | null;
- lastError: string | null;
- name: string;
- shortId: string;
- status: 'cancelled' | 'doctor' | 'error' | 'healthy' | 'unknown' | null;
- type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
- updatedAt: number;
- };
- repositoryId: string;
- retentionPolicy: {
- keepDaily?: number;
- keepHourly?: number;
- keepLast?: number;
- keepMonthly?: number;
- keepWeekly?: number;
- keepWithinDuration?: string;
- keepYearly?: number;
- } | null;
shortId: string;
+ name: string;
+ volumeId: number;
+ repositoryId: string;
+ enabled: boolean;
+ cronExpression: string;
+ retentionPolicy: {
+ keepLast?: number;
+ keepHourly?: number;
+ keepDaily?: number;
+ keepWeekly?: number;
+ keepMonthly?: number;
+ keepYearly?: number;
+ keepWithinDuration?: string;
+ } | null;
+ excludePatterns: Array | null;
+ excludeIfPresent: Array | null;
+ includePatterns: Array | null;
+ oneFileSystem: boolean;
+ customResticParams: Array | null;
+ lastBackupAt: number | null;
+ lastBackupStatus: 'success' | 'error' | 'in_progress' | 'warning' | null;
+ lastBackupError: string | null;
+ nextBackupAt: number | null;
+ createdAt: number;
updatedAt: number;
volume: {
- autoRemount: boolean;
+ id: number;
+ shortId: string;
+ name: string;
+ type: 'nfs' | 'smb' | 'directory' | 'webdav' | 'rclone' | 'sftp';
+ status: 'mounted' | 'unmounted' | 'error';
+ lastError: string | null;
+ createdAt: number;
+ updatedAt: number;
+ lastHealthCheck: number;
config: {
- backend: 'directory';
- path: string;
- readOnly?: false;
- } | {
backend: 'nfs';
- exportPath: string;
server: string;
+ exportPath: string;
+ port?: string | number;
version: '3' | '4' | '4.1';
- port?: number;
- readOnly?: boolean;
- } | {
- backend: 'rclone';
- path: string;
- remote: string;
- readOnly?: boolean;
- } | {
- backend: 'sftp';
- host: string;
- path: string;
- username: string;
- port?: number;
- skipHostKeyCheck?: boolean;
- knownHosts?: string;
- password?: string;
- privateKey?: string;
readOnly?: boolean;
} | {
backend: 'smb';
server: string;
share: string;
- vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
- port?: number;
- domain?: string;
- guest?: boolean;
- password?: string;
- readOnly?: boolean;
username?: string;
+ password?: string;
+ guest?: boolean;
+ vers?: '1.0' | '2.0' | '2.1' | '3.0' | 'auto';
+ domain?: string;
+ port?: string | number;
+ readOnly?: boolean;
} | {
backend: 'webdav';
- path: string;
server: string;
- port?: number;
+ path: string;
+ username?: string;
password?: string;
+ port?: string | number;
readOnly?: boolean;
ssl?: boolean;
- username?: string;
+ } | {
+ backend: 'directory';
+ path: string;
+ readOnly?: false;
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ readOnly?: boolean;
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ username: string;
+ password?: string;
+ privateKey?: string;
+ path: string;
+ readOnly?: boolean;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
};
- createdAt: number;
- id: number;
- lastError: string | null;
- lastHealthCheck: number;
- name: string;
+ autoRemount: boolean;
+ };
+ repository: {
+ id: string;
shortId: string;
- status: 'error' | 'mounted' | 'unmounted';
- type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav';
+ name: string;
+ type: 'local' | 's3' | 'r2' | 'gcs' | 'azure' | 'rclone' | 'rest' | 'sftp';
+ config: {
+ backend: 's3';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'local';
+ path: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'rclone';
+ remote: string;
+ path: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'rest';
+ url: string;
+ username?: string;
+ password?: string;
+ path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'sftp';
+ host: string;
+ port?: string | number;
+ user: string;
+ path: string;
+ privateKey: string;
+ skipHostKeyCheck?: boolean;
+ knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ };
+ compressionMode: 'off' | 'auto' | 'max' | null;
+ status: 'healthy' | 'error' | 'unknown' | 'doctor' | 'cancelled' | null;
+ lastChecked: number | null;
+ lastError: string | null;
+ doctorResult: {
+ success: boolean;
+ steps: Array<{
+ step: string;
+ success: boolean;
+ output: string | null;
+ error: string | null;
+ }>;
+ completedAt: number;
+ } | null;
+ createdAt: number;
updatedAt: number;
};
- volumeId: number;
} | null;
};
@@ -3481,81 +3481,81 @@ export type GetScheduleNotificationsResponses = {
* List of notification assignments for the schedule
*/
200: Array<{
+ scheduleId: number;
+ destinationId: number;
+ notifyOnStart: boolean;
+ notifyOnSuccess: boolean;
+ notifyOnWarning: boolean;
+ notifyOnFailure: boolean;
createdAt: number;
destination: {
+ id: number;
+ name: string;
+ enabled: boolean;
+ type: 'email' | 'slack' | 'discord' | 'gotify' | 'ntfy' | 'pushover' | 'telegram' | 'generic' | 'custom';
config: {
- apiToken: string;
- priority: -1 | 0 | 1;
- type: 'pushover';
- userKey: string;
- devices?: string;
- } | {
- botToken: string;
- chatId: string;
- type: 'telegram';
- threadId?: string;
- } | {
- from: string;
+ type: 'email';
smtpHost: string;
smtpPort: number;
- to: Array;
- type: 'email';
- useTLS: boolean;
+ username?: string;
+ password?: string;
+ from: string;
fromName?: string;
- password?: string;
- username?: string;
- } | {
- method: 'GET' | 'POST';
- type: 'generic';
- url: string;
- contentType?: string;
- headers?: Array;
- messageKey?: string;
- titleKey?: string;
- useJson?: boolean;
- } | {
- priority: 'default' | 'high' | 'low' | 'max' | 'min';
- topic: string;
- type: 'ntfy';
- accessToken?: string;
- password?: string;
- serverUrl?: string;
- username?: string;
- } | {
- priority: number;
- serverUrl: string;
- token: string;
- type: 'gotify';
- path?: string;
- } | {
- shoutrrrUrl: string;
- type: 'custom';
- } | {
- type: 'discord';
- webhookUrl: string;
- avatarUrl?: string;
- threadId?: string;
- username?: string;
+ to: Array;
+ useTLS: boolean;
} | {
type: 'slack';
webhookUrl: string;
channel?: string;
- iconEmoji?: string;
username?: string;
+ iconEmoji?: string;
+ } | {
+ type: 'discord';
+ webhookUrl: string;
+ username?: string;
+ avatarUrl?: string;
+ threadId?: string;
+ } | {
+ type: 'gotify';
+ serverUrl: string;
+ token: string;
+ path?: string;
+ priority: number;
+ } | {
+ type: 'ntfy';
+ serverUrl?: string;
+ topic: string;
+ priority: 'max' | 'high' | 'default' | 'low' | 'min';
+ username?: string;
+ password?: string;
+ accessToken?: string;
+ } | {
+ type: 'pushover';
+ userKey: string;
+ apiToken: string;
+ devices?: string;
+ priority: -1 | 0 | 1;
+ } | {
+ type: 'telegram';
+ botToken: string;
+ chatId: string;
+ threadId?: string;
+ } | {
+ type: 'generic';
+ url: string;
+ method: 'GET' | 'POST';
+ contentType?: string;
+ headers?: Array;
+ useJson?: boolean;
+ titleKey?: string;
+ messageKey?: string;
+ } | {
+ type: 'custom';
+ shoutrrrUrl: string;
};
createdAt: number;
- enabled: boolean;
- id: number;
- name: string;
- type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram';
updatedAt: number;
};
- destinationId: number;
- notifyOnFailure: boolean;
- notifyOnStart: boolean;
- notifyOnSuccess: boolean;
- notifyOnWarning: boolean;
- scheduleId: number;
}>;
};
@@ -3565,10 +3565,10 @@ export type UpdateScheduleNotificationsData = {
body: {
assignments: Array<{
destinationId: number;
- notifyOnFailure: boolean;
notifyOnStart: boolean;
notifyOnSuccess: boolean;
notifyOnWarning: boolean;
+ notifyOnFailure: boolean;
}>;
};
path: {
@@ -3583,81 +3583,81 @@ export type UpdateScheduleNotificationsResponses = {
* Notification assignments updated successfully
*/
200: Array<{
+ scheduleId: number;
+ destinationId: number;
+ notifyOnStart: boolean;
+ notifyOnSuccess: boolean;
+ notifyOnWarning: boolean;
+ notifyOnFailure: boolean;
createdAt: number;
destination: {
+ id: number;
+ name: string;
+ enabled: boolean;
+ type: 'email' | 'slack' | 'discord' | 'gotify' | 'ntfy' | 'pushover' | 'telegram' | 'generic' | 'custom';
config: {
- apiToken: string;
- priority: -1 | 0 | 1;
- type: 'pushover';
- userKey: string;
- devices?: string;
- } | {
- botToken: string;
- chatId: string;
- type: 'telegram';
- threadId?: string;
- } | {
- from: string;
+ type: 'email';
smtpHost: string;
smtpPort: number;
- to: Array;
- type: 'email';
- useTLS: boolean;
+ username?: string;
+ password?: string;
+ from: string;
fromName?: string;
- password?: string;
- username?: string;
- } | {
- method: 'GET' | 'POST';
- type: 'generic';
- url: string;
- contentType?: string;
- headers?: Array;
- messageKey?: string;
- titleKey?: string;
- useJson?: boolean;
- } | {
- priority: 'default' | 'high' | 'low' | 'max' | 'min';
- topic: string;
- type: 'ntfy';
- accessToken?: string;
- password?: string;
- serverUrl?: string;
- username?: string;
- } | {
- priority: number;
- serverUrl: string;
- token: string;
- type: 'gotify';
- path?: string;
- } | {
- shoutrrrUrl: string;
- type: 'custom';
- } | {
- type: 'discord';
- webhookUrl: string;
- avatarUrl?: string;
- threadId?: string;
- username?: string;
+ to: Array;
+ useTLS: boolean;
} | {
type: 'slack';
webhookUrl: string;
channel?: string;
- iconEmoji?: string;
username?: string;
+ iconEmoji?: string;
+ } | {
+ type: 'discord';
+ webhookUrl: string;
+ username?: string;
+ avatarUrl?: string;
+ threadId?: string;
+ } | {
+ type: 'gotify';
+ serverUrl: string;
+ token: string;
+ path?: string;
+ priority: number;
+ } | {
+ type: 'ntfy';
+ serverUrl?: string;
+ topic: string;
+ priority: 'max' | 'high' | 'default' | 'low' | 'min';
+ username?: string;
+ password?: string;
+ accessToken?: string;
+ } | {
+ type: 'pushover';
+ userKey: string;
+ apiToken: string;
+ devices?: string;
+ priority: -1 | 0 | 1;
+ } | {
+ type: 'telegram';
+ botToken: string;
+ chatId: string;
+ threadId?: string;
+ } | {
+ type: 'generic';
+ url: string;
+ method: 'GET' | 'POST';
+ contentType?: string;
+ headers?: Array;
+ useJson?: boolean;
+ titleKey?: string;
+ messageKey?: string;
+ } | {
+ type: 'custom';
+ shoutrrrUrl: string;
};
createdAt: number;
- enabled: boolean;
- id: number;
- name: string;
- type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram';
updatedAt: number;
};
- destinationId: number;
- notifyOnFailure: boolean;
- notifyOnStart: boolean;
- notifyOnSuccess: boolean;
- notifyOnWarning: boolean;
- scheduleId: number;
}>;
};
@@ -3677,193 +3677,193 @@ export type GetScheduleMirrorsResponses = {
* List of mirror repository assignments for the schedule
*/
200: Array<{
- createdAt: number;
+ scheduleId: string;
+ repositoryId: string;
enabled: boolean;
lastCopyAt: number | null;
+ lastCopyStatus: 'success' | 'error' | 'in_progress' | null;
lastCopyError: string | null;
- lastCopyStatus: 'error' | 'in_progress' | 'success' | null;
+ createdAt: number;
repository: {
- compressionMode: 'auto' | 'max' | 'off' | null;
+ id: string;
+ shortId: string;
+ name: string;
+ type: 'local' | 's3' | 'r2' | 'gcs' | 'azure' | 'rclone' | 'rest' | 'sftp';
config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
backend: 's3';
- bucket: string;
endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ accessKeyId: string;
+ secretAccessKey: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'local';
path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rclone';
- path: string;
remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ path: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rest';
url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
+ username?: string;
password?: string;
path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
- username?: string;
} | {
backend: 'sftp';
host: string;
+ port?: string | number;
+ user: string;
path: string;
privateKey: string;
- user: string;
- port?: number;
skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
};
- createdAt: number;
- doctorResult: {
- completedAt: number;
- steps: Array<{
- error: string | null;
- output: string | null;
- step: string;
- success: boolean;
- }>;
- success: boolean;
- } | null;
- id: string;
+ compressionMode: 'off' | 'auto' | 'max' | null;
+ status: 'healthy' | 'error' | 'unknown' | 'doctor' | 'cancelled' | null;
lastChecked: number | null;
lastError: string | null;
- name: string;
- shortId: string;
- status: 'cancelled' | 'doctor' | 'error' | 'healthy' | 'unknown' | null;
- type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
+ doctorResult: {
+ success: boolean;
+ steps: Array<{
+ step: string;
+ success: boolean;
+ output: string | null;
+ error: string | null;
+ }>;
+ completedAt: number;
+ } | null;
+ createdAt: number;
updatedAt: number;
};
- repositoryId: string;
- scheduleId: string;
}>;
};
@@ -3872,8 +3872,8 @@ export type GetScheduleMirrorsResponse = GetScheduleMirrorsResponses[keyof GetSc
export type UpdateScheduleMirrorsData = {
body: {
mirrors: Array<{
- enabled: boolean;
repositoryId: string;
+ enabled: boolean;
}>;
};
path: {
@@ -3888,193 +3888,193 @@ export type UpdateScheduleMirrorsResponses = {
* Mirror assignments updated successfully
*/
200: Array<{
- createdAt: number;
+ scheduleId: string;
+ repositoryId: string;
enabled: boolean;
lastCopyAt: number | null;
+ lastCopyStatus: 'success' | 'error' | 'in_progress' | null;
lastCopyError: string | null;
- lastCopyStatus: 'error' | 'in_progress' | 'success' | null;
+ createdAt: number;
repository: {
- compressionMode: 'auto' | 'max' | 'off' | null;
+ id: string;
+ shortId: string;
+ name: string;
+ type: 'local' | 's3' | 'r2' | 'gcs' | 'azure' | 'rclone' | 'rest' | 'sftp';
config: {
- accessKeyId: string;
- backend: 'r2';
- bucket: string;
- endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accessKeyId: string;
backend: 's3';
- bucket: string;
endpoint: string;
- secretAccessKey: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- accountKey: string;
- accountName: string;
- backend: 'azure';
- container: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- endpointSuffix?: string;
- insecureTls?: boolean;
- isExistingRepository?: boolean;
- uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- } | {
- backend: 'gcs';
bucket: string;
- credentialsJson: string;
- projectId: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ accessKeyId: string;
+ secretAccessKey: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'r2';
+ endpoint: string;
+ bucket: string;
+ accessKeyId: string;
+ secretAccessKey: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'local';
path: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'gcs';
+ bucket: string;
+ projectId: string;
+ credentialsJson: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ } | {
+ backend: 'azure';
+ container: string;
+ accountName: string;
+ accountKey: string;
+ endpointSuffix?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
+ uploadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rclone';
- path: string;
remote: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
+ path: string;
isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
} | {
backend: 'rest';
url: string;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
+ username?: string;
password?: string;
path?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
- username?: string;
} | {
backend: 'sftp';
host: string;
+ port?: string | number;
+ user: string;
path: string;
privateKey: string;
- user: string;
- port?: number;
skipHostKeyCheck?: boolean;
- cacert?: string;
- customPassword?: string;
- downloadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
- enabled?: boolean;
- };
- insecureTls?: boolean;
- isExistingRepository?: boolean;
knownHosts?: string;
+ isExistingRepository?: boolean;
+ customPassword?: string;
+ cacert?: string;
+ insecureTls?: boolean;
uploadLimit?: {
- unit?: 'Gbps' | 'Kbps' | 'Mbps';
- value?: number;
enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
+ };
+ downloadLimit?: {
+ enabled?: boolean;
+ value?: number;
+ unit?: 'Kbps' | 'Mbps' | 'Gbps';
};
};
- createdAt: number;
- doctorResult: {
- completedAt: number;
- steps: Array<{
- error: string | null;
- output: string | null;
- step: string;
- success: boolean;
- }>;
- success: boolean;
- } | null;
- id: string;
+ compressionMode: 'off' | 'auto' | 'max' | null;
+ status: 'healthy' | 'error' | 'unknown' | 'doctor' | 'cancelled' | null;
lastChecked: number | null;
lastError: string | null;
- name: string;
- shortId: string;
- status: 'cancelled' | 'doctor' | 'error' | 'healthy' | 'unknown' | null;
- type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp';
+ doctorResult: {
+ success: boolean;
+ steps: Array<{
+ step: string;
+ success: boolean;
+ output: string | null;
+ error: string | null;
+ }>;
+ completedAt: number;
+ } | null;
+ createdAt: number;
updatedAt: number;
};
- repositoryId: string;
- scheduleId: string;
}>;
};
@@ -4094,9 +4094,9 @@ export type GetMirrorCompatibilityResponses = {
* List of repositories with their mirror compatibility status
*/
200: Array<{
+ repositoryId: string;
compatible: boolean;
reason: string | null;
- repositoryId: string;
}>;
};
@@ -4136,16 +4136,15 @@ export type GetBackupProgressResponses = {
* Current backup progress or null if not yet available
*/
200: {
- bytes_done: number;
- files_done: number;
- percent_done: number;
- repositoryName: string;
scheduleId: string;
- seconds_elapsed: number;
- seconds_remaining: number;
- total_bytes: number;
- total_files: number;
volumeName: string;
+ repositoryName: string;
+ seconds_elapsed: number;
+ percent_done: number;
+ total_files: number;
+ files_done: number;
+ total_bytes: number;
+ bytes_done: number;
current_files?: Array;
} | null;
};
@@ -4164,71 +4163,71 @@ export type ListNotificationDestinationsResponses = {
* A list of notification destinations
*/
200: Array<{
+ id: number;
+ name: string;
+ enabled: boolean;
+ type: 'email' | 'slack' | 'discord' | 'gotify' | 'ntfy' | 'pushover' | 'telegram' | 'generic' | 'custom';
config: {
- apiToken: string;
- priority: -1 | 0 | 1;
- type: 'pushover';
- userKey: string;
- devices?: string;
- } | {
- botToken: string;
- chatId: string;
- type: 'telegram';
- threadId?: string;
- } | {
- from: string;
+ type: 'email';
smtpHost: string;
smtpPort: number;
- to: Array;
- type: 'email';
- useTLS: boolean;
+ username?: string;
+ password?: string;
+ from: string;
fromName?: string;
- password?: string;
- username?: string;
- } | {
- method: 'GET' | 'POST';
- type: 'generic';
- url: string;
- contentType?: string;
- headers?: Array;
- messageKey?: string;
- titleKey?: string;
- useJson?: boolean;
- } | {
- priority: 'default' | 'high' | 'low' | 'max' | 'min';
- topic: string;
- type: 'ntfy';
- accessToken?: string;
- password?: string;
- serverUrl?: string;
- username?: string;
- } | {
- priority: number;
- serverUrl: string;
- token: string;
- type: 'gotify';
- path?: string;
- } | {
- shoutrrrUrl: string;
- type: 'custom';
- } | {
- type: 'discord';
- webhookUrl: string;
- avatarUrl?: string;
- threadId?: string;
- username?: string;
+ to: Array;
+ useTLS: boolean;
} | {
type: 'slack';
webhookUrl: string;
channel?: string;
- iconEmoji?: string;
username?: string;
+ iconEmoji?: string;
+ } | {
+ type: 'discord';
+ webhookUrl: string;
+ username?: string;
+ avatarUrl?: string;
+ threadId?: string;
+ } | {
+ type: 'gotify';
+ serverUrl: string;
+ token: string;
+ path?: string;
+ priority: number;
+ } | {
+ type: 'ntfy';
+ serverUrl?: string;
+ topic: string;
+ priority: 'max' | 'high' | 'default' | 'low' | 'min';
+ username?: string;
+ password?: string;
+ accessToken?: string;
+ } | {
+ type: 'pushover';
+ userKey: string;
+ apiToken: string;
+ devices?: string;
+ priority: -1 | 0 | 1;
+ } | {
+ type: 'telegram';
+ botToken: string;
+ chatId: string;
+ threadId?: string;
+ } | {
+ type: 'generic';
+ url: string;
+ method: 'GET' | 'POST';
+ contentType?: string;
+ headers?: Array;
+ useJson?: boolean;
+ titleKey?: string;
+ messageKey?: string;
+ } | {
+ type: 'custom';
+ shoutrrrUrl: string;
};
createdAt: number;
- enabled: boolean;
- id: number;
- name: string;
- type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram';
updatedAt: number;
}>;
};
@@ -4237,67 +4236,67 @@ export type ListNotificationDestinationsResponse = ListNotificationDestinationsR
export type CreateNotificationDestinationData = {
body: {
+ name: string;
config: {
- apiToken: string;
- priority: -1 | 0 | 1;
- type: 'pushover';
- userKey: string;
- devices?: string;
- } | {
- botToken: string;
- chatId: string;
- type: 'telegram';
- threadId?: string;
- } | {
- from: string;
+ type: 'email';
smtpHost: string;
smtpPort: number;
- to: Array;
- type: 'email';
- useTLS: boolean;
+ username?: string;
+ password?: string;
+ from: string;
fromName?: string;
- password?: string;
- username?: string;
- } | {
- method: 'GET' | 'POST';
- type: 'generic';
- url: string;
- contentType?: string;
- headers?: Array;
- messageKey?: string;
- titleKey?: string;
- useJson?: boolean;
- } | {
- priority: 'default' | 'high' | 'low' | 'max' | 'min';
- topic: string;
- type: 'ntfy';
- accessToken?: string;
- password?: string;
- serverUrl?: string;
- username?: string;
- } | {
- priority: number;
- serverUrl: string;
- token: string;
- type: 'gotify';
- path?: string;
- } | {
- shoutrrrUrl: string;
- type: 'custom';
- } | {
- type: 'discord';
- webhookUrl: string;
- avatarUrl?: string;
- threadId?: string;
- username?: string;
+ to: Array;
+ useTLS: boolean;
} | {
type: 'slack';
webhookUrl: string;
channel?: string;
- iconEmoji?: string;
username?: string;
+ iconEmoji?: string;
+ } | {
+ type: 'discord';
+ webhookUrl: string;
+ username?: string;
+ avatarUrl?: string;
+ threadId?: string;
+ } | {
+ type: 'gotify';
+ serverUrl: string;
+ token: string;
+ path?: string;
+ priority: number;
+ } | {
+ type: 'ntfy';
+ serverUrl?: string;
+ topic: string;
+ priority: 'max' | 'high' | 'default' | 'low' | 'min';
+ username?: string;
+ password?: string;
+ accessToken?: string;
+ } | {
+ type: 'pushover';
+ userKey: string;
+ apiToken: string;
+ devices?: string;
+ priority: -1 | 0 | 1;
+ } | {
+ type: 'telegram';
+ botToken: string;
+ chatId: string;
+ threadId?: string;
+ } | {
+ type: 'generic';
+ url: string;
+ method: 'GET' | 'POST';
+ contentType?: string;
+ headers?: Array;
+ useJson?: boolean;
+ titleKey?: string;
+ messageKey?: string;
+ } | {
+ type: 'custom';
+ shoutrrrUrl: string;
};
- name: string;
};
path?: never;
query?: never;
@@ -4309,71 +4308,71 @@ export type CreateNotificationDestinationResponses = {
* Notification destination created successfully
*/
201: {
+ id: number;
+ name: string;
+ enabled: boolean;
+ type: 'email' | 'slack' | 'discord' | 'gotify' | 'ntfy' | 'pushover' | 'telegram' | 'generic' | 'custom';
config: {
- apiToken: string;
- priority: -1 | 0 | 1;
- type: 'pushover';
- userKey: string;
- devices?: string;
- } | {
- botToken: string;
- chatId: string;
- type: 'telegram';
- threadId?: string;
- } | {
- from: string;
+ type: 'email';
smtpHost: string;
smtpPort: number;
- to: Array;
- type: 'email';
- useTLS: boolean;
+ username?: string;
+ password?: string;
+ from: string;
fromName?: string;
- password?: string;
- username?: string;
- } | {
- method: 'GET' | 'POST';
- type: 'generic';
- url: string;
- contentType?: string;
- headers?: Array;
- messageKey?: string;
- titleKey?: string;
- useJson?: boolean;
- } | {
- priority: 'default' | 'high' | 'low' | 'max' | 'min';
- topic: string;
- type: 'ntfy';
- accessToken?: string;
- password?: string;
- serverUrl?: string;
- username?: string;
- } | {
- priority: number;
- serverUrl: string;
- token: string;
- type: 'gotify';
- path?: string;
- } | {
- shoutrrrUrl: string;
- type: 'custom';
- } | {
- type: 'discord';
- webhookUrl: string;
- avatarUrl?: string;
- threadId?: string;
- username?: string;
+ to: Array;
+ useTLS: boolean;
} | {
type: 'slack';
webhookUrl: string;
channel?: string;
- iconEmoji?: string;
username?: string;
+ iconEmoji?: string;
+ } | {
+ type: 'discord';
+ webhookUrl: string;
+ username?: string;
+ avatarUrl?: string;
+ threadId?: string;
+ } | {
+ type: 'gotify';
+ serverUrl: string;
+ token: string;
+ path?: string;
+ priority: number;
+ } | {
+ type: 'ntfy';
+ serverUrl?: string;
+ topic: string;
+ priority: 'max' | 'high' | 'default' | 'low' | 'min';
+ username?: string;
+ password?: string;
+ accessToken?: string;
+ } | {
+ type: 'pushover';
+ userKey: string;
+ apiToken: string;
+ devices?: string;
+ priority: -1 | 0 | 1;
+ } | {
+ type: 'telegram';
+ botToken: string;
+ chatId: string;
+ threadId?: string;
+ } | {
+ type: 'generic';
+ url: string;
+ method: 'GET' | 'POST';
+ contentType?: string;
+ headers?: Array;
+ useJson?: boolean;
+ titleKey?: string;
+ messageKey?: string;
+ } | {
+ type: 'custom';
+ shoutrrrUrl: string;
};
createdAt: number;
- enabled: boolean;
- id: number;
- name: string;
- type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram';
updatedAt: number;
};
};
@@ -4428,71 +4427,71 @@ export type GetNotificationDestinationResponses = {
* Notification destination details
*/
200: {
+ id: number;
+ name: string;
+ enabled: boolean;
+ type: 'email' | 'slack' | 'discord' | 'gotify' | 'ntfy' | 'pushover' | 'telegram' | 'generic' | 'custom';
config: {
- apiToken: string;
- priority: -1 | 0 | 1;
- type: 'pushover';
- userKey: string;
- devices?: string;
- } | {
- botToken: string;
- chatId: string;
- type: 'telegram';
- threadId?: string;
- } | {
- from: string;
+ type: 'email';
smtpHost: string;
smtpPort: number;
- to: Array;
- type: 'email';
- useTLS: boolean;
+ username?: string;
+ password?: string;
+ from: string;
fromName?: string;
- password?: string;
- username?: string;
- } | {
- method: 'GET' | 'POST';
- type: 'generic';
- url: string;
- contentType?: string;
- headers?: Array;
- messageKey?: string;
- titleKey?: string;
- useJson?: boolean;
- } | {
- priority: 'default' | 'high' | 'low' | 'max' | 'min';
- topic: string;
- type: 'ntfy';
- accessToken?: string;
- password?: string;
- serverUrl?: string;
- username?: string;
- } | {
- priority: number;
- serverUrl: string;
- token: string;
- type: 'gotify';
- path?: string;
- } | {
- shoutrrrUrl: string;
- type: 'custom';
- } | {
- type: 'discord';
- webhookUrl: string;
- avatarUrl?: string;
- threadId?: string;
- username?: string;
+ to: Array;
+ useTLS: boolean;
} | {
type: 'slack';
webhookUrl: string;
channel?: string;
- iconEmoji?: string;
username?: string;
+ iconEmoji?: string;
+ } | {
+ type: 'discord';
+ webhookUrl: string;
+ username?: string;
+ avatarUrl?: string;
+ threadId?: string;
+ } | {
+ type: 'gotify';
+ serverUrl: string;
+ token: string;
+ path?: string;
+ priority: number;
+ } | {
+ type: 'ntfy';
+ serverUrl?: string;
+ topic: string;
+ priority: 'max' | 'high' | 'default' | 'low' | 'min';
+ username?: string;
+ password?: string;
+ accessToken?: string;
+ } | {
+ type: 'pushover';
+ userKey: string;
+ apiToken: string;
+ devices?: string;
+ priority: -1 | 0 | 1;
+ } | {
+ type: 'telegram';
+ botToken: string;
+ chatId: string;
+ threadId?: string;
+ } | {
+ type: 'generic';
+ url: string;
+ method: 'GET' | 'POST';
+ contentType?: string;
+ headers?: Array;
+ useJson?: boolean;
+ titleKey?: string;
+ messageKey?: string;
+ } | {
+ type: 'custom';
+ shoutrrrUrl: string;
};
createdAt: number;
- enabled: boolean;
- id: number;
- name: string;
- type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram';
updatedAt: number;
};
};
@@ -4501,68 +4500,68 @@ export type GetNotificationDestinationResponse = GetNotificationDestinationRespo
export type UpdateNotificationDestinationData = {
body: {
+ name?: string;
+ enabled?: boolean;
config?: {
- apiToken: string;
- priority: -1 | 0 | 1;
- type: 'pushover';
- userKey: string;
- devices?: string;
- } | {
- botToken: string;
- chatId: string;
- type: 'telegram';
- threadId?: string;
- } | {
- from: string;
+ type: 'email';
smtpHost: string;
smtpPort: number;
- to: Array;
- type: 'email';
- useTLS: boolean;
+ username?: string;
+ password?: string;
+ from: string;
fromName?: string;
- password?: string;
- username?: string;
- } | {
- method: 'GET' | 'POST';
- type: 'generic';
- url: string;
- contentType?: string;
- headers?: Array;
- messageKey?: string;
- titleKey?: string;
- useJson?: boolean;
- } | {
- priority: 'default' | 'high' | 'low' | 'max' | 'min';
- topic: string;
- type: 'ntfy';
- accessToken?: string;
- password?: string;
- serverUrl?: string;
- username?: string;
- } | {
- priority: number;
- serverUrl: string;
- token: string;
- type: 'gotify';
- path?: string;
- } | {
- shoutrrrUrl: string;
- type: 'custom';
- } | {
- type: 'discord';
- webhookUrl: string;
- avatarUrl?: string;
- threadId?: string;
- username?: string;
+ to: Array;
+ useTLS: boolean;
} | {
type: 'slack';
webhookUrl: string;
channel?: string;
- iconEmoji?: string;
username?: string;
+ iconEmoji?: string;
+ } | {
+ type: 'discord';
+ webhookUrl: string;
+ username?: string;
+ avatarUrl?: string;
+ threadId?: string;
+ } | {
+ type: 'gotify';
+ serverUrl: string;
+ token: string;
+ path?: string;
+ priority: number;
+ } | {
+ type: 'ntfy';
+ serverUrl?: string;
+ topic: string;
+ priority: 'max' | 'high' | 'default' | 'low' | 'min';
+ username?: string;
+ password?: string;
+ accessToken?: string;
+ } | {
+ type: 'pushover';
+ userKey: string;
+ apiToken: string;
+ devices?: string;
+ priority: -1 | 0 | 1;
+ } | {
+ type: 'telegram';
+ botToken: string;
+ chatId: string;
+ threadId?: string;
+ } | {
+ type: 'generic';
+ url: string;
+ method: 'GET' | 'POST';
+ contentType?: string;
+ headers?: Array;
+ useJson?: boolean;
+ titleKey?: string;
+ messageKey?: string;
+ } | {
+ type: 'custom';
+ shoutrrrUrl: string;
};
- enabled?: boolean;
- name?: string;
};
path: {
id: string;
@@ -4583,71 +4582,71 @@ export type UpdateNotificationDestinationResponses = {
* Notification destination updated successfully
*/
200: {
+ id: number;
+ name: string;
+ enabled: boolean;
+ type: 'email' | 'slack' | 'discord' | 'gotify' | 'ntfy' | 'pushover' | 'telegram' | 'generic' | 'custom';
config: {
- apiToken: string;
- priority: -1 | 0 | 1;
- type: 'pushover';
- userKey: string;
- devices?: string;
- } | {
- botToken: string;
- chatId: string;
- type: 'telegram';
- threadId?: string;
- } | {
- from: string;
+ type: 'email';
smtpHost: string;
smtpPort: number;
- to: Array;
- type: 'email';
- useTLS: boolean;
+ username?: string;
+ password?: string;
+ from: string;
fromName?: string;
- password?: string;
- username?: string;
- } | {
- method: 'GET' | 'POST';
- type: 'generic';
- url: string;
- contentType?: string;
- headers?: Array;
- messageKey?: string;
- titleKey?: string;
- useJson?: boolean;
- } | {
- priority: 'default' | 'high' | 'low' | 'max' | 'min';
- topic: string;
- type: 'ntfy';
- accessToken?: string;
- password?: string;
- serverUrl?: string;
- username?: string;
- } | {
- priority: number;
- serverUrl: string;
- token: string;
- type: 'gotify';
- path?: string;
- } | {
- shoutrrrUrl: string;
- type: 'custom';
- } | {
- type: 'discord';
- webhookUrl: string;
- avatarUrl?: string;
- threadId?: string;
- username?: string;
+ to: Array;
+ useTLS: boolean;
} | {
type: 'slack';
webhookUrl: string;
channel?: string;
- iconEmoji?: string;
username?: string;
+ iconEmoji?: string;
+ } | {
+ type: 'discord';
+ webhookUrl: string;
+ username?: string;
+ avatarUrl?: string;
+ threadId?: string;
+ } | {
+ type: 'gotify';
+ serverUrl: string;
+ token: string;
+ path?: string;
+ priority: number;
+ } | {
+ type: 'ntfy';
+ serverUrl?: string;
+ topic: string;
+ priority: 'max' | 'high' | 'default' | 'low' | 'min';
+ username?: string;
+ password?: string;
+ accessToken?: string;
+ } | {
+ type: 'pushover';
+ userKey: string;
+ apiToken: string;
+ devices?: string;
+ priority: -1 | 0 | 1;
+ } | {
+ type: 'telegram';
+ botToken: string;
+ chatId: string;
+ threadId?: string;
+ } | {
+ type: 'generic';
+ url: string;
+ method: 'GET' | 'POST';
+ contentType?: string;
+ headers?: Array;
+ useJson?: boolean;
+ titleKey?: string;
+ messageKey?: string;
+ } | {
+ type: 'custom';
+ shoutrrrUrl: string;
};
createdAt: number;
- enabled: boolean;
- id: number;
- name: string;
- type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram';
updatedAt: number;
};
};
@@ -4723,13 +4722,13 @@ export type GetUpdatesResponses = {
*/
200: {
currentVersion: string;
- hasUpdate: boolean;
latestVersion: string;
+ hasUpdate: boolean;
missedReleases: Array<{
- body: string;
- publishedAt: string;
- url: string;
version: string;
+ url: string;
+ publishedAt: string;
+ body: string;
}>;
};
};
diff --git a/app/client/components/file-browsers/snapshot-tree-browser.tsx b/app/client/components/file-browsers/snapshot-tree-browser.tsx
index f9983dfd..d660c4c2 100644
--- a/app/client/components/file-browsers/snapshot-tree-browser.tsx
+++ b/app/client/components/file-browsers/snapshot-tree-browser.tsx
@@ -74,11 +74,7 @@ export const SnapshotTreeBrowser = ({
return await queryClient.ensureQueryData(
listSnapshotFilesOptions({
path: { shortId: repositoryId, snapshotId },
- query: {
- path,
- offset: offset.toString(),
- limit: pageSize.toString(),
- },
+ query: { path, offset: offset, limit: pageSize },
}),
);
},
@@ -86,11 +82,7 @@ export const SnapshotTreeBrowser = ({
void queryClient.prefetchQuery(
listSnapshotFilesOptions({
path: { shortId: repositoryId, snapshotId },
- query: {
- path,
- offset: "0",
- limit: pageSize.toString(),
- },
+ query: { path, offset: 0, limit: pageSize },
}),
);
},
diff --git a/app/client/components/file-browsers/volume-file-browser.tsx b/app/client/components/file-browsers/volume-file-browser.tsx
index 2b39d03f..902b191a 100644
--- a/app/client/components/file-browsers/volume-file-browser.tsx
+++ b/app/client/components/file-browsers/volume-file-browser.tsx
@@ -24,7 +24,7 @@ export const VolumeFileBrowser = ({ volumeId, enabled = true, ...uiProps }: Volu
return await queryClient.ensureQueryData(
listFilesOptions({
path: { shortId: volumeId },
- query: { path, offset: offset?.toString() },
+ query: { path, offset: offset },
}),
);
},
diff --git a/app/client/modules/auth/routes/login.tsx b/app/client/modules/auth/routes/login.tsx
index 83317925..58663c1b 100644
--- a/app/client/modules/auth/routes/login.tsx
+++ b/app/client/modules/auth/routes/login.tsx
@@ -1,5 +1,4 @@
-import { arktypeResolver } from "@hookform/resolvers/arktype";
-import { type } from "arktype";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -16,13 +15,17 @@ import { useNavigate } from "@tanstack/react-router";
import { normalizeUsername } from "~/lib/username";
import { cn } from "~/client/lib/utils";
import { SsoLoginSection } from "~/client/modules/sso/components/sso-login-section";
+import { z } from "zod";
-const loginSchema = type({
- username: "2<=string<=50",
- password: "string>=1",
+const loginSchema = z.object({
+ username: z
+ .string()
+ .min(2, "Username must be at least 2 characters")
+ .max(50, "Username must be at most 50 characters"),
+ password: z.string().min(1, "Password is required"),
});
-type LoginFormValues = typeof loginSchema.inferIn;
+type LoginFormValues = z.input;
type LoginPageProps = {
error?: string;
@@ -40,7 +43,7 @@ export function LoginPage({ error }: LoginPageProps = {}) {
const errorDescription = errorCode ? getLoginErrorDescription(errorCode) : null;
const form = useForm({
- resolver: arktypeResolver(loginSchema),
+ resolver: zodResolver(loginSchema),
defaultValues: {
username: "",
password: "",
diff --git a/app/client/modules/auth/routes/onboarding.tsx b/app/client/modules/auth/routes/onboarding.tsx
index 8eddace4..e477837e 100644
--- a/app/client/modules/auth/routes/onboarding.tsx
+++ b/app/client/modules/auth/routes/onboarding.tsx
@@ -1,5 +1,4 @@
-import { arktypeResolver } from "@hookform/resolvers/arktype";
-import { type } from "arktype";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import {
@@ -19,24 +18,28 @@ import { authClient } from "~/client/lib/auth-client";
import { useState } from "react";
import { useNavigate } from "@tanstack/react-router";
import { normalizeUsername } from "~/lib/username";
+import { z } from "zod";
export const clientMiddleware = [authMiddleware];
-const onboardingSchema = type({
- username: type("2<=string<=30").pipe(normalizeUsername),
- email: type("string.email").pipe((str) => str.trim().toLowerCase()),
- password: "string>=8",
- confirmPassword: "string>=1",
+const onboardingSchema = z.object({
+ username: z.string().min(2).max(30).transform(normalizeUsername),
+ email: z
+ .string()
+ .email()
+ .transform((str) => str.trim().toLowerCase()),
+ password: z.string().min(8),
+ confirmPassword: z.string().min(1),
});
-type OnboardingFormValues = typeof onboardingSchema.inferIn;
+type OnboardingFormValues = z.input;
export function OnboardingPage() {
const navigate = useNavigate();
const [submitting, setSubmitting] = useState(false);
const form = useForm({
- resolver: arktypeResolver(onboardingSchema),
+ resolver: zodResolver(onboardingSchema),
defaultValues: {
username: "",
password: "",
diff --git a/app/client/modules/backups/components/create-schedule-form/index.tsx b/app/client/modules/backups/components/create-schedule-form/index.tsx
index 242b4d4d..d5089233 100644
--- a/app/client/modules/backups/components/create-schedule-form/index.tsx
+++ b/app/client/modules/backups/components/create-schedule-form/index.tsx
@@ -1,4 +1,4 @@
-import { arktypeResolver } from "@hookform/resolvers/arktype";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useCallback, useState } from "react";
import { useForm } from "react-hook-form";
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
@@ -13,7 +13,7 @@ import { FrequencySection } from "./frequency-section";
import { PathsSection } from "./paths-section";
import { RetentionSection } from "./retention-section";
import { SummarySection } from "./summary-section";
-import { cleanSchema, type BackupScheduleFormValues, type InternalFormValues } from "./types";
+import { internalFormSchema, type BackupScheduleFormValues, type InternalFormValues } from "./types";
import { backupScheduleToFormValues } from "./utils";
export type { BackupScheduleFormValues };
@@ -29,7 +29,7 @@ type Props = {
export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: Props) => {
const form = useForm({
- resolver: arktypeResolver(cleanSchema as unknown as typeof import("./types").internalFormSchema),
+ resolver: zodResolver(internalFormSchema),
defaultValues: backupScheduleToFormValues(initialValues),
});
diff --git a/app/client/modules/backups/components/create-schedule-form/retention-section.tsx b/app/client/modules/backups/components/create-schedule-form/retention-section.tsx
index c28de050..ccad9aad 100644
--- a/app/client/modules/backups/components/create-schedule-form/retention-section.tsx
+++ b/app/client/modules/backups/components/create-schedule-form/retention-section.tsx
@@ -22,7 +22,7 @@ export const RetentionSection = ({ form }: RetentionSectionProps) => {
type="number"
min={0}
placeholder="Optional"
- onChange={(v) => field.onChange(Number(v.target.value))}
+ onChange={(v) => field.onChange(v.target.value === "" ? undefined : Number(v.target.value))}
/>
Keep the N most recent snapshots.
@@ -43,7 +43,7 @@ export const RetentionSection = ({ form }: RetentionSectionProps) => {
min={0}
placeholder="Optional"
{...field}
- onChange={(v) => field.onChange(Number(v.target.value))}
+ onChange={(v) => field.onChange(v.target.value === "" ? undefined : Number(v.target.value))}
/>
Keep the last N hourly snapshots.
@@ -64,7 +64,7 @@ export const RetentionSection = ({ form }: RetentionSectionProps) => {
min={0}
placeholder="e.g., 7"
{...field}
- onChange={(v) => field.onChange(Number(v.target.value))}
+ onChange={(v) => field.onChange(v.target.value === "" ? undefined : Number(v.target.value))}
/>
Keep the last N daily snapshots.
@@ -85,7 +85,7 @@ export const RetentionSection = ({ form }: RetentionSectionProps) => {
min={0}
placeholder="e.g., 4"
{...field}
- onChange={(v) => field.onChange(Number(v.target.value))}
+ onChange={(v) => field.onChange(v.target.value === "" ? undefined : Number(v.target.value))}
/>
Keep the last N weekly snapshots.
@@ -106,7 +106,7 @@ export const RetentionSection = ({ form }: RetentionSectionProps) => {
min={0}
placeholder="e.g., 6"
{...field}
- onChange={(v) => field.onChange(Number(v.target.value))}
+ onChange={(v) => field.onChange(v.target.value === "" ? undefined : Number(v.target.value))}
/>
Keep the last N monthly snapshots.
@@ -127,7 +127,7 @@ export const RetentionSection = ({ form }: RetentionSectionProps) => {
min={0}
placeholder="Optional"
{...field}
- onChange={(v) => field.onChange(Number(v.target.value))}
+ onChange={(v) => field.onChange(v.target.value === "" ? undefined : Number(v.target.value))}
/>
Keep the last N yearly snapshots.
diff --git a/app/client/modules/backups/components/create-schedule-form/types.ts b/app/client/modules/backups/components/create-schedule-form/types.ts
index 3467efb2..bad646ff 100644
--- a/app/client/modules/backups/components/create-schedule-form/types.ts
+++ b/app/client/modules/backups/components/create-schedule-form/types.ts
@@ -1,30 +1,27 @@
-import { type } from "arktype";
-import { deepClean } from "~/utils/object";
+import { z } from "zod";
-export const internalFormSchema = type({
- name: "1 <= string <= 128",
- repositoryId: "string",
- excludePatternsText: "string?",
- excludeIfPresentText: "string?",
- includePatternsText: "string?",
- includePatterns: "string[]?",
- frequency: "string",
- dailyTime: "string?",
- weeklyDay: "string?",
- monthlyDays: "string[]?",
- cronExpression: "string?",
- keepLast: "number?",
- keepHourly: "number?",
- keepDaily: "number?",
- keepWeekly: "number?",
- keepMonthly: "number?",
- keepYearly: "number?",
- oneFileSystem: "boolean?",
- customResticParamsText: "string?",
+export const internalFormSchema = z.object({
+ name: z.string().min(1).max(128),
+ repositoryId: z.string(),
+ excludePatternsText: z.string().optional(),
+ excludeIfPresentText: z.string().optional(),
+ includePatternsText: z.string().optional(),
+ includePatterns: z.array(z.string()).optional(),
+ frequency: z.string(),
+ dailyTime: z.string().optional(),
+ weeklyDay: z.string().optional(),
+ monthlyDays: z.array(z.string()).optional(),
+ cronExpression: z.string().optional(),
+ keepLast: z.number().optional(),
+ keepHourly: z.number().optional(),
+ keepDaily: z.number().optional(),
+ keepWeekly: z.number().optional(),
+ keepMonthly: z.number().optional(),
+ keepYearly: z.number().optional(),
+ oneFileSystem: z.boolean().optional(),
+ customResticParamsText: z.string().optional(),
});
-export const cleanSchema = type.pipe((d) => internalFormSchema(deepClean(d)));
-
export const weeklyDays = [
{ label: "Monday", value: "1" },
{ label: "Tuesday", value: "2" },
@@ -35,7 +32,7 @@ export const weeklyDays = [
{ label: "Sunday", value: "0" },
];
-export type InternalFormValues = typeof internalFormSchema.infer;
+export type InternalFormValues = z.infer;
export type BackupScheduleFormValues = Omit<
InternalFormValues,
diff --git a/app/client/modules/notifications/components/create-notification-form.tsx b/app/client/modules/notifications/components/create-notification-form.tsx
index 684740e2..4c51f02f 100644
--- a/app/client/modules/notifications/components/create-notification-form.tsx
+++ b/app/client/modules/notifications/components/create-notification-form.tsx
@@ -1,8 +1,7 @@
-import { arktypeResolver } from "@hookform/resolvers/arktype";
-import { type } from "arktype";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
+import { z } from "zod";
import { cn } from "~/client/lib/utils";
-import { deepClean } from "~/utils/object";
import {
Form,
FormControl,
@@ -14,7 +13,17 @@ import {
} from "~/client/components/ui/form";
import { Input } from "~/client/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
-import { notificationConfigSchemaBase } from "~/schemas/notifications";
+import {
+ customNotificationConfigSchema,
+ discordNotificationConfigSchema,
+ emailNotificationConfigSchema,
+ genericNotificationConfigSchema,
+ gotifyNotificationConfigSchema,
+ ntfyNotificationConfigSchema,
+ pushoverNotificationConfigSchema,
+ slackNotificationConfigSchema,
+ telegramNotificationConfigSchema,
+} from "~/schemas/notifications";
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
import {
CustomForm,
@@ -28,12 +37,21 @@ import {
TelegramForm,
} from "./notification-forms";
-export const formSchema = type({
- name: "2<=string<=32",
-}).and(notificationConfigSchemaBase);
-const cleanSchema = type.pipe((d) => formSchema(deepClean(d)));
+const baseFields = { name: z.string().min(2).max(32) };
-export type NotificationFormValues = typeof formSchema.inferIn;
+export const formSchema = z.discriminatedUnion("type", [
+ emailNotificationConfigSchema.extend(baseFields),
+ slackNotificationConfigSchema.extend(baseFields),
+ discordNotificationConfigSchema.extend(baseFields),
+ gotifyNotificationConfigSchema.extend(baseFields),
+ ntfyNotificationConfigSchema.extend(baseFields),
+ pushoverNotificationConfigSchema.extend(baseFields),
+ telegramNotificationConfigSchema.extend(baseFields),
+ genericNotificationConfigSchema.extend(baseFields),
+ customNotificationConfigSchema.extend(baseFields),
+]);
+
+export type NotificationFormValues = z.input;
type Props = {
onSubmit: (values: NotificationFormValues) => void;
@@ -106,7 +124,7 @@ const defaultValuesForType = {
export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValues, formId, className }: Props) => {
const form = useForm({
- resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema),
+ resolver: zodResolver(formSchema, undefined, { raw: true }),
defaultValues: initialValues || {
name: "",
},
diff --git a/app/client/modules/repositories/components/create-repository-form.tsx b/app/client/modules/repositories/components/create-repository-form.tsx
index 5af7a393..3bfcc7af 100644
--- a/app/client/modules/repositories/components/create-repository-form.tsx
+++ b/app/client/modules/repositories/components/create-repository-form.tsx
@@ -1,10 +1,9 @@
-import { arktypeResolver } from "@hookform/resolvers/arktype";
-import { type } from "arktype";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { Save } from "lucide-react";
+import { z } from "zod";
import { cn } from "~/client/lib/utils";
-import { deepClean } from "~/utils/object";
import { Button } from "../../../components/ui/button";
import {
Form,
@@ -21,7 +20,17 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
import { useSystemInfo } from "~/client/hooks/use-system-info";
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
-import { COMPRESSION_MODES, repositoryConfigSchemaBase } from "~/schemas/restic";
+import {
+ COMPRESSION_MODES,
+ azureRepositoryConfigSchema,
+ gcsRepositoryConfigSchema,
+ localRepositoryConfigSchema,
+ r2RepositoryConfigSchema,
+ rcloneRepositoryConfigSchema,
+ restRepositoryConfigSchema,
+ s3RepositoryConfigSchema,
+ sftpRepositoryConfigSchema,
+} from "~/schemas/restic";
import { Checkbox } from "../../../components/ui/checkbox";
import {
LocalRepositoryForm,
@@ -38,13 +47,23 @@ import { useServerFn } from "@tanstack/react-start";
import { getServerConstants } from "~/server/lib/functions/server-constants";
import { useSuspenseQuery } from "@tanstack/react-query";
-export const formSchema = type({
- name: "2<=string<=32",
- compressionMode: type.valueOf(COMPRESSION_MODES).optional(),
-}).and(repositoryConfigSchemaBase);
-const cleanSchema = type.pipe((d) => formSchema(deepClean(d)));
+const formBaseFields = {
+ name: z.string().min(2).max(32),
+ compressionMode: z.enum(COMPRESSION_MODES).optional(),
+};
-export type RepositoryFormValues = typeof formSchema.inferIn;
+export const formSchema = z.discriminatedUnion("backend", [
+ localRepositoryConfigSchema.extend(formBaseFields),
+ s3RepositoryConfigSchema.extend(formBaseFields),
+ r2RepositoryConfigSchema.extend(formBaseFields),
+ gcsRepositoryConfigSchema.extend(formBaseFields),
+ azureRepositoryConfigSchema.extend(formBaseFields),
+ rcloneRepositoryConfigSchema.extend(formBaseFields),
+ restRepositoryConfigSchema.extend(formBaseFields),
+ sftpRepositoryConfigSchema.extend(formBaseFields),
+]);
+
+export type RepositoryFormValues = z.input;
type Props = {
onSubmit: (values: RepositoryFormValues) => void;
@@ -81,7 +100,7 @@ export const CreateRepositoryForm = ({
});
const form = useForm({
- resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema),
+ resolver: zodResolver(formSchema, undefined, { raw: true }),
defaultValues: initialValues,
resetOptions: {
keepDefaultValues: true,
diff --git a/app/client/modules/repositories/routes/create-repository.tsx b/app/client/modules/repositories/routes/create-repository.tsx
index db450a63..92912c49 100644
--- a/app/client/modules/repositories/routes/create-repository.tsx
+++ b/app/client/modules/repositories/routes/create-repository.tsx
@@ -5,6 +5,7 @@ import { toast } from "sonner";
import { createRepositoryMutation } from "~/client/api-client/@tanstack/react-query.gen";
import {
CreateRepositoryForm,
+ formSchema,
type RepositoryFormValues,
} from "~/client/modules/repositories/components/create-repository-form";
import { Button } from "~/client/components/ui/button";
@@ -26,11 +27,13 @@ export function CreateRepositoryPage() {
});
const handleSubmit = (values: RepositoryFormValues) => {
+ const { name, compressionMode, ...config } = formSchema.parse(values);
+
createRepository.mutate({
body: {
- config: values,
- name: values.name,
- compressionMode: values.compressionMode,
+ config,
+ name,
+ compressionMode,
},
});
};
diff --git a/app/client/modules/repositories/routes/edit-repository.tsx b/app/client/modules/repositories/routes/edit-repository.tsx
index ad949104..d448f4a2 100644
--- a/app/client/modules/repositories/routes/edit-repository.tsx
+++ b/app/client/modules/repositories/routes/edit-repository.tsx
@@ -5,6 +5,7 @@ import { toast } from "sonner";
import { getRepositoryOptions, updateRepositoryMutation } from "~/client/api-client/@tanstack/react-query.gen";
import {
CreateRepositoryForm,
+ formSchema,
type RepositoryFormValues,
} from "~/client/modules/repositories/components/create-repository-form";
import {
@@ -35,11 +36,11 @@ const riskyLocationFieldsByBackend = {
rclone: ["remote", "path"],
} as const;
-const hasRiskyLocationChange = (initialConfig: RepositoryConfig, nextConfig: RepositoryFormValues): boolean => {
+const hasRiskyLocationChange = (initialConfig: RepositoryConfig, nextConfig: RepositoryConfig): boolean => {
const fields = riskyLocationFieldsByBackend[initialConfig.backend] ?? [];
return fields.some(
- (field) => initialConfig[field as keyof RepositoryConfig] !== nextConfig[field as keyof RepositoryFormValues],
+ (field) => initialConfig[field as keyof RepositoryConfig] !== nextConfig[field as keyof RepositoryConfig],
);
};
@@ -75,18 +76,20 @@ export function EditRepositoryPage({ repositoryId }: { repositoryId: string }) {
};
const submitUpdate = (values: RepositoryFormValues) => {
+ const { name, compressionMode, ...config } = formSchema.parse(values);
+
updateRepository.mutate({
path: { shortId: repositoryId },
body: {
- name: values.name,
- compressionMode: values.compressionMode,
- config: values,
+ name,
+ compressionMode,
+ config,
},
});
};
const handleSubmit = (values: RepositoryFormValues) => {
- const nextConfig = values;
+ const { name: _name, compressionMode: _compressionMode, ...nextConfig } = formSchema.parse(values);
if (hasRiskyLocationChange(initialConfig, nextConfig)) {
setPendingValues(values);
diff --git a/app/client/modules/settings/components/create-user-dialog.tsx b/app/client/modules/settings/components/create-user-dialog.tsx
index 25ba8533..8c669903 100644
--- a/app/client/modules/settings/components/create-user-dialog.tsx
+++ b/app/client/modules/settings/components/create-user-dialog.tsx
@@ -1,10 +1,10 @@
-import { arktypeResolver } from "@hookform/resolvers/arktype";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
-import { type } from "arktype";
import { Plus, UserPlus } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
+import { z } from "zod";
import { Button } from "~/client/components/ui/button";
import {
Dialog,
@@ -20,15 +20,15 @@ import { Input } from "~/client/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
import { authClient } from "~/client/lib/auth-client";
-const createUserSchema = type({
- name: "string>=1",
- username: "string>=1",
- email: "string",
- password: "string>=8",
- role: "'user'|'admin'",
+const createUserSchema = z.object({
+ name: z.string().min(1),
+ username: z.string().min(1),
+ email: z.string().email(),
+ password: z.string().min(8),
+ role: z.enum(["user", "admin"]),
});
-type CreateUserFormValues = typeof createUserSchema.infer;
+type CreateUserFormValues = z.infer;
interface CreateUserDialogProps {
onUserCreated?: () => void;
@@ -38,7 +38,7 @@ export function CreateUserDialog({ onUserCreated }: CreateUserDialogProps) {
const [isOpen, setIsOpen] = useState(false);
const form = useForm({
- resolver: arktypeResolver(createUserSchema),
+ resolver: zodResolver(createUserSchema),
defaultValues: {
name: "",
username: "",
diff --git a/app/client/modules/sso/routes/create-sso-provider.tsx b/app/client/modules/sso/routes/create-sso-provider.tsx
index 8b13f41a..f293dd64 100644
--- a/app/client/modules/sso/routes/create-sso-provider.tsx
+++ b/app/client/modules/sso/routes/create-sso-provider.tsx
@@ -1,11 +1,11 @@
-import { arktypeResolver } from "@hookform/resolvers/arktype";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
-import { type } from "arktype";
import { ShieldCheck, Plus } from "lucide-react";
import { useId } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
+import { z } from "zod";
import { updateSsoProviderAutoLinkingMutation } from "~/client/api-client/@tanstack/react-query.gen";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
@@ -24,17 +24,17 @@ import { authClient } from "~/client/lib/auth-client";
import { parseError } from "~/client/lib/errors";
import { useOrganizationContext } from "~/client/hooks/use-org-context";
-const ssoProviderSchema = type({
- providerId: "string>=1",
- issuer: "string>=1",
- domain: "string>=1",
- clientId: "string>=1",
- clientSecret: "string>=1",
- discoveryEndpoint: "string>=1",
- linkMatchingEmails: "boolean",
+const ssoProviderSchema = z.object({
+ providerId: z.string().min(1),
+ issuer: z.string().min(1),
+ domain: z.string().min(1),
+ clientId: z.string().min(1),
+ clientSecret: z.string().min(1),
+ discoveryEndpoint: z.string().min(1),
+ linkMatchingEmails: z.boolean(),
});
-type ProviderForm = typeof ssoProviderSchema.infer;
+type ProviderForm = z.infer;
export function CreateSsoProviderPage() {
const navigate = useNavigate();
@@ -42,7 +42,7 @@ export function CreateSsoProviderPage() {
const { activeOrganization } = useOrganizationContext();
const form = useForm({
- resolver: arktypeResolver(ssoProviderSchema),
+ resolver: zodResolver(ssoProviderSchema),
defaultValues: {
providerId: "",
issuer: "",
diff --git a/app/client/modules/volumes/components/create-volume-form.tsx b/app/client/modules/volumes/components/create-volume-form.tsx
index 598d47c8..0b1d51f9 100644
--- a/app/client/modules/volumes/components/create-volume-form.tsx
+++ b/app/client/modules/volumes/components/create-volume-form.tsx
@@ -1,11 +1,10 @@
-import { arktypeResolver } from "@hookform/resolvers/arktype";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
-import { type } from "arktype";
import { CheckCircle, Loader2, Plug, Save, XCircle } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
+import { z } from "zod";
import { cn } from "~/client/lib/utils";
-import { deepClean } from "~/utils/object";
import { Button } from "../../../components/ui/button";
import {
Form,
@@ -18,19 +17,31 @@ import {
} from "../../../components/ui/form";
import { Input } from "../../../components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../components/ui/select";
-import { volumeConfigSchemaBase } from "~/schemas/volumes";
+import {
+ directoryConfigSchema,
+ nfsConfigSchema,
+ rcloneConfigSchema,
+ sftpConfigSchema,
+ smbConfigSchema,
+ volumeConfigSchema,
+ webdavConfigSchema,
+} from "~/schemas/volumes";
import { testConnectionMutation } from "../../../api-client/@tanstack/react-query.gen";
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
import { useSystemInfo } from "~/client/hooks/use-system-info";
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
import { DirectoryForm, NFSForm, SMBForm, WebDAVForm, RcloneForm, SFTPForm } from "./volume-forms";
-export const formSchema = type({
- name: "2<=string<=32",
-}).and(volumeConfigSchemaBase);
-const cleanSchema = type.pipe((d) => formSchema(deepClean(d)));
+export const formSchema = z.discriminatedUnion("backend", [
+ directoryConfigSchema.extend({ name: z.string().min(2).max(32) }),
+ nfsConfigSchema.extend({ name: z.string().min(2).max(32) }),
+ smbConfigSchema.extend({ name: z.string().min(2).max(32) }),
+ webdavConfigSchema.extend({ name: z.string().min(2).max(32) }),
+ rcloneConfigSchema.extend({ name: z.string().min(2).max(32) }),
+ sftpConfigSchema.extend({ name: z.string().min(2).max(32) }),
+]);
-export type FormValues = typeof formSchema.inferIn;
+export type FormValues = z.input;
type Props = {
onSubmit: (values: FormValues) => void;
@@ -52,7 +63,7 @@ const defaultValuesForType = {
export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, formId, loading, className }: Props) => {
const form = useForm({
- resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema),
+ resolver: zodResolver(formSchema, undefined, { raw: true }),
defaultValues: initialValues || {
name: "",
backend: "directory",
@@ -89,15 +100,22 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
const handleTestConnection = async () => {
const formValues = getValues();
+ const { name: _, ...configCandidate } = formValues;
+ const parsedConfig = volumeConfigSchema.safeParse(configCandidate);
+
+ if (!parsedConfig.success) {
+ setTestMessage({ success: false, message: "Please fix validation errors before testing the connection." });
+ return;
+ }
if (
- formValues.backend === "nfs" ||
- formValues.backend === "smb" ||
- formValues.backend === "webdav" ||
- formValues.backend === "sftp"
+ parsedConfig.data.backend === "nfs" ||
+ parsedConfig.data.backend === "smb" ||
+ parsedConfig.data.backend === "webdav" ||
+ parsedConfig.data.backend === "sftp"
) {
testBackendConnection.mutate({
- body: { config: formValues },
+ body: { config: parsedConfig.data },
});
}
};
diff --git a/app/client/modules/volumes/routes/create-volume.tsx b/app/client/modules/volumes/routes/create-volume.tsx
index a6bb98c2..db00d37a 100644
--- a/app/client/modules/volumes/routes/create-volume.tsx
+++ b/app/client/modules/volumes/routes/create-volume.tsx
@@ -3,7 +3,7 @@ import { HardDrive, Plus } from "lucide-react";
import { useId } from "react";
import { toast } from "sonner";
import { createVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
-import { CreateVolumeForm, type FormValues } from "~/client/modules/volumes/components/create-volume-form";
+import { CreateVolumeForm, formSchema, type FormValues } from "~/client/modules/volumes/components/create-volume-form";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
import { parseError } from "~/client/lib/errors";
@@ -23,10 +23,12 @@ export function CreateVolumePage() {
});
const handleSubmit = (values: FormValues) => {
+ const { name, ...config } = formSchema.parse(values);
+
createVolume.mutate({
body: {
- config: values,
- name: values.name,
+ config,
+ name,
},
});
};
diff --git a/app/client/modules/volumes/tabs/info.tsx b/app/client/modules/volumes/tabs/info.tsx
index be9221be..29dbc6e7 100644
--- a/app/client/modules/volumes/tabs/info.tsx
+++ b/app/client/modules/volumes/tabs/info.tsx
@@ -2,7 +2,7 @@ import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { toast } from "sonner";
import { Check, Plug, Trash2, Unplug } from "lucide-react";
-import { CreateVolumeForm, type FormValues } from "~/client/modules/volumes/components/create-volume-form";
+import { CreateVolumeForm, formSchema, type FormValues } from "~/client/modules/volumes/components/create-volume-form";
import {
AlertDialog,
AlertDialogAction,
@@ -102,9 +102,11 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
const confirmUpdate = () => {
if (pendingValues) {
+ const { name, ...config } = formSchema.parse(pendingValues);
+
updateMutation.mutate({
path: { shortId: volume.shortId },
- body: { name: pendingValues.name, config: pendingValues },
+ body: { name, config },
});
}
};
diff --git a/app/routes/(auth)/login.error.tsx b/app/routes/(auth)/login.error.tsx
index 36f3b2d8..e977d09b 100644
--- a/app/routes/(auth)/login.error.tsx
+++ b/app/routes/(auth)/login.error.tsx
@@ -1,11 +1,11 @@
import { createFileRoute } from "@tanstack/react-router";
-import { type } from "arktype";
+import { z } from "zod";
import { LoginPage } from "~/client/modules/auth/routes/login";
export const Route = createFileRoute("/(auth)/login/error")({
component: RouteComponent,
errorComponent: () => Failed to load login error
,
- validateSearch: type({ error: "string?" }),
+ validateSearch: z.object({ error: z.string().optional() }),
head: () => ({
meta: [
{ title: "Zerobyte - Login Error" },
diff --git a/app/routes/(auth)/login.tsx b/app/routes/(auth)/login.tsx
index d3a0259c..a2c39764 100644
--- a/app/routes/(auth)/login.tsx
+++ b/app/routes/(auth)/login.tsx
@@ -1,11 +1,11 @@
import { Outlet, createFileRoute, useRouterState } from "@tanstack/react-router";
-import { type } from "arktype";
+import { z } from "zod";
import { LoginPage } from "~/client/modules/auth/routes/login";
export const Route = createFileRoute("/(auth)/login")({
component: RouteComponent,
errorComponent: () => Failed to load login
,
- validateSearch: type({ error: "string?" }),
+ validateSearch: z.object({ error: z.string().optional() }),
head: () => ({
meta: [
{ title: "Zerobyte - Login" },
diff --git a/app/routes/(dashboard)/admin/index.tsx b/app/routes/(dashboard)/admin/index.tsx
index ff953ba4..b21b38d1 100644
--- a/app/routes/(dashboard)/admin/index.tsx
+++ b/app/routes/(dashboard)/admin/index.tsx
@@ -1,12 +1,12 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
-import { type } from "arktype";
+import { z } from "zod";
import { fetchUser } from "../route";
import type { AppContext } from "~/context";
import { AdminPage } from "~/client/modules/admin/routes/admin-page";
import { getAdminUsersOptions, getRegistrationStatusOptions } from "~/client/api-client/@tanstack/react-query.gen";
export const Route = createFileRoute("/(dashboard)/admin/")({
- validateSearch: type({ tab: "string?" }),
+ validateSearch: z.object({ tab: z.string().optional() }),
component: RouteComponent,
errorComponent: () => Failed to load admin
,
loader: async ({ context }) => {
diff --git a/app/routes/(dashboard)/backups/$backupId/index.tsx b/app/routes/(dashboard)/backups/$backupId/index.tsx
index 842a17de..7fd35a54 100644
--- a/app/routes/(dashboard)/backups/$backupId/index.tsx
+++ b/app/routes/(dashboard)/backups/$backupId/index.tsx
@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
-import { type } from "arktype";
+import { z } from "zod";
import {
getBackupProgressOptions,
getBackupScheduleOptions,
@@ -15,7 +15,7 @@ import { prefetchOrSkip } from "~/utils/prefetch";
export const Route = createFileRoute("/(dashboard)/backups/$backupId/")({
component: RouteComponent,
errorComponent: () => Failed to load backup
,
- validateSearch: type({ snapshot: "string?" }),
+ validateSearch: z.object({ snapshot: z.string().optional() }),
loader: async ({ params, context }) => {
const { backupId } = params;
diff --git a/app/routes/(dashboard)/repositories/$repositoryId/index.tsx b/app/routes/(dashboard)/repositories/$repositoryId/index.tsx
index 0e7158f6..82368797 100644
--- a/app/routes/(dashboard)/repositories/$repositoryId/index.tsx
+++ b/app/routes/(dashboard)/repositories/$repositoryId/index.tsx
@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
-import { type } from "arktype";
+import { z } from "zod";
import {
getRepositoryOptions,
getRepositoryStatsOptions,
@@ -30,7 +30,7 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/")
stats: context.queryClient.getQueryData(statsOptions.queryKey),
};
},
- validateSearch: type({ tab: "string?" }),
+ validateSearch: z.object({ tab: z.string().optional() }),
staticData: {
breadcrumb: (match) => [
{ label: "Repositories", href: "/repositories" },
diff --git a/app/routes/(dashboard)/settings/index.tsx b/app/routes/(dashboard)/settings/index.tsx
index 9034d40b..b97f0569 100644
--- a/app/routes/(dashboard)/settings/index.tsx
+++ b/app/routes/(dashboard)/settings/index.tsx
@@ -2,14 +2,14 @@ import { createFileRoute } from "@tanstack/react-router";
import { fetchUser } from "../route";
import type { AppContext } from "~/context";
import { SettingsPage } from "~/client/modules/settings/routes/settings";
-import { type } from "arktype";
+import { z } from "zod";
+import { getOrganizationContext } from "~/server/lib/functions/organization-context";
import { getOrgMembersOptions, getSsoSettingsOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { getOrigin } from "~/client/functions/get-origin";
-import { getOrganizationContext } from "~/server/lib/functions/organization-context";
export const Route = createFileRoute("/(dashboard)/settings/")({
component: RouteComponent,
- validateSearch: type({ tab: "string?" }),
+ validateSearch: z.object({ tab: z.string().optional() }),
errorComponent: () => Failed to load settings
,
loader: async ({ context }) => {
const [authContext, orgContext] = await Promise.all([
diff --git a/app/routes/(dashboard)/volumes/$volumeId.tsx b/app/routes/(dashboard)/volumes/$volumeId.tsx
index c88d10d0..7dfc9610 100644
--- a/app/routes/(dashboard)/volumes/$volumeId.tsx
+++ b/app/routes/(dashboard)/volumes/$volumeId.tsx
@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
-import { type } from "arktype";
+import { z } from "zod";
import { getVolumeOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { VolumeDetails } from "~/client/modules/volumes/routes/volume-details";
@@ -13,7 +13,7 @@ export const Route = createFileRoute("/(dashboard)/volumes/$volumeId")({
return res;
},
- validateSearch: type({ tab: "string?" }),
+ validateSearch: z.object({ tab: z.string().optional() }),
staticData: {
breadcrumb: (match) => [
{ label: "Volumes", href: "/volumes" },
diff --git a/app/schemas/events-dto.ts b/app/schemas/events-dto.ts
index 17a2e5f3..02e892b8 100644
--- a/app/schemas/events-dto.ts
+++ b/app/schemas/events-dto.ts
@@ -1,85 +1,84 @@
-import { type } from "arktype";
+import { z } from "zod";
import { resticBackupProgressMetricsSchema, resticBackupRunSummarySchema } from "~/schemas/restic-dto";
-export const backupEventStatusSchema = type("'success' | 'error' | 'stopped' | 'warning'");
-export const restoreEventStatusSchema = type("'success' | 'error'");
+export const backupEventStatusSchema = z.enum(["success", "error", "stopped", "warning"]);
+export const restoreEventStatusSchema = z.enum(["success", "error"]);
-const backupEventBaseSchema = type({
- scheduleId: "string",
- volumeName: "string",
- repositoryName: "string",
+const backupEventBaseSchema = z.object({
+ scheduleId: z.string(),
+ volumeName: z.string(),
+ repositoryName: z.string(),
});
-const organizationScopedSchema = type({
- organizationId: "string",
+const organizationScopedSchema = z.object({
+ organizationId: z.string(),
});
-const restoreEventBaseSchema = type({
- repositoryId: "string",
- snapshotId: "string",
+const restoreEventBaseSchema = z.object({
+ repositoryId: z.string(),
+ snapshotId: z.string(),
});
-const dumpStartedEventSchema = type({
- repositoryId: "string",
- snapshotId: "string",
- path: "string",
- filename: "string",
+const dumpStartedEventSchema = z.object({
+ repositoryId: z.string(),
+ snapshotId: z.string(),
+ path: z.string(),
+ filename: z.string(),
});
-const restoreProgressMetricsSchema = type({
- seconds_elapsed: "number",
- percent_done: "number",
- total_files: "number",
- files_restored: "number",
- total_bytes: "number",
- bytes_restored: "number",
+const restoreProgressMetricsSchema = z.object({
+ seconds_elapsed: z.number(),
+ percent_done: z.number(),
+ total_files: z.number(),
+ files_restored: z.number(),
+ total_bytes: z.number(),
+ bytes_restored: z.number(),
});
export const backupStartedEventSchema = backupEventBaseSchema;
-export const backupProgressEventSchema = backupEventBaseSchema.and(resticBackupProgressMetricsSchema);
+export const backupProgressEventSchema = backupEventBaseSchema.extend(resticBackupProgressMetricsSchema.shape);
-export const backupCompletedEventSchema = backupEventBaseSchema.and(
- type({
- status: backupEventStatusSchema,
- summary: resticBackupRunSummarySchema.optional(),
- }),
-);
+export const backupCompletedEventSchema = backupEventBaseSchema.extend({
+ status: backupEventStatusSchema,
+ summary: resticBackupRunSummarySchema.optional(),
+});
export const restoreStartedEventSchema = restoreEventBaseSchema;
-export const restoreProgressEventSchema = restoreEventBaseSchema.and(restoreProgressMetricsSchema);
+export const restoreProgressEventSchema = restoreEventBaseSchema.extend(restoreProgressMetricsSchema.shape);
-export const restoreCompletedEventSchema = restoreEventBaseSchema.and(
- type({ status: restoreEventStatusSchema, error: "string?" }),
-);
+export const restoreCompletedEventSchema = restoreEventBaseSchema.extend({
+ status: restoreEventStatusSchema,
+ error: z.string().optional(),
+});
-export const serverBackupStartedEventSchema = organizationScopedSchema.and(backupStartedEventSchema);
+export const serverBackupStartedEventSchema = organizationScopedSchema.extend(backupStartedEventSchema.shape);
-export const serverBackupProgressEventSchema = organizationScopedSchema.and(backupProgressEventSchema);
+export const serverBackupProgressEventSchema = organizationScopedSchema.extend(backupProgressEventSchema.shape);
-export const serverBackupCompletedEventSchema = organizationScopedSchema.and(backupCompletedEventSchema);
+export const serverBackupCompletedEventSchema = organizationScopedSchema.extend(backupCompletedEventSchema.shape);
-export const serverRestoreStartedEventSchema = organizationScopedSchema.and(restoreStartedEventSchema);
+export const serverRestoreStartedEventSchema = organizationScopedSchema.extend(restoreStartedEventSchema.shape);
-export const serverRestoreProgressEventSchema = organizationScopedSchema.and(restoreProgressEventSchema);
+export const serverRestoreProgressEventSchema = organizationScopedSchema.extend(restoreProgressEventSchema.shape);
-export const serverRestoreCompletedEventSchema = organizationScopedSchema.and(restoreCompletedEventSchema);
+export const serverRestoreCompletedEventSchema = organizationScopedSchema.extend(restoreCompletedEventSchema.shape);
-export const serverDumpStartedEventSchema = organizationScopedSchema.and(dumpStartedEventSchema);
+export const serverDumpStartedEventSchema = organizationScopedSchema.extend(dumpStartedEventSchema.shape);
-export type BackupEventStatusDto = typeof backupEventStatusSchema.infer;
-export type BackupStartedEventDto = typeof backupStartedEventSchema.infer;
-export type BackupProgressEventDto = typeof backupProgressEventSchema.infer;
-export type BackupCompletedEventDto = typeof backupCompletedEventSchema.infer;
-export type RestoreStartedEventDto = typeof restoreStartedEventSchema.infer;
-export type RestoreProgressEventDto = typeof restoreProgressEventSchema.infer;
-export type RestoreCompletedEventDto = typeof restoreCompletedEventSchema.infer;
-export type DumpStartedEventDto = typeof dumpStartedEventSchema.infer;
-export type ServerBackupStartedEventDto = typeof serverBackupStartedEventSchema.infer;
-export type ServerBackupProgressEventDto = typeof serverBackupProgressEventSchema.infer;
-export type ServerBackupCompletedEventDto = typeof serverBackupCompletedEventSchema.infer;
-export type ServerRestoreStartedEventDto = typeof serverRestoreStartedEventSchema.infer;
-export type ServerRestoreProgressEventDto = typeof serverRestoreProgressEventSchema.infer;
-export type ServerRestoreCompletedEventDto = typeof serverRestoreCompletedEventSchema.infer;
-export type ServerDumpStartedEventDto = typeof serverDumpStartedEventSchema.infer;
+export type BackupEventStatusDto = z.infer;
+export type BackupStartedEventDto = z.infer;
+export type BackupProgressEventDto = z.infer;
+export type BackupCompletedEventDto = z.infer;
+export type RestoreStartedEventDto = z.infer;
+export type RestoreProgressEventDto = z.infer;
+export type RestoreCompletedEventDto = z.infer;
+export type DumpStartedEventDto = z.infer;
+export type ServerBackupStartedEventDto = z.infer;
+export type ServerBackupProgressEventDto = z.infer;
+export type ServerBackupCompletedEventDto = z.infer;
+export type ServerRestoreStartedEventDto = z.infer;
+export type ServerRestoreProgressEventDto = z.infer;
+export type ServerRestoreCompletedEventDto = z.infer;
+export type ServerDumpStartedEventDto = z.infer;
diff --git a/app/schemas/notifications.ts b/app/schemas/notifications.ts
index d8909f6d..d93db511 100644
--- a/app/schemas/notifications.ts
+++ b/app/schemas/notifications.ts
@@ -1,4 +1,4 @@
-import { type } from "arktype";
+import { z } from "zod";
export const NOTIFICATION_TYPES = {
email: "email",
@@ -14,96 +14,98 @@ export const NOTIFICATION_TYPES = {
export type NotificationType = keyof typeof NOTIFICATION_TYPES;
-export const emailNotificationConfigSchema = type({
- type: "'email'",
- smtpHost: "string",
- smtpPort: "1 <= number <= 65535",
- username: "string?",
- password: "string?",
- from: "string",
- fromName: "string?",
- to: "string[]",
- useTLS: "boolean",
+export const emailNotificationConfigSchema = z.object({
+ type: z.literal("email"),
+ smtpHost: z.string().min(1),
+ smtpPort: z.number().int().min(1).max(65535),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ from: z.string().min(1),
+ fromName: z.string().optional(),
+ to: z.array(z.string()),
+ useTLS: z.boolean(),
});
-export const slackNotificationConfigSchema = type({
- type: "'slack'",
- webhookUrl: "string",
- channel: "string?",
- username: "string?",
- iconEmoji: "string?",
+export const slackNotificationConfigSchema = z.object({
+ type: z.literal("slack"),
+ webhookUrl: z.string().min(1),
+ channel: z.string().optional(),
+ username: z.string().optional(),
+ iconEmoji: z.string().optional(),
});
-export const discordNotificationConfigSchema = type({
- type: "'discord'",
- webhookUrl: "string",
- username: "string?",
- avatarUrl: "string?",
- threadId: "string?",
+export const discordNotificationConfigSchema = z.object({
+ type: z.literal("discord"),
+ webhookUrl: z.string().min(1),
+ username: z.string().optional(),
+ avatarUrl: z.string().optional(),
+ threadId: z.string().optional(),
});
-export const gotifyNotificationConfigSchema = type({
- type: "'gotify'",
- serverUrl: "string",
- token: "string",
- path: "string?",
- priority: "0 <= number <= 10",
+export const gotifyNotificationConfigSchema = z.object({
+ type: z.literal("gotify"),
+ serverUrl: z.string().min(1),
+ token: z.string().min(1),
+ path: z.string().optional(),
+ priority: z.number().min(0).max(10),
});
-export const ntfyNotificationConfigSchema = type({
- type: "'ntfy'",
- serverUrl: "string?",
- topic: "string",
- priority: "'max' | 'high' | 'default' | 'low' | 'min'",
- username: "string?",
- password: "string?",
- accessToken: "string?",
+export const ntfyNotificationConfigSchema = z.object({
+ type: z.literal("ntfy"),
+ serverUrl: z.string().optional(),
+ topic: z.string().min(1),
+ priority: z.enum(["max", "high", "default", "low", "min"]),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ accessToken: z.string().optional(),
});
-export const pushoverNotificationConfigSchema = type({
- type: "'pushover'",
- userKey: "string",
- apiToken: "string",
- devices: "string?",
- priority: "-1 | 0 | 1",
+export const pushoverNotificationConfigSchema = z.object({
+ type: z.literal("pushover"),
+ userKey: z.string().min(1),
+ apiToken: z.string().min(1),
+ devices: z.string().optional(),
+ priority: z.union([z.literal(-1), z.literal(0), z.literal(1)]),
});
-export const telegramNotificationConfigSchema = type({
- type: "'telegram'",
- botToken: "string",
- chatId: "string",
- threadId: "string?",
+export const telegramNotificationConfigSchema = z.object({
+ type: z.literal("telegram"),
+ botToken: z.string().min(1),
+ chatId: z.string().min(1),
+ threadId: z.string().optional(),
});
-export const genericNotificationConfigSchema = type({
- type: "'generic'",
- url: "string",
- method: "'GET' | 'POST'",
- contentType: "string?",
- headers: "string[]?",
- useJson: "boolean?",
- titleKey: "string?",
- messageKey: "string?",
+export const genericNotificationConfigSchema = z.object({
+ type: z.literal("generic"),
+ url: z.string().min(1),
+ method: z.enum(["GET", "POST"]),
+ contentType: z.string().optional(),
+ headers: z.array(z.string()).optional(),
+ useJson: z.boolean().optional(),
+ titleKey: z.string().optional(),
+ messageKey: z.string().optional(),
});
-export const customNotificationConfigSchema = type({
- type: "'custom'",
- shoutrrrUrl: "string",
+export const customNotificationConfigSchema = z.object({
+ type: z.literal("custom"),
+ shoutrrrUrl: z.string().min(1),
});
-export const notificationConfigSchemaBase = emailNotificationConfigSchema
- .or(slackNotificationConfigSchema)
- .or(discordNotificationConfigSchema)
- .or(gotifyNotificationConfigSchema)
- .or(ntfyNotificationConfigSchema)
- .or(pushoverNotificationConfigSchema)
- .or(telegramNotificationConfigSchema)
- .or(genericNotificationConfigSchema)
- .or(customNotificationConfigSchema);
+export const notificationConfigSchemaBase = z.discriminatedUnion("type", [
+ emailNotificationConfigSchema,
+ slackNotificationConfigSchema,
+ discordNotificationConfigSchema,
+ gotifyNotificationConfigSchema,
+ ntfyNotificationConfigSchema,
+ pushoverNotificationConfigSchema,
+ telegramNotificationConfigSchema,
+ genericNotificationConfigSchema,
+ customNotificationConfigSchema,
+]);
-export const notificationConfigSchema = notificationConfigSchemaBase.onUndeclaredKey("delete");
+export const notificationConfigSchema = notificationConfigSchemaBase;
-export type NotificationConfig = typeof notificationConfigSchema.infer;
+export type NotificationConfig = z.infer;
export const NOTIFICATION_EVENTS = {
start: "start",
diff --git a/app/schemas/restic-dto.ts b/app/schemas/restic-dto.ts
index 390cf265..e7185c7b 100644
--- a/app/schemas/restic-dto.ts
+++ b/app/schemas/restic-dto.ts
@@ -1,83 +1,72 @@
-import { type } from "arktype";
+import { z } from "zod";
-export const resticSummaryBaseSchema = type({
- files_new: "number",
- files_changed: "number",
- files_unmodified: "number",
- dirs_new: "number",
- dirs_changed: "number",
- dirs_unmodified: "number",
- data_blobs: "number",
- tree_blobs: "number",
- data_added: "number",
- data_added_packed: "number?",
- total_files_processed: "number",
- total_bytes_processed: "number",
+export const resticSummaryBaseSchema = z.object({
+ files_new: z.number(),
+ files_changed: z.number(),
+ files_unmodified: z.number(),
+ dirs_new: z.number(),
+ dirs_changed: z.number(),
+ dirs_unmodified: z.number(),
+ data_blobs: z.number(),
+ tree_blobs: z.number(),
+ data_added: z.number(),
+ data_added_packed: z.number().optional(),
+ total_files_processed: z.number(),
+ total_bytes_processed: z.number(),
});
-export const resticSnapshotSummarySchema = resticSummaryBaseSchema.and(
- type({
- backup_start: "string",
- backup_end: "string",
- }),
-);
-
-export const resticBackupRunSummarySchema = resticSummaryBaseSchema.and(
- type({
- total_duration: "number",
- snapshot_id: "string",
- }),
-);
-
-export const resticBackupOutputSchema = resticBackupRunSummarySchema.and(
- type({
- message_type: "'summary'",
- }),
-);
-
-export const resticBackupProgressMetricsSchema = type({
- seconds_elapsed: "number",
- seconds_remaining: "number = 0",
- percent_done: "number",
- total_files: "number",
- files_done: "number",
- total_bytes: "number",
- bytes_done: "number",
- current_files: type("string")
- .array()
- .default(() => []),
+export const resticSnapshotSummarySchema = resticSummaryBaseSchema.extend({
+ backup_start: z.string(),
+ backup_end: z.string(),
});
-export const resticBackupProgressSchema = resticBackupProgressMetricsSchema.and(
- type({
- message_type: "'status'",
- }),
-);
-
-export const resticRestoreOutputSchema = type({
- message_type: "'summary'",
- total_files: "number?",
- files_restored: "number",
- files_skipped: "number",
- total_bytes: "number?",
- bytes_restored: "number?",
- bytes_skipped: "number",
+export const resticBackupRunSummarySchema = resticSummaryBaseSchema.extend({
+ total_duration: z.number(),
+ snapshot_id: z.string(),
});
-export const resticStatsSchema = type({
- total_size: "number = 0",
- total_uncompressed_size: "number = 0",
- compression_ratio: "number = 0",
- compression_progress: "number = 0",
- compression_space_saving: "number = 0",
- snapshots_count: "number = 0",
+export const resticBackupOutputSchema = resticBackupRunSummarySchema.extend({
+ message_type: z.literal("summary"),
});
-export type ResticSnapshotSummaryDto = typeof resticSnapshotSummarySchema.infer;
-export type ResticBackupRunSummaryDto = typeof resticBackupRunSummarySchema.infer;
-export type ResticBackupOutputDto = typeof resticBackupOutputSchema.infer;
-export type ResticBackupProgressMetricsDto = typeof resticBackupProgressMetricsSchema.infer;
-export type ResticBackupProgressDto = typeof resticBackupProgressSchema.infer;
+export const resticBackupProgressMetricsSchema = z.object({
+ seconds_elapsed: z.number(),
+ percent_done: z.number(),
+ total_files: z.number(),
+ files_done: z.number(),
+ total_bytes: z.number(),
+ bytes_done: z.number(),
+ current_files: z.array(z.string()).default([]),
+});
-export type ResticRestoreOutputDto = typeof resticRestoreOutputSchema.infer;
-export type ResticStatsDto = typeof resticStatsSchema.infer;
+export const resticBackupProgressSchema = resticBackupProgressMetricsSchema.extend({
+ message_type: z.literal("status"),
+});
+
+export const resticRestoreOutputSchema = z.object({
+ message_type: z.literal("summary"),
+ total_files: z.number().optional(),
+ files_restored: z.number(),
+ files_skipped: z.number(),
+ total_bytes: z.number().optional(),
+ bytes_restored: z.number().optional(),
+ bytes_skipped: z.number(),
+});
+
+export const resticStatsSchema = z.object({
+ total_size: z.number().default(0),
+ total_uncompressed_size: z.number().default(0),
+ compression_ratio: z.number().default(0),
+ compression_progress: z.number().default(0),
+ compression_space_saving: z.number().default(0),
+ snapshots_count: z.number().default(0),
+});
+
+export type ResticSnapshotSummaryDto = z.infer;
+export type ResticBackupRunSummaryDto = z.infer;
+export type ResticBackupOutputDto = z.infer;
+export type ResticBackupProgressMetricsDto = z.infer;
+export type ResticBackupProgressDto = z.infer;
+
+export type ResticRestoreOutputDto = z.infer;
+export type ResticStatsDto = z.infer;
diff --git a/app/schemas/restic.ts b/app/schemas/restic.ts
index c92969c5..ee73f05a 100644
--- a/app/schemas/restic.ts
+++ b/app/schemas/restic.ts
@@ -1,4 +1,4 @@
-import { type } from "arktype";
+import { z } from "zod";
export const REPOSITORY_BACKENDS = {
local: "local",
@@ -21,98 +21,120 @@ export const BANDWIDTH_UNITS = {
export type BandwidthUnit = keyof typeof BANDWIDTH_UNITS;
-export const bandwidthLimitSchema = type({
- enabled: "boolean = false",
- value: "number > 0 = 1",
- unit: type.valueOf(BANDWIDTH_UNITS).default("Mbps"),
+const bandwidthUnitSchema = z.enum(["Kbps", "Mbps", "Gbps"]);
+
+export const bandwidthLimitSchema = z.object({
+ enabled: z.boolean().default(false),
+ value: z.number().positive().default(1),
+ unit: bandwidthUnitSchema.default("Mbps"),
});
-export type BandwidthLimit = typeof bandwidthLimitSchema.infer;
+export type BandwidthLimit = z.infer;
-// Common fields for all repository configs
-const baseRepositoryConfigSchema = type({
- isExistingRepository: "boolean?",
- customPassword: "string?",
- cacert: "string?",
- insecureTls: "boolean?",
- // Bandwidth controls
+const baseRepositoryConfigSchema = z.object({
+ isExistingRepository: z.boolean().optional(),
+ customPassword: z.string().optional(),
+ cacert: z.string().optional(),
+ insecureTls: z.boolean().optional(),
uploadLimit: bandwidthLimitSchema.optional(),
downloadLimit: bandwidthLimitSchema.optional(),
});
-export const s3RepositoryConfigSchema = type({
- backend: "'s3'",
- endpoint: "string",
- bucket: "string",
- accessKeyId: "string",
- secretAccessKey: "string",
-}).and(baseRepositoryConfigSchema);
+export const s3RepositoryConfigSchema = z
+ .object({
+ backend: z.literal("s3"),
+ endpoint: z.string().min(1),
+ bucket: z.string().min(1),
+ accessKeyId: z.string().min(1),
+ secretAccessKey: z.string().min(1),
+ })
+ .extend(baseRepositoryConfigSchema.shape);
-export const r2RepositoryConfigSchema = type({
- backend: "'r2'",
- endpoint: "string",
- bucket: "string",
- accessKeyId: "string",
- secretAccessKey: "string",
-}).and(baseRepositoryConfigSchema);
+export const r2RepositoryConfigSchema = z
+ .object({
+ backend: z.literal("r2"),
+ endpoint: z.string().min(1),
+ bucket: z.string().min(1),
+ accessKeyId: z.string().min(1),
+ secretAccessKey: z.string().min(1),
+ })
+ .extend(baseRepositoryConfigSchema.shape);
-export const localRepositoryConfigSchema = type({
- backend: "'local'",
- path: "string",
-}).and(baseRepositoryConfigSchema);
+export const localRepositoryConfigSchema = z
+ .object({
+ backend: z.literal("local"),
+ path: z.string().min(1),
+ })
+ .extend(baseRepositoryConfigSchema.shape);
-export const gcsRepositoryConfigSchema = type({
- backend: "'gcs'",
- bucket: "string",
- projectId: "string",
- credentialsJson: "string",
-}).and(baseRepositoryConfigSchema);
+export const gcsRepositoryConfigSchema = z
+ .object({
+ backend: z.literal("gcs"),
+ bucket: z.string().min(1),
+ projectId: z.string().min(1),
+ credentialsJson: z.string().min(1),
+ })
+ .extend(baseRepositoryConfigSchema.shape);
-export const azureRepositoryConfigSchema = type({
- backend: "'azure'",
- container: "string",
- accountName: "string",
- accountKey: "string",
- endpointSuffix: "string?",
-}).and(baseRepositoryConfigSchema);
+export const azureRepositoryConfigSchema = z
+ .object({
+ backend: z.literal("azure"),
+ container: z.string().min(1),
+ accountName: z.string().min(1),
+ accountKey: z.string().min(1),
+ endpointSuffix: z.string().optional(),
+ })
+ .extend(baseRepositoryConfigSchema.shape);
-export const rcloneRepositoryConfigSchema = type({
- backend: "'rclone'",
- remote: "string",
- path: "string",
-}).and(baseRepositoryConfigSchema);
+export const rcloneRepositoryConfigSchema = z
+ .object({
+ backend: z.literal("rclone"),
+ remote: z.string().min(1),
+ path: z.string().min(1),
+ })
+ .extend(baseRepositoryConfigSchema.shape);
-export const restRepositoryConfigSchema = type({
- backend: "'rest'",
- url: "string",
- username: "string?",
- password: "string?",
- path: "string?",
-}).and(baseRepositoryConfigSchema);
+export const restRepositoryConfigSchema = z
+ .object({
+ backend: z.literal("rest"),
+ url: z.string().min(1),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ path: z.string().optional(),
+ })
+ .extend(baseRepositoryConfigSchema.shape);
-export const sftpRepositoryConfigSchema = type({
- backend: "'sftp'",
- host: "string",
- port: type("string.integer").or(type("number")).to("1 <= number <= 65535").default(22),
- user: "string",
- path: "string",
- privateKey: "string",
- skipHostKeyCheck: "boolean = true",
- knownHosts: "string?",
-}).and(baseRepositoryConfigSchema);
+export const sftpRepositoryConfigSchema = z
+ .object({
+ backend: z.literal("sftp"),
+ host: z.string().min(1),
+ port: z
+ .union([z.string(), z.number()])
+ .transform((value) => (typeof value === "string" ? Number.parseInt(value, 10) : value))
+ .pipe(z.number().int().min(1).max(65535))
+ .default(22),
+ user: z.string().min(1),
+ path: z.string().min(1),
+ privateKey: z.string().min(1),
+ skipHostKeyCheck: z.boolean().default(true),
+ knownHosts: z.string().optional(),
+ })
+ .extend(baseRepositoryConfigSchema.shape);
-export const repositoryConfigSchemaBase = s3RepositoryConfigSchema
- .or(r2RepositoryConfigSchema)
- .or(localRepositoryConfigSchema)
- .or(gcsRepositoryConfigSchema)
- .or(azureRepositoryConfigSchema)
- .or(rcloneRepositoryConfigSchema)
- .or(restRepositoryConfigSchema)
- .or(sftpRepositoryConfigSchema);
+export const repositoryConfigSchemaBase = z.discriminatedUnion("backend", [
+ s3RepositoryConfigSchema,
+ r2RepositoryConfigSchema,
+ localRepositoryConfigSchema,
+ gcsRepositoryConfigSchema,
+ azureRepositoryConfigSchema,
+ rcloneRepositoryConfigSchema,
+ restRepositoryConfigSchema,
+ sftpRepositoryConfigSchema,
+]);
-export const repositoryConfigSchema = repositoryConfigSchemaBase.onUndeclaredKey("delete");
+export const repositoryConfigSchema = repositoryConfigSchemaBase;
-export type RepositoryConfig = typeof repositoryConfigSchema.infer;
+export type RepositoryConfig = z.infer;
export const COMPRESSION_MODES = {
off: "off",
@@ -132,22 +154,22 @@ export const REPOSITORY_STATUS = {
export type RepositoryStatus = keyof typeof REPOSITORY_STATUS;
-export const doctorStepSchema = type({
- step: "string",
- success: "boolean",
- output: "string | null",
- error: "string | null",
+export const doctorStepSchema = z.object({
+ step: z.string(),
+ success: z.boolean(),
+ output: z.string().nullable(),
+ error: z.string().nullable(),
});
-export type DoctorStep = typeof doctorStepSchema.infer;
+export type DoctorStep = z.infer;
-export const doctorResultSchema = type({
- success: "boolean",
+export const doctorResultSchema = z.object({
+ success: z.boolean(),
steps: doctorStepSchema.array(),
- completedAt: "number",
+ completedAt: z.number(),
});
-export type DoctorResult = typeof doctorResultSchema.infer;
+export type DoctorResult = z.infer;
export const OVERWRITE_MODES = {
always: "always",
diff --git a/app/schemas/volumes.ts b/app/schemas/volumes.ts
index dd57e713..cec43538 100644
--- a/app/schemas/volumes.ts
+++ b/app/schemas/volumes.ts
@@ -1,4 +1,4 @@
-import { type } from "arktype";
+import { z } from "zod";
export const BACKEND_TYPES = {
nfs: "nfs",
@@ -11,75 +11,93 @@ export const BACKEND_TYPES = {
export type BackendType = keyof typeof BACKEND_TYPES;
-export const nfsConfigSchema = type({
- backend: "'nfs'",
- server: "string",
- exportPath: "string",
- port: type("string.integer").or(type("number")).to("1 <= number <= 65536").default(2049),
- version: "'3' | '4' | '4.1'",
- readOnly: "boolean?",
+export const nfsConfigSchema = z.object({
+ backend: z.literal("nfs"),
+ server: z.string().min(1),
+ exportPath: z.string().min(1),
+ port: z
+ .union([z.string(), z.number()])
+ .transform((value) => (typeof value === "string" ? Number.parseInt(value, 10) : value))
+ .pipe(z.number().int().min(1).max(65535))
+ .default(2049),
+ version: z.enum(["3", "4", "4.1"]),
+ readOnly: z.boolean().optional(),
});
-export const smbConfigSchema = type({
- backend: "'smb'",
- server: "string",
- share: "string",
- username: "string?",
- password: "string?",
- guest: "boolean?",
- vers: type("'1.0' | '2.0' | '2.1' | '3.0' | 'auto'").default("auto"),
- domain: "string?",
- port: type("string.integer").or(type("number")).to("1 <= number <= 65535").default(445),
- readOnly: "boolean?",
+export const smbConfigSchema = z.object({
+ backend: z.literal("smb"),
+ server: z.string().min(1),
+ share: z.string().min(1),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ guest: z.boolean().optional(),
+ vers: z.enum(["1.0", "2.0", "2.1", "3.0", "auto"]).default("auto"),
+ domain: z.string().optional(),
+ port: z
+ .union([z.string(), z.number()])
+ .transform((value) => (typeof value === "string" ? Number.parseInt(value, 10) : value))
+ .pipe(z.number().int().min(1).max(65535))
+ .default(445),
+ readOnly: z.boolean().optional(),
});
-export const directoryConfigSchema = type({
- backend: "'directory'",
- path: "string",
- readOnly: "false?",
+export const directoryConfigSchema = z.object({
+ backend: z.literal("directory"),
+ path: z.string().min(1),
+ readOnly: z.literal(false).optional(),
});
-export const webdavConfigSchema = type({
- backend: "'webdav'",
- server: "string",
- path: "string",
- username: "string?",
- password: "string?",
- port: type("string.integer").or(type("number")).to("1 <= number <= 65536").default(80),
- readOnly: "boolean?",
- ssl: "boolean?",
+export const webdavConfigSchema = z.object({
+ backend: z.literal("webdav"),
+ server: z.string().min(1),
+ path: z.string().min(1),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ port: z
+ .union([z.string(), z.number()])
+ .transform((value) => (typeof value === "string" ? Number.parseInt(value, 10) : value))
+ .pipe(z.number().int().min(1).max(65535))
+ .default(80),
+ readOnly: z.boolean().optional(),
+ ssl: z.boolean().optional(),
});
-export const rcloneConfigSchema = type({
- backend: "'rclone'",
- remote: "string",
- path: "string",
- readOnly: "boolean?",
+export const rcloneConfigSchema = z.object({
+ backend: z.literal("rclone"),
+ remote: z.string().min(1),
+ path: z.string().min(1),
+ readOnly: z.boolean().optional(),
});
-export const sftpConfigSchema = type({
- backend: "'sftp'",
- host: "string",
- port: type("string.integer").or(type("number")).to("1 <= number <= 65535").default(22),
- username: "string",
- password: "string?",
- privateKey: "string?",
- path: "string",
- readOnly: "boolean?",
- skipHostKeyCheck: "boolean = true",
- knownHosts: "string?",
+export const sftpConfigSchema = z.object({
+ backend: z.literal("sftp"),
+ host: z.string().min(1),
+ port: z
+ .union([z.string(), z.number()])
+ .transform((value) => (typeof value === "string" ? Number.parseInt(value, 10) : value))
+ .pipe(z.number().int().min(1).max(65535))
+ .default(22),
+ username: z.string().min(1),
+ password: z.string().optional(),
+ privateKey: z.string().optional(),
+ path: z.string().min(1),
+ readOnly: z.boolean().optional(),
+ skipHostKeyCheck: z.boolean().default(true),
+ knownHosts: z.string().optional(),
});
-export const volumeConfigSchemaBase = nfsConfigSchema
- .or(smbConfigSchema)
- .or(webdavConfigSchema)
- .or(directoryConfigSchema)
- .or(rcloneConfigSchema)
- .or(sftpConfigSchema);
+export const volumeConfigSchemaBase = z.discriminatedUnion("backend", [
+ nfsConfigSchema,
+ smbConfigSchema,
+ webdavConfigSchema,
+ directoryConfigSchema,
+ rcloneConfigSchema,
+ sftpConfigSchema,
+]);
-export const volumeConfigSchema = volumeConfigSchemaBase.onUndeclaredKey("delete");
+export const volumeConfigSchema = volumeConfigSchemaBase;
-export type BackendConfig = typeof volumeConfigSchema.infer;
+export type BackendConfig = z.infer;
export const BACKEND_STATUS = {
mounted: "mounted",
diff --git a/app/server/core/config.ts b/app/server/core/config.ts
index a4392dc4..18284b95 100644
--- a/app/server/core/config.ts
+++ b/app/server/core/config.ts
@@ -1,6 +1,6 @@
import { readFileSync } from "node:fs";
import os from "node:os";
-import { type } from "arktype";
+import { z } from "zod";
import "dotenv/config";
const getResticHostname = () => {
@@ -24,43 +24,45 @@ const getResticHostname = () => {
return "zerobyte";
};
-const envSchema = type({
- NODE_ENV: type.enumerated("development", "production", "test").default("production"),
- SERVER_IP: 'string = "localhost"',
- SERVER_IDLE_TIMEOUT: 'string.integer.parse = "60"',
- RESTIC_HOSTNAME: "string?",
- PORT: 'string.integer.parse = "4096"',
- MIGRATIONS_PATH: "string?",
- APP_VERSION: "string = 'dev'",
- TRUSTED_ORIGINS: "string?",
- DISABLE_RATE_LIMITING: 'string = "false"',
- APP_SECRET: "32 <= string <= 256",
- BASE_URL: "string",
- ENABLE_DEV_PANEL: 'string = "false"',
-}).pipe((s) => ({
- __prod__: s.NODE_ENV === "production",
- environment: s.NODE_ENV,
- serverIp: s.SERVER_IP,
- serverIdleTimeout: s.SERVER_IDLE_TIMEOUT,
- resticHostname: s.RESTIC_HOSTNAME || getResticHostname(),
- port: s.PORT,
- migrationsPath: s.MIGRATIONS_PATH,
- appVersion: s.APP_VERSION,
- trustedOrigins: s.TRUSTED_ORIGINS?.split(",")
- .map((origin) => origin.trim())
- .filter(Boolean)
- .concat(s.BASE_URL) ?? [s.BASE_URL],
- disableRateLimiting: s.DISABLE_RATE_LIMITING === "true",
- appSecret: s.APP_SECRET,
- baseUrl: s.BASE_URL,
- isSecure: s.BASE_URL?.startsWith("https://") ?? false,
- enableDevPanel: s.ENABLE_DEV_PANEL === "true",
-}));
+const envSchema = z
+ .object({
+ NODE_ENV: z.enum(["development", "production", "test"]).default("production"),
+ SERVER_IP: z.string().default("localhost"),
+ SERVER_IDLE_TIMEOUT: z.coerce.number().int().default(60),
+ RESTIC_HOSTNAME: z.string().optional(),
+ PORT: z.coerce.number().int().default(4096),
+ MIGRATIONS_PATH: z.string().optional(),
+ APP_VERSION: z.string().default("dev"),
+ TRUSTED_ORIGINS: z.string().optional(),
+ DISABLE_RATE_LIMITING: z.string().default("false"),
+ APP_SECRET: z.string().min(32).max(256),
+ BASE_URL: z.string(),
+ ENABLE_DEV_PANEL: z.string().default("false"),
+ })
+ .transform((s) => ({
+ __prod__: s.NODE_ENV === "production",
+ environment: s.NODE_ENV,
+ serverIp: s.SERVER_IP,
+ serverIdleTimeout: s.SERVER_IDLE_TIMEOUT,
+ resticHostname: s.RESTIC_HOSTNAME || getResticHostname(),
+ port: s.PORT,
+ migrationsPath: s.MIGRATIONS_PATH,
+ appVersion: s.APP_VERSION,
+ trustedOrigins: s.TRUSTED_ORIGINS?.split(",")
+ .map((origin) => origin.trim())
+ .filter(Boolean)
+ .concat(s.BASE_URL) ?? [s.BASE_URL],
+ disableRateLimiting: s.DISABLE_RATE_LIMITING === "true",
+ appSecret: s.APP_SECRET,
+ baseUrl: s.BASE_URL,
+ isSecure: s.BASE_URL?.startsWith("https://") ?? false,
+ enableDevPanel: s.ENABLE_DEV_PANEL === "true",
+ }));
const parseConfig = (env: unknown) => {
- const result = envSchema(env);
+ const result = envSchema.safeParse(env);
- if (result instanceof type.errors) {
+ if (!result.success) {
if (!process.env.APP_SECRET) {
const errorMessage = [
"",
@@ -82,11 +84,12 @@ const parseConfig = (env: unknown) => {
console.error(errorMessage);
}
- console.error(`Environment variable validation failed: ${result.summary}`);
+
+ console.error(`Environment variable validation failed: ${result.error.message}`);
throw new Error("Invalid environment variables");
}
- return result;
+ return result.data;
};
export const config = parseConfig(process.env);
diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts
index 0d5e0f39..dc9e719a 100644
--- a/app/server/db/schema.ts
+++ b/app/server/db/schema.ts
@@ -3,14 +3,14 @@ import { index, int, integer, sqliteTable, text, real, primaryKey, unique, uniqu
import type {
CompressionMode,
RepositoryBackend,
- repositoryConfigSchema,
+ RepositoryConfig,
RepositoryStatus,
BandwidthUnit,
DoctorResult,
} from "~/schemas/restic";
import type { ResticStatsDto } from "~/schemas/restic-dto";
-import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
-import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications";
+import type { BackendConfig, BackendStatus, BackendType } from "~/schemas/volumes";
+import type { NotificationConfig, NotificationType } from "~/schemas/notifications";
import type { ShortId } from "~/server/utils/branded";
/**
@@ -217,7 +217,7 @@ export const volumesTable = sqliteTable(
.notNull()
.$onUpdate(() => Date.now())
.default(sql`(unixepoch() * 1000)`),
- config: text("config", { mode: "json" }).$type().notNull(),
+ config: text("config", { mode: "json" }).$type().notNull(),
autoRemount: int("auto_remount", { mode: "boolean" }).notNull().default(true),
organizationId: text("organization_id")
.notNull()
@@ -236,7 +236,7 @@ export const repositoriesTable = sqliteTable("repositories_table", {
shortId: text("short_id").$type().notNull().unique(),
name: text().notNull(),
type: text().$type().notNull(),
- config: text("config", { mode: "json" }).$type().notNull(),
+ config: text("config", { mode: "json" }).$type().notNull(),
compressionMode: text("compression_mode").$type().default("auto"),
status: text().$type().default("unknown"),
lastChecked: int("last_checked", { mode: "number" }),
@@ -319,7 +319,7 @@ export const notificationDestinationsTable = sqliteTable("notification_destinati
name: text().notNull(),
enabled: int("enabled", { mode: "boolean" }).notNull().default(true),
type: text().$type().notNull(),
- config: text("config", { mode: "json" }).$type().notNull(),
+ config: text("config", { mode: "json" }).$type().notNull(),
createdAt: int("created_at", { mode: "number" })
.notNull()
.default(sql`(unixepoch() * 1000)`),
diff --git a/app/server/modules/auth/auth.dto.ts b/app/server/modules/auth/auth.dto.ts
index eee61222..942d1ffb 100644
--- a/app/server/modules/auth/auth.dto.ts
+++ b/app/server/modules/auth/auth.dto.ts
@@ -1,26 +1,30 @@
-import { type } from "arktype";
+import { z } from "zod";
import { describeRoute, resolver } from "hono-openapi";
-const statusResponseSchema = type({
- hasUsers: "boolean",
+const statusResponseSchema = z.object({
+ hasUsers: z.boolean(),
});
-export const adminUsersResponse = type({
- users: type({
- id: "string",
- name: "string | null",
- email: "string",
- role: "string",
- banned: "boolean",
- accounts: type({
- id: "string",
- providerId: "string",
- }).array(),
- }).array(),
- total: "number",
+export const adminUsersResponse = z.object({
+ users: z
+ .object({
+ id: z.string(),
+ name: z.string().nullable(),
+ email: z.string(),
+ role: z.string(),
+ banned: z.boolean(),
+ accounts: z
+ .object({
+ id: z.string(),
+ providerId: z.string(),
+ })
+ .array(),
+ })
+ .array(),
+ total: z.number(),
});
-export type AdminUsersDto = typeof adminUsersResponse.infer;
+export type AdminUsersDto = z.infer;
export const getAdminUsersDto = describeRoute({
description: "List admin users for settings management",
@@ -54,21 +58,23 @@ export const getStatusDto = describeRoute({
},
});
-export type GetStatusDto = typeof statusResponseSchema.infer;
+export type GetStatusDto = z.infer;
-export const userDeletionImpactDto = type({
- organizations: type({
- id: "string",
- name: "string",
- resources: {
- volumesCount: "number",
- repositoriesCount: "number",
- backupSchedulesCount: "number",
- },
- }).array(),
+export const userDeletionImpactDto = z.object({
+ organizations: z
+ .object({
+ id: z.string(),
+ name: z.string(),
+ resources: z.object({
+ volumesCount: z.number(),
+ repositoriesCount: z.number(),
+ backupSchedulesCount: z.number(),
+ }),
+ })
+ .array(),
});
-export type UserDeletionImpactDto = typeof userDeletionImpactDto.infer;
+export type UserDeletionImpactDto = z.infer;
export const getUserDeletionImpactDto = describeRoute({
description: "Get impact of deleting a user",
@@ -103,20 +109,22 @@ export const deleteUserAccountDto = describeRoute({
},
});
-export const orgMembersResponse = type({
- members: type({
- id: "string",
- userId: "string",
- role: "string",
- createdAt: "string",
- user: {
- name: "string | null",
- email: "string",
- },
- }).array(),
+export const orgMembersResponse = z.object({
+ members: z
+ .object({
+ id: z.string(),
+ userId: z.string(),
+ role: z.string(),
+ createdAt: z.string(),
+ user: z.object({
+ name: z.string().nullable(),
+ email: z.string(),
+ }),
+ })
+ .array(),
});
-export type OrgMembersDto = typeof orgMembersResponse.infer;
+export type OrgMembersDto = z.infer;
export const getOrgMembersDto = describeRoute({
description: "Get members of the active organization",
@@ -134,8 +142,8 @@ export const getOrgMembersDto = describeRoute({
},
});
-export const updateMemberRoleBody = type({
- role: "'member' | 'admin'",
+export const updateMemberRoleBody = z.object({
+ role: z.enum(["member", "admin"]),
});
export const updateMemberRoleDto = describeRoute({
diff --git a/app/server/modules/backends/backend.ts b/app/server/modules/backends/backend.ts
index 25f51411..25811167 100644
--- a/app/server/modules/backends/backend.ts
+++ b/app/server/modules/backends/backend.ts
@@ -41,5 +41,8 @@ export const createVolumeBackend = (volume: Volume): VolumeBackend => {
case "sftp": {
return makeSftpBackend(volume.config, path);
}
+ default: {
+ throw new Error("Unsupported backend");
+ }
}
};
diff --git a/app/server/modules/backups/backups.dto.ts b/app/server/modules/backups/backups.dto.ts
index 560bb147..5cd08fa6 100644
--- a/app/server/modules/backups/backups.dto.ts
+++ b/app/server/modules/backups/backups.dto.ts
@@ -1,67 +1,61 @@
-import { type } from "arktype";
+import { z } from "zod";
import { describeRoute, resolver } from "hono-openapi";
import { volumeSchema } from "../volumes/volume.dto";
import { repositorySchema } from "../repositories/repositories.dto";
import { backupProgressEventSchema } from "~/schemas/events-dto";
-const retentionPolicySchema = type({
- keepLast: "number?",
- keepHourly: "number?",
- keepDaily: "number?",
- keepWeekly: "number?",
- keepMonthly: "number?",
- keepYearly: "number?",
- keepWithinDuration: "string?",
+const retentionPolicySchema = z.object({
+ keepLast: z.number().optional(),
+ keepHourly: z.number().optional(),
+ keepDaily: z.number().optional(),
+ keepWeekly: z.number().optional(),
+ keepMonthly: z.number().optional(),
+ keepYearly: z.number().optional(),
+ keepWithinDuration: z.string().optional(),
});
-export type RetentionPolicy = typeof retentionPolicySchema.infer;
+export type RetentionPolicy = z.infer;
-const backupScheduleSchema = type({
- id: "number",
- shortId: "string",
- name: "string",
- volumeId: "number",
- repositoryId: "string",
- enabled: "boolean",
- cronExpression: "string",
- retentionPolicy: retentionPolicySchema.or("null"),
- excludePatterns: "string[] | null",
- excludeIfPresent: "string[] | null",
- includePatterns: "string[] | null",
- oneFileSystem: "boolean",
- customResticParams: "string[] | null",
- lastBackupAt: "number | null",
- lastBackupStatus: "'success' | 'error' | 'in_progress' | 'warning' | null",
- lastBackupError: "string | null",
- nextBackupAt: "number | null",
- createdAt: "number",
- updatedAt: "number",
-}).and(
- type({
- volume: volumeSchema,
- repository: repositorySchema,
- }),
-);
-
-const scheduleMirrorSchema = type({
- scheduleId: "string",
- repositoryId: "string",
- enabled: "boolean",
- lastCopyAt: "number | null",
- lastCopyStatus: "'success' | 'error' | 'in_progress' | null",
- lastCopyError: "string | null",
- createdAt: "number",
+const backupScheduleSchema = z.object({
+ id: z.number(),
+ shortId: z.string(),
+ name: z.string(),
+ volumeId: z.number(),
+ repositoryId: z.string(),
+ enabled: z.boolean(),
+ cronExpression: z.string(),
+ retentionPolicy: retentionPolicySchema.nullable(),
+ excludePatterns: z.array(z.string()).nullable(),
+ excludeIfPresent: z.array(z.string()).nullable(),
+ includePatterns: z.array(z.string()).nullable(),
+ oneFileSystem: z.boolean(),
+ customResticParams: z.array(z.string()).nullable(),
+ lastBackupAt: z.number().nullable(),
+ lastBackupStatus: z.enum(["success", "error", "in_progress", "warning"]).nullable(),
+ lastBackupError: z.string().nullable(),
+ nextBackupAt: z.number().nullable(),
+ createdAt: z.number(),
+ updatedAt: z.number(),
+ volume: volumeSchema,
repository: repositorySchema,
});
-export type ScheduleMirrorDto = typeof scheduleMirrorSchema.infer;
+const scheduleMirrorSchema = z.object({
+ scheduleId: z.string(),
+ repositoryId: z.string(),
+ enabled: z.boolean(),
+ lastCopyAt: z.number().nullable(),
+ lastCopyStatus: z.enum(["success", "error", "in_progress"]).nullable(),
+ lastCopyError: z.string().nullable(),
+ createdAt: z.number(),
+ repository: repositorySchema,
+});
+
+export type ScheduleMirrorDto = z.infer;
-/**
- * List all backup schedules
- */
export const listBackupSchedulesResponse = backupScheduleSchema.array();
-export type ListBackupSchedulesResponseDto = typeof listBackupSchedulesResponse.infer;
+export type ListBackupSchedulesResponseDto = z.infer;
export const listBackupSchedulesDto = describeRoute({
description: "List all backup schedules",
@@ -79,12 +73,9 @@ export const listBackupSchedulesDto = describeRoute({
},
});
-/**
- * Get a single backup schedule
- */
export const getBackupScheduleResponse = backupScheduleSchema;
-export type GetBackupScheduleDto = typeof getBackupScheduleResponse.infer;
+export type GetBackupScheduleDto = z.infer;
export const getBackupScheduleDto = describeRoute({
description: "Get a backup schedule by ID",
@@ -102,9 +93,9 @@ export const getBackupScheduleDto = describeRoute({
},
});
-export const getBackupScheduleForVolumeResponse = backupScheduleSchema.or("null");
+export const getBackupScheduleForVolumeResponse = backupScheduleSchema.nullable();
-export type GetBackupScheduleForVolumeResponseDto = typeof getBackupScheduleForVolumeResponse.infer;
+export type GetBackupScheduleForVolumeResponseDto = z.infer;
export const getBackupScheduleForVolumeDto = describeRoute({
description: "Get a backup schedule for a specific volume",
@@ -122,29 +113,26 @@ export const getBackupScheduleForVolumeDto = describeRoute({
},
});
-/**
- * Create a new backup schedule
- */
-export const createBackupScheduleBody = type({
- name: "1 <= string <= 128",
- volumeId: "string | number",
- repositoryId: "string",
- enabled: "boolean",
- cronExpression: "string",
+export const createBackupScheduleBody = z.object({
+ name: z.string().min(1).max(128),
+ volumeId: z.union([z.string(), z.number()]),
+ repositoryId: z.string(),
+ enabled: z.boolean(),
+ cronExpression: z.string(),
retentionPolicy: retentionPolicySchema.optional(),
- excludePatterns: "string[]?",
- excludeIfPresent: "string[]?",
- includePatterns: "string[]?",
- oneFileSystem: "boolean?",
- tags: "string[]?",
- customResticParams: "string[]?",
+ excludePatterns: z.array(z.string()).optional(),
+ excludeIfPresent: z.array(z.string()).optional(),
+ includePatterns: z.array(z.string()).optional(),
+ oneFileSystem: z.boolean().optional(),
+ tags: z.array(z.string()).optional(),
+ customResticParams: z.array(z.string()).optional(),
});
-export type CreateBackupScheduleBody = typeof createBackupScheduleBody.infer;
+export type CreateBackupScheduleBody = z.infer