* docs: add agents instructions * feat: naming backup schedules * fix: wrong table for filtering
9.3 KiB
AGENTS.md
Important instructions
- Never create migration files manually. Always use the provided command to generate migrations
- If you realize an automated migration is incorrect, make sure to remove all the associated entries from the
_journal.jsonand the newly created files located inapp/drizzle/before re-generating the migration
Project Overview
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, 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: Biome (formatter & linter)
- Containerization: Docker with multi-stage builds
Repository Structure
This is a unified application with the following structure:
app/server- Bun-based API server with Honoapp/client- React Router SSR frontend components and modulesapp/schemas- Shared ArkType schemas for validationapp/drizzle- Database migrations
Type Checking
# Run type checking and generate React Router types
bun run tsc
Building
# Build for production
bun run build
Database Migrations
# Generate new migration from schema changes
bun gen:migrations
# Generate a custom empty migration
bunx drizzle-kit generate --custom --name=fix-timestamps-to-ms
API Client Generation
# Generate TypeScript API client from OpenAPI spec
# Note: Server is always running don't need to start it separately
bun run gen:api-client
Code Quality
# Format and lint (Biome)
bunx biome check --write .
# Format only
bunx biome format --write .
# Lint only
bunx biome lint .
Architecture
Server Architecture
The server follows a modular service-oriented architecture:
Entry Point: app/server/index.ts
- Initializes servers using
react-router-hono-server:- Main API server on port 4096 (REST API + serves static frontend)
- Docker volume plugin server on Unix socket
/run/docker/plugins/zerobyte.sock(optional, if Docker is available)
Modules (app/server/modules/):
Each module follows a controller <20> service <20> database pattern:
auth/- User authentication and session managementvolumes/- Volume mounting/unmounting (NFS, SMB, WebDAV, directories)repositories/- Restic repository management (S3, Azure, GCS, local, rclone)backups/- Backup schedule management and executionnotifications/- Notification system with multiple providers (Discord, email, Gotify, Ntfy, Slack, Pushover)driver/- Docker volume plugin implementationevents/- Server-Sent Events for real-time updatessystem/- System information and capabilitieslifecycle/- Application startup/shutdown hooks
Backends (app/server/modules/backends/):
Each volume backend (NFS, SMB, WebDAV, directory) implements mounting logic using system tools (mount.nfs, mount.cifs, davfs2).
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-croncapabilities.ts- Detects available system features (Docker support, etc.)constants.ts- Application-wide constants
Utils (app/server/utils/):
restic.ts- Restic CLI wrapper with type-safe output parsingspawn.ts- Safe subprocess execution helperslogger.ts- Winston-based loggingcrypto.ts- Encryption utilitieserrors.ts- Error handling middleware
Database (app/server/db/):
- Uses Drizzle ORM with SQLite
- Schema in
schema.tsdefines: 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, 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 backupwith 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 backendsbuildEnv()- Sets environment variables (credentials, cache dir)ensurePassfile()- Manages encryption password file- 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.
Docker Volume Plugin
When Docker socket is available (/var/run/docker.sock), Zerobyte registers as a Docker volume plugin:
Plugin Location: /run/docker/plugins/zerobyte.sock
Implementation: app/server/modules/driver/driver.controller.ts
This allows other containers to mount Zerobyte volumes using Docker.
The plugin implements the Docker Volume Plugin API v1.
Environment & Configuration
Runtime Environment Variables:
- Database path:
./data/zerobyte.db(configurable viadrizzle.config.ts) - Restic cache:
/var/lib/zerobyte/restic/cache - Restic password:
/var/lib/zerobyte/restic/password - Volume mounts:
/var/lib/zerobyte/mounts/<volume-name> - Local repositories:
/var/lib/zerobyte/repositories/<repo-name>
Capabilities Detection:
On startup, the server detects available capabilities (see core/capabilities.ts):
- Docker: Requires
/var/run/docker.sockaccess - System will gracefully degrade if capabilities are unavailable
Common Workflows
Adding a New Volume Backend
- Create backend implementation in
app/server/modules/backends/<backend>/ - Implement
mount()andunmount()methods - Add schema to
app/schemas/volumes.ts - Update
volumeConfigSchemadiscriminated union - Update backend factory in
app/server/modules/backends/backend.ts
Adding a New Repository Backend
- Add backend type to
app/schemas/restic.ts - Update
buildRepoUrl()inapp/server/utils/restic.ts - Update
buildEnv()to handle credentials/configuration - Add DTO schemas in
app/server/modules/repositories/repositories.dto.ts - Update repository service to handle new backend
Adding a New Scheduled Job
- Create job class in
app/server/jobs/<job-name>.tsextendingJob - Implement
run()method - Register in
app/server/modules/lifecycle/startup.tswith cron expression:Scheduler.build(YourJob).schedule("* * * * *");
Important Notes
- Code Style: Uses Biome with tabs (not spaces), 120 char line width, double quotes
- Imports: Organize imports is disabled in Biome - do not auto-organize
- TypeScript: Uses
"type": "module"- all imports must include extensions when targeting Node/Bun - Validation: Prefer ArkType over Zod - it's used throughout the codebase
- 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
Docker Development Setup
The docker-compose.yml defines two services:
zerobyte-dev- Development with hot reload (usesdevelopmentstage)zerobyte-prod- Production build (usesproductionstage)
Both mount:
/var/lib/zerobytefor persistent data/dev/fusedevice for FUSE mounting- Optionally
/var/run/docker.sockfor Docker plugin functionality