From 1118f45788930b369f91a4fef0c6e8d48ab0d421 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Fri, 30 Jan 2026 18:01:41 +0100 Subject: [PATCH] chore: update readme with mandatory env variables --- AGENTS.md | 184 ------------------------------------------------------ README.md | 22 ++++--- 2 files changed, 12 insertions(+), 194 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c44518e2..d54b9a50 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,26 +9,6 @@ Zerobyte is a backup automation tool built on top of Restic that provides a web interface for scheduling, managing, and monitoring encrypted backups. It supports multiple volume backends (NFS, SMB, WebDAV, SFTP, local directories) and repository backends (S3, Azure, GCS, local, and rclone-based storage). -## Technology Stack - -- **Runtime**: Bun 1.3.1 -- **Server**: Hono (web framework) with Bun runtime -- **Client**: React Router v7 (SSR) with React 19 -- **Database**: SQLite with Drizzle ORM -- **Validation**: ArkType for runtime schema validation -- **Styling**: Tailwind CSS v4 + Radix UI components -- **Architecture**: Unified application structure (not a monorepo) -- **Code Quality**: Oxfmt for formatting, Oxlint for linting - -## Repository Structure - -This is a unified application with the following structure: - -- `app/server` - Bun-based API server with Hono -- `app/client` - React Router SSR frontend components and modules -- `app/schemas` - Shared ArkType schemas for validation -- `app/drizzle` - Database migrations - ### Type Checking ```bash @@ -67,8 +47,6 @@ bunx drizzle-kit generate --custom --name=fix-timestamps-to-ms ### API Client Generation ```bash -# Generate TypeScript API client from OpenAPI spec -# Note: Server is always running don't need to start it separately bun run gen:api-client ``` @@ -81,165 +59,3 @@ bunx oxfmt format --write # Lint bun run lint ``` - -## Architecture - -### Server Architecture - -The server follows a modular service-oriented architecture: - -**Entry Point**: `app/server/index.ts` - -- Initializes main API server on port 4096 (REST API + serves static frontend) - -**Modules** (`app/server/modules/`): -Each module follows a controller � service � database pattern: - -- `auth/` - User authentication and session management -- `volumes/` - Volume mounting/unmounting (NFS, SMB, WebDAV, SFTP, directories) -- `repositories/` - Restic repository management (S3, Azure, GCS, local, rclone) -- `backups/` - Backup schedule management and execution -- `notifications/` - Notification system with multiple providers (Discord, email, Gotify, Ntfy, Slack, Pushover) -- `driver/` - Docker volume plugin implementation -- `events/` - Server-Sent Events for real-time updates -- `system/` - System information and capabilities -- `lifecycle/` - Application startup/shutdown hooks - -**Backends** (`app/server/modules/backends/`): -Each volume backend (NFS, SMB, WebDAV, SFTP, directory) implements mounting logic using system tools (mount.nfs, mount.cifs, davfs2, sshfs). - -**Jobs** (`app/server/jobs/`): -Cron-based background jobs managed by the Scheduler: - -- `backup-execution.ts` - Runs scheduled backups (every minute) -- `cleanup-dangling.ts` - Removes stale mounts (hourly) -- `healthchecks.ts` - Checks volume health (every 5 minutes) -- `repository-healthchecks.ts` - Validates repositories (every 10 minutes) -- `cleanup-sessions.ts` - Expires old sessions (daily) - -**Core** (`app/server/core/`): - -- `scheduler.ts` - Job scheduling system using node-cron -- `capabilities.ts` - Detects available system features -- `constants.ts` - Application-wide constants - -**Utils** (`app/server/utils/`): - -- `restic.ts` - Restic CLI wrapper with type-safe output parsing -- `spawn.ts` - Safe subprocess execution helpers -- `logger.ts` - Winston-based logging -- `crypto.ts` - Encryption utilities -- `errors.ts` - Error handling middleware - -**Database** (`app/server/db/`): - -- Uses Drizzle ORM with SQLite -- Schema in `schema.ts` defines: volumes, repositories, backup schedules, notifications, users, sessions -- Migrations: `app/drizzle/` - -### Client Architecture - -**Framework**: React Router v7 with SSR -**Entry Point**: `app/root.tsx` - -The client uses: - -- TanStack Query for server state management -- Auto-generated API client from OpenAPI spec (in `app/client/api-client/`) -- Radix UI primitives with custom Tailwind styling -- Server-Sent Events hook (`use-server-events.ts`) for real-time updates - -Routes are organized in feature modules at `app/client/modules/*/routes/`. - -### Shared Schemas - -`app/schemas/` contains ArkType schemas used by both client and server: - -- Volume configurations (NFS, SMB, WebDAV, SFTP, directory) -- Repository configurations (S3, Azure, GCS, local, rclone) -- Restic command output parsing types -- Backend status types - -These schemas provide runtime validation and TypeScript types. - -## Restic Integration - -Zerobyte is a wrapper around Restic for backup operations. Key integration points: - -**Repository Management**: - -- Creates/initializes Restic repositories via `restic init` -- Supports multiple backends: local, S3, Azure Blob Storage, Google Cloud Storage, or any rclone-supported backend -- Stores single encryption password in `/var/lib/zerobyte/restic/password` (auto-generated on first run) - -**Backup Operations**: - -- Executes `restic backup` with user-defined schedules (cron expressions) -- Supports include/exclude patterns for selective backups -- Parses JSON output for progress tracking and statistics -- Implements retention policies via `restic forget --prune` - -**Repository Utilities** (`utils/restic.ts`): - -- `buildRepoUrl()` - Constructs repository URLs for different backends -- `buildEnv()` - Sets environment variables (credentials, cache dir) -- Type-safe parsing of Restic JSON output using ArkType schemas - -**Rclone Integration** (`app/server/modules/repositories/`): - -- Allows using any rclone backend as a Restic repository -- Dynamically generates rclone config and passes via environment variables -- Supports backends like Dropbox, Google Drive, OneDrive, Backblaze B2, etc. - -## Environment & Configuration - -**Runtime Environment Variables**: - -- Database path: `./data/zerobyte.db` (configurable via `drizzle.config.ts`) -- Restic cache: `/var/lib/zerobyte/restic/cache` -- Restic password: `/var/lib/zerobyte/restic/password` -- Volume mounts: `/var/lib/zerobyte/mounts/` -- Local repositories: `/var/lib/zerobyte/repositories/` - -**Capabilities Detection**: -On startup, the server detects available capabilities (see `core/capabilities.ts`): - -- **rclone**: Requires `/root/.config/rclone` directory access -- System will gracefully degrade if capabilities are unavailable - -## Common Workflows - -### Adding a New Volume Backend - -1. Create backend implementation in `app/server/modules/backends//` -2. Implement `mount()` and `unmount()` methods -3. Add schema to `app/schemas/volumes.ts` -4. Update `volumeConfigSchema` discriminated union -5. Update backend factory in `app/server/modules/backends/backend.ts` - -### Adding a New Repository Backend - -1. Add backend type to `app/schemas/restic.ts` -2. Update `buildRepoUrl()` in `app/server/utils/restic.ts` -3. Update `buildEnv()` to handle credentials/configuration -4. Add DTO schemas in `app/server/modules/repositories/repositories.dto.ts` -5. Update repository service to handle new backend - -### Adding a New Scheduled Job - -1. Create job class in `app/server/jobs/.ts` extending `Job` -2. Implement `run()` method -3. Register in `app/server/modules/lifecycle/startup.ts` with cron expression: - ```typescript - Scheduler.build(YourJob).schedule("* * * * *"); - ``` - -## Important Notes - -- **TypeScript**: Uses `"type": "module"` - all imports must include extensions when targeting Node/Bun -- **Validation**: Prefer ArkType over Zod - it's used throughout the codebase -- **Visibility**: Prefer using the `cn` helper with `{ hidden: condition }` instead of conditional rendering with ternaries or `&&` for toggling element visibility in the DOM. -- **Database**: Timestamps are stored as Unix epoch integers, not ISO strings -- **Security**: Restic password file has 0600 permissions - never expose it -- **Mounting**: Requires privileged container or CAP_SYS_ADMIN for FUSE mounts -- **API Documentation**: OpenAPI spec auto-generated at `/api/v1/openapi.json`, docs at `/api/v1/docs` diff --git a/README.md b/README.md index 78c3b0b6..1c0dbb37 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,13 @@ services: > [!NOTE] > **TrueNAS Users:** The host path `/var/lib` is ephemeral on TrueNAS and will be reset during system upgrades. Instead of using `/var/lib/zerobyte:/var/lib/zerobyte`, create a dedicated ZFS dataset (e.g., `tank/docker/zerobyte`) and mount it instead: +> > ```yaml > volumes: > - /etc/localtime:/etc/localtime:ro > - /mnt/tank/docker/zerobyte:/var/lib/zerobyte > ``` +> > This ensures your configuration, encryption keys, and database persist across TrueNAS upgrades. Then, run the following command to start Zerobyte: @@ -87,16 +89,16 @@ Zerobyte can be customized using environment variables. Below are the available ### Environment Variables -| Variable | Description | Default | -| :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | :--------- | -| `BASE_URL` | The base URL of your Zerobyte instance (e.g., `https://zerobyte.example.com`). See [Authentication](#authentication) below. | (none) | -| `APP_SECRET` | **Required.** A random secret key (32+ chars) used to encrypt sensitive data in the database. Generate with `openssl rand -hex 32`. | (none) | -| `PORT` | The port the web interface and API will listen on. | `4096` | -| `RESTIC_HOSTNAME` | The hostname used by Restic when creating snapshots. Automatically detected if a custom hostname is set in Docker. | `zerobyte` | -| `TZ` | Timezone for the container (e.g., `Europe/Paris`). **Crucial for accurate backup scheduling.** | `UTC` | -| `TRUSTED_ORIGINS` | Comma-separated list of extra trusted origins for CORS (e.g., `http://localhost:3000,http://example.com`). | (none) | -| `LOG_LEVEL` | Logging verbosity. Options: `debug`, `info`, `warn`, `error`. | `info` | -| `SERVER_IDLE_TIMEOUT` | Idle timeout for the server in seconds. | `60` | +| Variable | Description | Default | +| :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :--------- | +| `BASE_URL` | **Required.** The base URL of your Zerobyte instance (e.g., `https://zerobyte.example.com`). See [Authentication](#authentication) below. | (none) | +| `APP_SECRET` | **Required.** A random secret key (32+ chars) used to encrypt sensitive data in the database. Generate with `openssl rand -hex 32`. | (none) | +| `PORT` | The port the web interface and API will listen on. | `4096` | +| `RESTIC_HOSTNAME` | The hostname used by Restic when creating snapshots. Automatically detected if a custom hostname is set in Docker. | `zerobyte` | +| `TZ` | Timezone for the container (e.g., `Europe/Paris`). **Crucial for accurate backup scheduling.** | `UTC` | +| `TRUSTED_ORIGINS` | Comma-separated list of extra trusted origins for CORS (e.g., `http://localhost:3000,http://example.com`). | (none) | +| `LOG_LEVEL` | Logging verbosity. Options: `debug`, `info`, `warn`, `error`. | `info` | +| `SERVER_IDLE_TIMEOUT` | Idle timeout for the server in seconds. | `60` | ### Secret References