chore: various fixes
fix(create-schedule): expand folder button submitting the form style(schedule-form): fix multi scrollbar on long file list
This commit is contained in:
parent
5fc17950ba
commit
6e68c75ef1
12 changed files with 65 additions and 15 deletions
5
.env.example
Normal file
5
.env.example
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
DATABASE_URL=./data/zerobyte.db
|
||||
RESTIC_PASS_FILE=./data/restic.pass
|
||||
RESTIC_CACHE_DIR=./data/restic/cache
|
||||
ZEROBYTE_REPOSITORIES_DIR=./data/repositories
|
||||
ZEROBYTE_VOLUMES_DIR=./data/volumes
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -14,3 +14,9 @@ mutagen.yml.lock
|
|||
notes.md
|
||||
smb-password.txt
|
||||
cache.db
|
||||
|
||||
data/
|
||||
|
||||
.env*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -70,6 +70,32 @@ docker compose up -d
|
|||
|
||||
Once the container is running, you can access the web interface at `http://<your-server-ip>:4096`.
|
||||
|
||||
## Development (no Docker)
|
||||
|
||||
You can run Zerobyte locally during development without Docker:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
bun run dev
|
||||
```
|
||||
|
||||
For local development, create a `.env.local` file at the repo root and override the Docker paths:
|
||||
|
||||
```bash
|
||||
# Example
|
||||
DATABASE_URL=./data/zerobyte.db
|
||||
RESTIC_PASS_FILE=./data/restic.pass
|
||||
RESTIC_CACHE_DIR=./data/restic/cache
|
||||
ZEROBYTE_REPOSITORIES_DIR=./data/repositories
|
||||
ZEROBYTE_VOLUMES_DIR=./data/volumes
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Remote mount backends (NFS/SMB/WebDAV/SFTP) rely on Linux mount tooling and `CAP_SYS_ADMIN`; on macOS they are expected to be unavailable.
|
||||
- To actually run backups/repository checks, install `restic` on your machine (e.g. via Homebrew). If `restic` is not installed, the app still starts but backup operations will fail with a clear error.
|
||||
- rclone support is detected via `RCLONE_CONFIG` or `~/.config/rclone/rclone.conf`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Zerobyte can be customized using environment variables. Below are the available options:
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@ const NodeButton = memo(({ depth, icon, onClick, onMouseEnter, className, childr
|
|||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn("flex items-center gap-2 w-full pr-2 text-sm py-1.5 text-left", className)}
|
||||
style={{ paddingLeft }}
|
||||
onClick={onClick}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function GridBackground({ children, className, containerClassName }: Grid
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative min-h-full w-full overflow-x-hidden",
|
||||
"relative min-h-full w-full",
|
||||
"bg-size-[20px_20px] sm:bg-size-[40px_40px]",
|
||||
"bg-[linear-gradient(to_right,#e4e4e7_1px,transparent_1px),linear-gradient(to_bottom,#e4e4e7_1px,transparent_1px)]",
|
||||
"dark:bg-[linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { FolderOpen } from "lucide-react";
|
|||
import { FileTree } from "~/client/components/file-tree";
|
||||
import { listFilesOptions } from "../api-client/@tanstack/react-query.gen";
|
||||
import { useFileBrowser } from "../hooks/use-file-browser";
|
||||
import { parseError } from "../lib/errors";
|
||||
|
||||
type VolumeFileBrowserProps = {
|
||||
volumeName: string;
|
||||
|
|
@ -66,7 +67,7 @@ export const VolumeFileBrowser = ({
|
|||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full min-h-50">
|
||||
<p className="text-destructive">Failed to load files: {(error as Error).message}</p>
|
||||
<p className="text-destructive">Failed to load files: {parseError(error)?.message}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import { X } from "lucide-react";
|
|||
import { useCallback, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { listRepositoriesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
import { CronInput } from "~/client/components/cron-input";
|
||||
import { RepositoryIcon } from "~/client/components/repository-icon";
|
||||
import { Button } from "~/client/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||
import { Checkbox } from "~/client/components/ui/checkbox";
|
||||
import {
|
||||
|
|
@ -20,13 +22,11 @@ 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 { Button } from "~/client/components/ui/button";
|
||||
import { Textarea } from "~/client/components/ui/textarea";
|
||||
import { VolumeFileBrowser } from "~/client/components/volume-file-browser";
|
||||
import { CronInput } from "~/client/components/cron-input";
|
||||
import { cronToFormValues } from "../lib/cron-utils";
|
||||
import type { BackupSchedule, Volume } from "~/client/lib/types";
|
||||
import { deepClean } from "~/utils/object";
|
||||
import { cronToFormValues } from "../lib/cron-utils";
|
||||
|
||||
const internalFormSchema = type({
|
||||
name: "1 <= string <= 32",
|
||||
|
|
@ -377,7 +377,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
|||
onSelectionChange={handleSelectionChange}
|
||||
withCheckboxes={true}
|
||||
foldersOnly={false}
|
||||
className="flex-1 border rounded-md bg-card p-2 min-h-75 max-h-100 overflow-auto"
|
||||
className="relative border rounded-md bg-card p-2 h-100 overflow-y-auto"
|
||||
/>
|
||||
{selectedPaths.size > 0 && (
|
||||
<div className="mt-4">
|
||||
|
|
@ -641,8 +641,8 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
|||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="h-full">
|
||||
<Card className="h-full">
|
||||
<div className="xl:sticky xl:top-6 xl:self-start">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between gap-4">
|
||||
<div>
|
||||
<CardTitle>Schedule summary</CardTitle>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import * as fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
export type SystemCapabilities = {
|
||||
|
|
@ -55,6 +57,11 @@ async function detectRclone(): Promise<boolean> {
|
|||
}
|
||||
|
||||
async function detectSysAdmin(): Promise<boolean> {
|
||||
if (process.platform !== "linux") {
|
||||
logger.warn("sysAdmin capability: disabled. Non-Linux platform detected");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const procStatus = await fs.readFile("/proc/self/status", "utf-8");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
export const OPERATION_TIMEOUT = 5000;
|
||||
export const VOLUME_MOUNT_BASE = "/var/lib/zerobyte/volumes";
|
||||
export const REPOSITORY_BASE = "/var/lib/zerobyte/repositories";
|
||||
|
||||
export const VOLUME_MOUNT_BASE = process.env.ZEROBYTE_VOLUMES_DIR || "/var/lib/zerobyte/volumes";
|
||||
export const REPOSITORY_BASE = process.env.ZEROBYTE_REPOSITORIES_DIR || "/var/lib/zerobyte/repositories";
|
||||
|
||||
export const RESTIC_CACHE_DIR = process.env.RESTIC_CACHE_DIR || "/var/lib/zerobyte/restic/cache";
|
||||
|
||||
export const DATABASE_URL = process.env.DATABASE_URL || "/var/lib/zerobyte/data/ironmount.db";
|
||||
export const RESTIC_PASS_FILE = "/var/lib/zerobyte/data/restic.pass";
|
||||
export const RESTIC_PASS_FILE = process.env.RESTIC_PASS_FILE || "/var/lib/zerobyte/data/restic.pass";
|
||||
|
||||
export const DEFAULT_EXCLUDES = [DATABASE_URL, RESTIC_PASS_FILE, REPOSITORY_BASE];
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const runDbMigrations = () => {
|
|||
} else if (config.__prod__) {
|
||||
migrationsFolder = path.join("/app", "assets", "migrations");
|
||||
} else {
|
||||
migrationsFolder = path.join("/app", "app", "drizzle");
|
||||
migrationsFolder = path.join(process.cwd(), "app", "drizzle");
|
||||
}
|
||||
|
||||
migrate(db, { migrationsFolder });
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import path from "node:path";
|
|||
import os from "node:os";
|
||||
import { throttle } from "es-toolkit";
|
||||
import { type } from "arktype";
|
||||
import { REPOSITORY_BASE, RESTIC_PASS_FILE, DEFAULT_EXCLUDES } from "../core/constants";
|
||||
import { REPOSITORY_BASE, RESTIC_PASS_FILE, DEFAULT_EXCLUDES, RESTIC_CACHE_DIR } from "../core/constants";
|
||||
import { config as appConfig } from "../core/config";
|
||||
import { logger } from "./logger";
|
||||
import { cryptoUtils } from "./crypto";
|
||||
|
|
@ -107,7 +107,7 @@ export const buildRepoUrl = (config: RepositoryConfig): string => {
|
|||
|
||||
export const buildEnv = async (config: RepositoryConfig) => {
|
||||
const env: Record<string, string> = {
|
||||
RESTIC_CACHE_DIR: "/var/lib/zerobyte/restic/cache",
|
||||
RESTIC_CACHE_DIR,
|
||||
PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"scripts": {
|
||||
"lint": "oxlint --type-aware",
|
||||
"build": "react-router build",
|
||||
"dev": "bunx --bun vite",
|
||||
"dev": "NODE_ENV=development bunx --bun vite",
|
||||
"start": "bun ./dist/server/index.js",
|
||||
"cli:dev": "bun run app/server/cli/main.ts",
|
||||
"cli": "ZEROBYTE_CLI=1 bun ./dist/server/index.js",
|
||||
|
|
|
|||
Loading…
Reference in a new issue