refactor: server constants

This commit is contained in:
Nicolas Meienberger 2026-02-14 00:12:41 +01:00
parent c1c8138197
commit ed80131a9b
8 changed files with 43 additions and 18 deletions

View file

@ -1,2 +0,0 @@
export const REPOSITORY_BASE = "/var/lib/zerobyte/repositories";
export const REGISTRATION_ENABLED_KEY = "registrations_enabled";

View file

@ -33,7 +33,9 @@ import {
SftpRepositoryForm, SftpRepositoryForm,
AdvancedForm, AdvancedForm,
} from "./repository-forms"; } from "./repository-forms";
import { REPOSITORY_BASE } from "~/client/lib/constants"; import { useServerFn } from "@tanstack/react-start";
import { getServerConstants } from "~/server/core/constants";
import { useSuspenseQuery } from "@tanstack/react-query";
export const formSchema = type({ export const formSchema = type({
name: "2<=string<=32", name: "2<=string<=32",
@ -52,8 +54,8 @@ type Props = {
className?: string; className?: string;
}; };
const defaultValuesForType = { const defaultValuesForType = (repoBase: string) => ({
local: { backend: "local" as const, compressionMode: "auto" as const, path: REPOSITORY_BASE }, local: { backend: "local" as const, compressionMode: "auto" as const, path: repoBase },
s3: { backend: "s3" as const, compressionMode: "auto" as const }, s3: { backend: "s3" as const, compressionMode: "auto" as const },
r2: { backend: "r2" as const, compressionMode: "auto" as const }, r2: { backend: "r2" as const, compressionMode: "auto" as const },
gcs: { backend: "gcs" as const, compressionMode: "auto" as const }, gcs: { backend: "gcs" as const, compressionMode: "auto" as const },
@ -61,7 +63,7 @@ const defaultValuesForType = {
rclone: { backend: "rclone" as const, compressionMode: "auto" as const }, rclone: { backend: "rclone" as const, compressionMode: "auto" as const },
rest: { backend: "rest" as const, compressionMode: "auto" as const }, rest: { backend: "rest" as const, compressionMode: "auto" as const },
sftp: { backend: "sftp" as const, compressionMode: "auto" as const, port: 22, skipHostKeyCheck: false }, sftp: { backend: "sftp" as const, compressionMode: "auto" as const, port: 22, skipHostKeyCheck: false },
}; });
export const CreateRepositoryForm = ({ export const CreateRepositoryForm = ({
onSubmit, onSubmit,
@ -71,6 +73,12 @@ export const CreateRepositoryForm = ({
loading, loading,
className, className,
}: Props) => { }: Props) => {
const getConstants = useServerFn(getServerConstants);
const { data: constants } = useSuspenseQuery({
queryKey: ["server-constants"],
queryFn: getConstants,
});
const form = useForm<RepositoryFormValues>({ const form = useForm<RepositoryFormValues>({
resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema), resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema),
defaultValues: initialValues, defaultValues: initialValues,
@ -125,7 +133,9 @@ export const CreateRepositoryForm = ({
name: form.getValues().name, name: form.getValues().name,
isExistingRepository: form.getValues().isExistingRepository, isExistingRepository: form.getValues().isExistingRepository,
customPassword: form.getValues().customPassword, customPassword: form.getValues().customPassword,
...defaultValuesForType[value as keyof typeof defaultValuesForType], ...defaultValuesForType(constants.REPOSITORY_BASE)[
value as keyof ReturnType<typeof defaultValuesForType>
],
}); });
}} }}
value={field.value} value={field.value}

View file

@ -1,7 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import type { UseFormReturn } from "react-hook-form"; import type { UseFormReturn } from "react-hook-form";
import { Check, Pencil, X, AlertTriangle } from "lucide-react"; import { Check, Pencil, X, AlertTriangle } from "lucide-react";
import { REPOSITORY_BASE } from "~/client/lib/constants";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
import { FormItem, FormLabel, FormDescription, FormField, FormControl } from "../../../../components/ui/form"; import { FormItem, FormLabel, FormDescription, FormField, FormControl } from "../../../../components/ui/form";
import { DirectoryBrowser } from "../../../../components/directory-browser"; import { DirectoryBrowser } from "../../../../components/directory-browser";
@ -16,6 +15,9 @@ import {
AlertDialogTitle, AlertDialogTitle,
} from "../../../../components/ui/alert-dialog"; } from "../../../../components/ui/alert-dialog";
import type { RepositoryFormValues } from "../create-repository-form"; import type { RepositoryFormValues } from "../create-repository-form";
import { useServerFn } from "@tanstack/react-start";
import { getServerConstants } from "~/server/core/constants";
import { useSuspenseQuery } from "@tanstack/react-query";
type Props = { type Props = {
form: UseFormReturn<RepositoryFormValues>; form: UseFormReturn<RepositoryFormValues>;
@ -25,6 +27,12 @@ export const LocalRepositoryForm = ({ form }: Props) => {
const [showPathBrowser, setShowPathBrowser] = useState(false); const [showPathBrowser, setShowPathBrowser] = useState(false);
const [showPathWarning, setShowPathWarning] = useState(false); const [showPathWarning, setShowPathWarning] = useState(false);
const getConstants = useServerFn(getServerConstants);
const { data: constants } = useSuspenseQuery({
queryKey: ["server-constants"],
queryFn: getConstants,
});
return ( return (
<FormField <FormField
control={form.control} control={form.control}
@ -36,7 +44,7 @@ export const LocalRepositoryForm = ({ form }: Props) => {
<FormControl> <FormControl>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex-1 text-sm font-mono bg-muted px-3 py-2 rounded-md border"> <div className="flex-1 text-sm font-mono bg-muted px-3 py-2 rounded-md border">
{field.value || REPOSITORY_BASE} {field.value || constants.REPOSITORY_BASE}
</div> </div>
<Button type="button" variant="outline" onClick={() => setShowPathWarning(true)} size="sm"> <Button type="button" variant="outline" onClick={() => setShowPathWarning(true)} size="sm">
<Pencil className="h-4 w-4 mr-2" /> <Pencil className="h-4 w-4 mr-2" />
@ -60,8 +68,8 @@ export const LocalRepositoryForm = ({ form }: Props) => {
If the path is not a host mount, you will lose your repository data when the container restarts. If the path is not a host mount, you will lose your repository data when the container restarts.
</p> </p>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
The default path <code className="bg-muted px-1 rounded">{REPOSITORY_BASE}</code> is safe to use if The default path <code className="bg-muted px-1 rounded">{constants.REPOSITORY_BASE}</code> is safe
you followed the recommended Docker Compose setup. to use if you followed the recommended Docker Compose setup.
</p> </p>
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
@ -92,7 +100,7 @@ export const LocalRepositoryForm = ({ form }: Props) => {
onSelectPath={(path) => { onSelectPath={(path) => {
field.onChange(path); field.onChange(path);
}} }}
selectedPath={field.value || REPOSITORY_BASE} selectedPath={field.value || constants.REPOSITORY_BASE}
/> />
</div> </div>
<AlertDialogFooter> <AlertDialogFooter>

View file

@ -14,7 +14,6 @@ import {
AlertDialogTitle, AlertDialogTitle,
} from "~/client/components/ui/alert-dialog"; } from "~/client/components/ui/alert-dialog";
import type { Repository } from "~/client/lib/types"; import type { Repository } from "~/client/lib/types";
import { REPOSITORY_BASE } from "~/client/lib/constants";
import { formatDateTime, formatTimeAgo } from "~/client/lib/datetime"; import { formatDateTime, formatTimeAgo } from "~/client/lib/datetime";
import { import {
cancelDoctorMutation, cancelDoctorMutation,

View file

@ -1,3 +1,5 @@
import { createServerFn } from "@tanstack/react-start";
export const OPERATION_TIMEOUT = 5000; export const OPERATION_TIMEOUT = 5000;
export const VOLUME_MOUNT_BASE = process.env.ZEROBYTE_VOLUMES_DIR || "/var/lib/zerobyte/volumes"; export const VOLUME_MOUNT_BASE = process.env.ZEROBYTE_VOLUMES_DIR || "/var/lib/zerobyte/volumes";
@ -11,3 +13,12 @@ export const RESTIC_PASS_FILE = process.env.RESTIC_PASS_FILE || "/var/lib/zeroby
export const RCLONE_CONFIG_DIR = process.env.RCLONE_CONFIG_DIR || "/root/.config/rclone"; export const RCLONE_CONFIG_DIR = process.env.RCLONE_CONFIG_DIR || "/root/.config/rclone";
export const DEFAULT_EXCLUDES = [DATABASE_URL, RESTIC_PASS_FILE, REPOSITORY_BASE]; export const DEFAULT_EXCLUDES = [DATABASE_URL, RESTIC_PASS_FILE, REPOSITORY_BASE];
export const REGISTRATION_ENABLED_KEY = "registrations_enabled";
export const getServerConstants = createServerFn({ method: "GET" }).handler(() => {
return {
REPOSITORY_BASE,
REGISTRATION_ENABLED_KEY,
};
});

View file

@ -2,7 +2,7 @@ import { db } from "~/server/db/db";
import type { AuthMiddlewareContext } from "../auth"; import type { AuthMiddlewareContext } from "../auth";
import { logger } from "~/server/utils/logger"; import { logger } from "~/server/utils/logger";
import { ForbiddenError } from "http-errors-enhanced"; import { ForbiddenError } from "http-errors-enhanced";
import { REGISTRATION_ENABLED_KEY } from "~/client/lib/constants"; import { REGISTRATION_ENABLED_KEY } from "~/server/core/constants";
export const ensureOnlyOneUser = async (ctx: AuthMiddlewareContext) => { export const ensureOnlyOneUser = async (ctx: AuthMiddlewareContext) => {
const { path } = ctx; const { path } = ctx;

View file

@ -3,8 +3,7 @@ import { db } from "../../../db/db";
import { repositoriesTable } from "../../../db/schema"; import { repositoriesTable } from "../../../db/schema";
import { logger } from "../../../utils/logger"; import { logger } from "../../../utils/logger";
import { toMessage } from "~/server/utils/errors"; import { toMessage } from "~/server/utils/errors";
import { REPOSITORY_BASE } from "~/server/core/constants";
const DEFAULT_LOCAL_REPOSITORY_ROOT = "/var/lib/zerobyte/repositories";
type MigrationError = { name: string; error: string }; type MigrationError = { name: string; error: string };
@ -31,7 +30,7 @@ const isPathAlreadyMigrated = (path: string, name: string): boolean => {
const buildPath = (path: string | null, name: string): string => { const buildPath = (path: string | null, name: string): string => {
if (path === null || path.trim() === "") { if (path === null || path.trim() === "") {
return `${DEFAULT_LOCAL_REPOSITORY_ROOT}/${name}`; return `${REPOSITORY_BASE}/${name}`;
} }
return `${trimTrailingSlashes(path)}/${name}`; return `${trimTrailingSlashes(path)}/${name}`;

View file

@ -6,7 +6,7 @@ import { cache } from "../../utils/cache";
import { logger } from "~/server/utils/logger"; import { logger } from "~/server/utils/logger";
import { db } from "../../db/db"; import { db } from "../../db/db";
import { appMetadataTable } from "../../db/schema"; import { appMetadataTable } from "../../db/schema";
import { REGISTRATION_ENABLED_KEY } from "~/client/lib/constants"; import { REGISTRATION_ENABLED_KEY } from "~/server/core/constants";
const CACHE_TTL = 60 * 60; const CACHE_TTL = 60 * 60;