# DBackup Self-hosted web app for automating database and file backups with encryption, compression, and retention policies. Built with **Next.js 16 (App Router)**, **TypeScript**, **Prisma** (SQLite), and **Shadcn UI**. Supported sources include MySQL, PostgreSQL, MongoDB, MariaDB, SQLite, MSSQL, Redis, and Valkey. The adapter lists grow frequently. Never treat a hardcoded adapter list in any doc as exhaustive - run `ls src/lib/adapters/{database,storage,notification}/` for the authoritative list before making claims about supported adapters. ## Guide map Claude Code loads the nearest `CLAUDE.md` when you touch files in a directory. Read the matching guide before working in that area: | Working on | Guide | | :--- | :--- | | Services, Server Actions, API routes, runner pipeline | [src/CLAUDE.md](src/CLAUDE.md) | | UI components and the design system | [src/components/CLAUDE.md](src/components/CLAUDE.md) | | Dashboard pages and the look of the UI redesign | [src/app/dashboard/CLAUDE.md](src/app/dashboard/CLAUDE.md) | | Database, storage, or notification adapters | [src/lib/adapters/CLAUDE.md](src/lib/adapters/CLAUDE.md) | | Wiki pages and the changelog | [docs/CLAUDE.md](docs/CLAUDE.md) | | Marketing website (dbackup.app) | [website/CLAUDE.md](website/CLAUDE.md) | | Unit and audit tests | [tests/CLAUDE.md](tests/CLAUDE.md) | | End-to-end tests (Playwright) | [e2e/CLAUDE.md](e2e/CLAUDE.md) | ## Non-negotiable rules 1. **Package manager is `pnpm`.** Never `npm install` or `yarn`. Prisma CLI calls go through `npx prisma ...`. 2. **Never use `console.log` / `console.error` / `console.warn`.** Use the logger from `@/lib/logging/logger`. This holds in Client Components too. 3. **Every change gets a changelog entry** in the same response, written into the fragment of its branch under `changelog/unreleased/`, never into `docs/changelog.md`. AI tooling changes get none. See [Changelog workflow](#changelog-workflow). 4. **Typography**: no em dashes, no semicolons. Use a hyphen where a dash is needed, and end sentences with a period. Applies to code comments, docs, and commit messages. 5. **Language**: all code, comments, and documentation in English. 6. **Never run `prisma migrate dev` while `pnpm dev` is running**, and never use `prisma db push`. See [Prisma migrations](#prisma-migrations). 7. **Every Server Action and API route starts with a permission check.** See [src/CLAUDE.md](src/CLAUDE.md). 8. **Never run `git commit`, `git push`, or `git merge`.** The maintainer commits everything himself. Finish the work, run `pnpm validate`, leave the files in the working tree, and report what changed. This holds even when a plan, a task list, or your own earlier message says "one commit per step" - a plan you wrote is not permission. Never add a `Co-Authored-By` trailer for an AI tool. If a commit was already created by mistake, undo it with `git reset --soft HEAD~1` so the work stays staged, and never force-push to fix it without asking first. 9. **Branches: feature branch, then `dev`, then `main`.** Work happens on a feature branch off `dev`, its pull request goes into `dev`, and a release merges `dev` into `main`. A pull request or a base branch suggested to anyone points at `dev`, never at `main`. ## Architecture (4 layers) ``` src/app/ Route definitions and Server Actions - NO business logic src/services/ All business logic, organized by domain <- core src/lib/adapters/ Plugin system for databases, storage, notifications src/lib/runner/ Step-based backup execution pipeline ``` Data flows one direction: `page.tsx` (Server Component) fetches through a Service, then passes props to a Client Component. Server Actions are thin wrappers: auth check, Zod validation, service call, revalidate. A Server Action or API route that contains business logic is a bug. Move it into `src/services/`. ## Commands ```bash pnpm dev # Dev server - auto-applies pending migrations on startup pnpm run build # Production build - run before committing pnpm validate # Lint + typecheck + unit tests (scripts/validate.sh), no build pnpm type # tsc --noEmit only pnpm test # Unit tests (vitest) pnpm test:e2e # End-to-end tests: the image of this checkout beside the dev lab (Playwright) pnpm lab # Menu of the dev lab commands, lab:status shows what runs and what is missing pnpm lab:up # Start the dev lab: test DBs, storage servers, notification receivers pnpm lab:seed # Fill the dev DB with the first user and every lab connection and job pnpm lab:check --run # Test every lab connection and run every lab job once pnpm run database:reset # Reset dev DB from scratch via all migrations (lab:reset also seeds it) pnpm toolbox # Menu of the release and maintenance commands below, see scripts/toolbox/README.md pnpm changelog:preview # Show the changelog block the next release writes from the fragments pnpm version:bump # Next version, changelog block and version everywhere, after a CodeQL check of dev pnpm codeql:check # CodeQL alerts open on dev that main does not have pnpm release:tag # Tag the release on main and push it (release:untag deletes a tag) ``` Docs site and marketing site are separate workspaces: `pnpm docs:dev` (VitePress) and `pnpm website:dev`. ### Prisma migrations `pnpm dev` runs `prisma migrate deploy` on startup, so the local DB is always current after pulling. No manual step needed. **Schema changes:** 1. Stop the dev server (Ctrl+C in the node terminal). 2. `npx prisma migrate dev --name ` 3. Restart `pnpm dev`. Running `migrate dev` against a live dev server can trigger an interactive DB reset, which fights the SQLite file lock and crashes the Node process - often taking VS Code with it. `prisma db push` applies schema changes without a migration file, which desyncs `_prisma_migrations` from the real schema and breaks `database:deploy` in production and for other developers. ## Changelog workflow Every change - feature, bug fix, refactor, docs, CI - gets an entry in the same response. Do not defer it. **Exception: AI tooling changes never get a changelog entry.** The changelog is published on the docs site for people who run DBackup. Anything that only configures the assistant is invisible to them: - `CLAUDE.md` files anywhere in the tree - `.claude/` in full - agents, skills, commands, settings, launch config - `.gitignore` rules that only exist to track those files The test is who the line is for. A reader upgrading their instance never needs to know a prompt file changed. Code that ships in the product still counts even when it exists to keep the assistant honest - a lint guard under `tests/` changes the build for every contributor, so it belongs in `### ๐Ÿงช Tests`. **Off `main`, first ask whether the change needs an entry at all.** A fix or behavior change to something already released does. A step in building the unreleased feature the branch exists for does not, and neither does a bug that only ever existed on that branch, because the feature gets one entry rather than one per step. Check it against `git log main..HEAD` instead of assuming: if the code being fixed arrived in one of those commits, nobody has ever run it. **Every entry is at most two sentences** and stays shallow. Say what the change is, not why it was made, how it works, or what it took to build. Anything needing a third sentence belongs in that feature's guide. **Inside an entry there is no `;`, no ` - ` and no `- `.** Stricter than the typography rule above, which allows a hyphen as a dash. A sentence reaching for one of them is doing too much work, so split it or cut it. The `- ` opening the line is the list marker and stays. **Where the entry goes**: into `changelog/unreleased/.md`, named after the branch with `/` replaced by `-`, never into `docs/changelog.md`. One file per branch, in the Markdown of a version block without the version header and the Docker section, so pull requests that run side by side never touch the same file. A branch that changes its mind edits its own file. `pnpm version:bump` collects the fragments into `docs/changelog.md` at the release and deletes them, a lint guard checks every fragment, and `pnpm changelog:preview` shows the block the next release writes. Format and example: [changelog/unreleased/README.md](changelog/unreleased/README.md). **Section order** (skip sections with no entries, never reorder): | # | Change type | Section | | :--- | :--- | :--- | | 1 | New feature, adapter, or page | `### โœจ Features` | | 2 | Bug fix | `### ๐Ÿ› Bug Fixes` | | 3 | Security fix | `### ๐Ÿ”’ Security` | | 4 | Performance, UX, code quality | `### ๐ŸŽจ Improvements` | | 5 | Behavior change (non-breaking) | `### ๐Ÿ”„ Changed` | | 6 | Deleted feature or code | `### ๐Ÿ—‘๏ธ Removed` | | 7 | New or updated docs article | `### ๐Ÿ“ Documentation` | | 8 | Test changes | `### ๐Ÿงช Tests` | | 9 | GitHub Actions, Dockerfile, scripts | `### ๐Ÿ”ง CI/CD` | | 10 | Docker image info (always last, written by the release) | `### ๐Ÿณ Docker` | Entry format and full rules live in [docs/CLAUDE.md](docs/CLAUDE.md). ## File conventions - **Naming**: `kebab-case` (`backup-service.ts`, `user-table.tsx`). - **Exports**: named exports preferred, no barrel files in `src/components/`. - **Path alias**: `@/` maps to `src/`. - **Max file size**: roughly 300 lines, then split. The runner pipeline in `src/lib/runner/steps/` is the reference for how to split. ## Quick reference | Concern | Location | | :--- | :--- | | DB schema | [prisma/schema.prisma](prisma/schema.prisma) | | Types and interfaces | [src/lib/core/](src/lib/core/) | | Permissions | [src/lib/auth/permissions.ts](src/lib/auth/permissions.ts) | | Access control | [src/lib/auth/access-control.ts](src/lib/auth/access-control.ts) | | Adapters | [src/lib/adapters/](src/lib/adapters/) | | Services | [src/services/](src/services/) | | Server Actions | [src/app/actions/](src/app/actions/) | | Scheduler (cron) | [src/lib/server/scheduler.ts](src/lib/server/scheduler.ts) | | Crypto (encrypt/decrypt) | [src/lib/crypto/index.ts](src/lib/crypto/index.ts) | | Crypto (streams) | [src/lib/crypto/stream.ts](src/lib/crypto/stream.ts) | | Logger | [src/lib/logging/logger.ts](src/lib/logging/logger.ts) | | Error classes | [src/lib/logging/errors.ts](src/lib/logging/errors.ts) | | Shadcn primitives | [src/components/ui/](src/components/ui/) | | Task colors (tones) | [src/components/ui/tone.ts](src/components/ui/tone.ts), rules under Color in [src/app/dashboard/CLAUDE.md](src/app/dashboard/CLAUDE.md) | | Dev lab (test DBs, storage, seed) | [lab/](lab/), guide in [docs/developer-guide/reference/dev-lab.md](docs/developer-guide/reference/dev-lab.md) | ## Environment variables - `ENCRYPTION_KEY` - system key for encrypting secrets at rest. Required. - `LOG_LEVEL` - `debug` | `info` (default) | `warn` | `error`. - `SQLITE_WAL_MODE` - `true` (default) | `false`. WAL is on by default for the Prisma SQLite DB. See `src/lib/prisma.ts`. - `DISABLE_EMAIL_LOGIN` - `true` | `false` (default). Rejects browser email sign-in and sign-up. Server-side `auth.api.*` calls stay open so admin user creation and password resets keep working. See `src/lib/auth/env-flags.ts`. - `TRUSTED_PROXIES` - the reverse proxies whose `X-Forwarded-For` counts, comma-separated addresses, CIDR ranges and the keywords `private` and `cloudflare`, `none`, or unset for `private`, the loopback and private networks. The custom server resolves the client address in `server/client-address.ts` and passes it in a header of its own, so the app reads `clientAddress()` from `@/lib/core/client-address` and never `X-Forwarded-For` itself. A lint guard checks that. - `OIDC_AUTO_REDIRECT` - `SsoProvider.providerId` to redirect to instead of rendering the login page. Format checked in `env-validation.ts`, existence in `startup-checks.ts`.