Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 46 additions & 16 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ applyTo: "**/*"
# Database Backup Manager - AI Assistant Guidelines

## Project Overview
Self-hosted web app for automating database backups (MySQL, PostgreSQL, MongoDB, MariaDB, SQLite, MSSQL, Redis) with encryption, compression, and retention policies. Built with **Next.js 16 (App Router)**, **TypeScript**, **Prisma** (SQLite), and **Shadcn UI**.
Self-hosted web app for automating database backups (MySQL, PostgreSQL, MongoDB, MariaDB, SQLite, MSSQL, Redis, Valkey, and more) with encryption, compression, and retention policies. Built with **Next.js 16 (App Router)**, **TypeScript**, **Prisma** (SQLite), and **Shadcn UI**.

Database, storage, and notification adapter lists grow frequently. Do not treat any hardcoded adapter list in these instructions as exhaustive - run `ls src/lib/adapters/{database,storage,notification}/` to get the current, authoritative list before making claims about supported adapters.

## Language & Commands
- **Code/Comments**: English
Expand All @@ -19,6 +21,19 @@ Self-hosted web app for automating database backups (MySQL, PostgreSQL, MongoDB,
- `page.tsx`: Fetch via Services → pass to Client Components
- `actions/*.ts`: Server Actions - thin wrappers (Auth → Zod Validation → Service call → Revalidate)

```typescript
// src/app/actions/jobs.ts - canonical Server Action shape
"use server";

export async function updateJob(id: string, input: UpdateJobInput) {
await checkPermission(PERMISSIONS.JOBS.WRITE); // 1. Auth
const data = UpdateJobSchema.parse(input); // 2. Zod validation
const job = await jobService.update(id, data); // 3. Service call (all logic lives there)
revalidatePath("/jobs"); // 4. Revalidate
return { success: true, data: job };
}
```

### 2. Service Layer (`src/services`) ⭐ CORE
All business logic lives here, organized by domain:
```
Expand All @@ -31,8 +46,8 @@ src/services/
storage/ → storage-service.ts, verification-service.ts, storage-alert-service.ts
notifications/ → notification-log-service.ts, system-notification-service.ts
system/ → healthcheck-service.ts, system-task-service.ts, update-service.ts, db-version-service.ts, certificate-service.ts
config/ → config-service.ts, export.ts, import.ts
templates/ → naming-template-service.ts, retention-policy-service.ts, schedule-preset-service.ts
config/ → config-service.ts, export.ts, import.ts, parse.ts, restore-pipeline.ts
templates/ → naming-template-service.ts, notification-template-service.ts, retention-policy-service.ts, schedule-preset-service.ts
user/ → user-service.ts
dashboard-service.ts (flat, no subdirectory)
audit-service.ts (flat, no subdirectory)
Expand All @@ -51,7 +66,18 @@ registry.register(MySQLAdapter);
registry.get("mysql") // Retrieve by ID
```

**Adding a new adapter**: Create folder in `src/lib/adapters/{database|storage|notification}/`, implement interface, register in `src/lib/adapters/index.ts`.
**Adding a new adapter** - a full adapter touches ~8-11 files, not just the adapter class. Missing any of these is the most common source of "half-registered adapter" bugs:

1. `src/lib/adapters/{database|storage|notification}/<name>.ts` (or `<name>/index.ts` if the adapter needs sub-modules) - implement the interface (`dump`/`restore`/`test` for database, `upload`/`download`/`list`/`delete` for storage, `send` for notification)
2. `src/lib/adapters/definitions/{database|storage|notification}.ts` - add the Zod config schema (`NewAdapterSchema`)
3. `src/lib/adapters/definitions/index.ts` - add an entry to `ADAPTER_DEFINITIONS` (`id`, `type`, `name`, `configSchema`, `group` for storage)
4. `src/lib/adapters/index.ts` - import the adapter class and call `registry.register(...)` in `registerAdapters()`
5. `src/lib/core/credential-requirements.ts` - if the adapter uses a credential profile, add an entry to `ADAPTER_CREDENTIAL_REQUIREMENTS[id]`
6. `src/components/adapter/utils.ts` - add the adapter to `ADAPTER_ICON_MAP` (and `ADAPTER_COLOR_MAP` if applicable), otherwise it renders with a generic fallback icon in the UI
7. `src/components/adapter/form-constants.ts` - add field keys to the relevant `*_CONNECTION_KEYS`/`*_CONFIG_KEYS` and `PLACEHOLDERS` if the adapter needs custom form field grouping/placeholders
8. Database adapters only: `src/lib/backup-extensions.ts` - add the dump file extension and description
9. Tests (if the adapter is testable in CI): `docker-compose.test.yml` (service definition) and `tests/integration/test-configs.ts` (`testDatabases`/`CLI_REQUIREMENTS`)
10. Docs (per `docs.instructions.md` template): a new page under `docs/user-guide/{sources|destinations|notifications}/<name>.md`, plus a row in the relevant `docs/developer-guide/adapters/*.md` table

**Adapter connectivity methods:**
- `test()` – full write/delete verification (~15 s timeout). Used for manual connection tests.
Expand Down Expand Up @@ -334,18 +360,21 @@ Reusable named credential sets encrypted with the system `ENCRYPTION_KEY`:

## Notification Events

16 configurable event types (enable/disable per event, set reminder interval, target recipient):
Two distinct event scopes, both defined in `src/lib/notifications/` (`types.ts` for the `NOTIFICATION_EVENTS` map, `events.ts` for `EVENT_DEFINITIONS`):

- **Global events** (`EVENT_DEFINITIONS` in `events.ts`, ~14 entries) - configurable system-wide (enable/disable, reminder interval, target recipient) via Settings > Notifications:

| Category | Events |
|----------|--------|
| Auth | `USER_LOGIN`, `USER_CREATED` |
| Restore | `RESTORE_COMPLETE`, `RESTORE_FAILURE` |
| System | `CONFIG_BACKUP`, `SYSTEM_ERROR` |
| Storage | `STORAGE_USAGE_SPIKE`, `STORAGE_LIMIT_WARNING`, `STORAGE_MISSING_BACKUP` |
| Updates | `UPDATE_AVAILABLE` |
| Backup | `INTEGRITY_CHECK_FAILURE` |
| Health | `CONNECTION_OFFLINE`, `CONNECTION_ONLINE`, `DB_VERSION_CHANGED` |

| Category | Events |
|----------|--------|
| Auth | `USER_LOGIN`, `USER_CREATED` |
| Backup | `BACKUP_SUCCESS`, `BACKUP_FAILURE` |
| Restore | `RESTORE_COMPLETE`, `RESTORE_FAILURE` |
| System | `CONFIG_BACKUP`, `SYSTEM_ERROR`, `UPDATE_AVAILABLE` |
| Storage | `STORAGE_USAGE_SPIKE`, `STORAGE_LIMIT_WARNING`, `STORAGE_MISSING_BACKUP` |
| Connectivity | `CONNECTION_OFFLINE`, `CONNECTION_ONLINE` |
| Database | `DB_VERSION_CHANGED` |
| Integrity | `INTEGRITY_CHECK_FAILURE` |
- **Per-job backup events** (`BACKUP_SUCCESS`, `BACKUP_PARTIAL`, `BACKUP_FAILURE`) are intentionally **not** in `EVENT_DEFINITIONS` - they are configured per-job (Job → Notify tab), not globally. Their message templates live in `src/lib/notifications/templates.ts` and are triggered by the runner pipeline (`04-completion.ts`).

Notification log: `src/services/notifications/notification-log-service.ts`.

Expand Down Expand Up @@ -390,8 +419,9 @@ catch (e: unknown) {
- `PermissionError`, `AuthenticationError`
- `BackupError`, `RestoreError`, `EncryptionError`, `QueueError`

### Environment Variable
### Environment Variables
- `LOG_LEVEL`: `debug` | `info` (default) | `warn` | `error`
- `SQLITE_WAL_MODE`: `true` (default) | `false`. WAL mode is on by default for the Prisma SQLite DB. Set to `false` to opt out. See `src/lib/prisma.ts`.

## Quick Reference

Expand Down
5 changes: 4 additions & 1 deletion .github/instructions/changelog.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ Every changelog entry uses a **bold component prefix** followed by a description
- **component**: Description of the change (1-2 sentences max) ([#N](url))
```

- **component**: Short, lowercase area/adapter name (e.g., `auth`, `MSSQL`, `dashboard`, `ui`, `backup`, `storage`, `SSO`, `Redis`). Must be a **name**, never a sentence or description.
- **component**: Short, lowercase area/adapter name (e.g., `auth`, `MSSQL`, `dashboard`, `ui`, `backup`, `storage`, `SSO`, `Redis`). Must be a **single name or adapter**, never a sentence, never two areas joined together.
- ✅ `**storage**: ...` / ❌ `**storage alerts**: ...` (pick the more specific single area - `storage` or the alert subsystem's own tag, not a two-word compound)
- ✅ `**Valkey**: ...` / ❌ `**new Valkey adapter**: ...` (the "what happened" belongs in the description, not the component)
- **Description**: One sentence - as short as possible while still making sense. Two sentences only if absolutely necessary. Write **what** was done, not why or how.
- **Issue links**: Always at the **end** of the entry in the format `([#N](url))`. Never embed issue numbers in the component name.
- **One entry per user-visible change** - if a single PR touches many files to deliver one behavior change, that is still **one** changelog line, not one per file/commit. Conversely, don't cram two unrelated changes into one bullet just because they landed in the same PR.

## Section Headings

Expand Down
12 changes: 10 additions & 2 deletions .github/instructions/ui.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,28 @@ applyTo: "src/components/**/*.tsx, src/app/**/*.tsx"
</tech_stack>

<styling>
- Avoid inline styles. Use Tailwind utility classes.
- Avoid inline styles (`style={{...}}`). Use Tailwind utility classes. Exception: dynamically computed values (chart colors, progress percentages) may need `style` - keep these isolated to the specific element, never as a substitute for static styling.
- **Tailwind Best Practices**: Prefer standard utility classes (e.g., `h-px`, `w-4`) over arbitrary values (e.g., `h-[1px]`, `w-[1rem]`) whenever possible.
- **Feedback**: Use `toast` (Sonner) for success/error notifications. Never use `alert()`.
</styling>

<formatting>
- **Dates**:
- ❌ Forbidden: `new Date().toLocaleDateString()` or any direct locale formatting.
- ❌ Forbidden: `.toLocaleDateString()`, `.toLocaleTimeString()`, `.toLocaleString()` on a `Date`, or any direct locale formatting - not just the `Date` variant. All three sneak in easily in chart tooltips, table cells, and preview components.
- ✅ Use the `useDateFormatter` hook from `src/hooks/use-date-formatter.ts` instead.
- This ensures user timezone and format preferences are respected.
- Numbers (not dates) may use `.toLocaleString()` for thousands separators if there is genuinely no timezone/format concern - but check `formatBytes`/`formatDuration` in `src/lib/utils.ts` first, they likely already cover the case.
</formatting>

<logging>
- **Never** use raw `console.log`/`console.error`/`console.warn`, including inside `.catch()` handlers. Import `logger` from `@/lib/logging/logger` instead - it has no Node-only dependencies, so it is safe in both Server and Client Components, and gives structured, level-filtered output.
- Never log full context/session/auth objects, even via `logger` - log specific fields (`{ userId }`, not the whole user/session object) to avoid leaking sensitive data into the browser console.
- User-facing errors still go through `toast`, not just the logger - the logger is for diagnostics, the toast is for the user.
</logging>

<architecture>
- **Separation**: Decouple data fetching (Server Actions) from presentation.
- **Props**: Validate all props with strict TypeScript Interfaces.
- **Server Components by default**: `page.tsx` should rarely need `"use client"` itself. Fetch data in the (Server Component) page, then pass it as props to a child Client Component that owns the interactive parts (forms, client-side sort/filter, real-time widgets). Before adding `"use client"` to a page, check whether only a sub-tree actually needs it.
</architecture>
</rules>
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ next-env.d.ts
# db
prisma/dev.db
prisma/dev.db-journal
prisma/dev.db-shm
prisma/dev.db-wal
db-data/

# backups
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
@.github/copilot-instructions.md
@.github/instructions/workflow.instructions.md
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<img src="https://img.shields.io/badge/MongoDB-47A248?logo=mongodb&logoColor=white" alt="MongoDB">
<img src="https://img.shields.io/badge/SQLite-003B57?logo=sqlite&logoColor=white" alt="SQLite">
<img src="https://img.shields.io/badge/Redis-DC382D?logo=redis&logoColor=white" alt="Redis">
<img src="https://img.shields.io/badge/Valkey-4B6BFB?logoColor=white" alt="Valkey">
<img src="https://custom-icon-badges.demolab.com/badge/Microsoft%20SQL%20Server-CC2927?logo=mssqlserver-white&logoColor=white" alt="MSSQL">
<br>
<img src="https://img.shields.io/badge/license-GPL--3.0-blue.svg" alt="License">
Expand Down Expand Up @@ -57,7 +58,7 @@ Whether you're running a single MySQL database or managing multiple PostgreSQL,

### 🗄️ Database Backup

- **7 Database Engines** - MySQL, MariaDB, PostgreSQL, MongoDB, SQLite, Redis, and Microsoft SQL Server
- **8 Database Engines** - MySQL, MariaDB, PostgreSQL, MongoDB, SQLite, Redis, Valkey, and Microsoft SQL Server
- **Selective Database Backup** - Choose exactly which databases to back up per job instead of creating separate sources for each database
- **Multi-Database Jobs** - Back up multiple databases from a single source in one job with a unified TAR archive format
- **AES-256-GCM Encryption** - Encrypt backups with managed Encryption Profiles, key rotation, and downloadable Recovery Kits for offline decryption
Expand Down Expand Up @@ -159,15 +160,16 @@ Open [https://localhost:3000](https://localhost:3000) and create your admin acco

## 🗄️ Supported Databases

| Database | Versions | Connection Modes |
| :--- | :--- | :--- |
| PostgreSQL | 12, 13, 14, 15, 16, 17, 18 | Direct, SSH |
| MySQL | 5.7, 8.x, 9.x | Direct, SSH |
| MariaDB | 10.x, 11.x | Direct, SSH |
| MongoDB | 4.x, 5.x, 6.x, 7.x, 8.x | Direct, SSH |
| Redis | 6.x, 7.x, 8.x | Direct, SSH |
| SQLite | 3.x | Local, SSH |
| Microsoft SQL Server | 2017, 2019, 2022, Azure SQL Edge | Direct (+ SSH for file transfer) |
| Database | Versions | Connection Modes | Restore |
| :--- | :--- | :--- | :--- |
| PostgreSQL | 12, 13, 14, 15, 16, 17, 18 | Direct, SSH | Yes |
| MySQL | 5.7, 8.x, 9.x | Direct, SSH | Yes |
| MariaDB | 10.x, 11.x | Direct, SSH | Yes |
| MongoDB | 4.x, 5.x, 6.x, 7.x, 8.x | Direct, SSH | Yes |
| Redis | 2.8+ | Direct, SSH | Guided |
| Valkey | 7.2+ | Direct, SSH | Guided |
| SQLite | 3.x | Local, SSH | Yes |
| Microsoft SQL Server | 2017, 2019, 2022, Azure SQL Edge | Direct (+ SSH for file transfer) | Yes |

## ☁️ Supported Destinations

Expand Down
2 changes: 1 addition & 1 deletion api-docs/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: DBackup API
version: 2.8.0
version: 2.9.0
description: |
REST API for DBackup - a self-hosted database backup automation platform with encryption, compression, and smart retention.

Expand Down
17 changes: 17 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,20 @@ services:
interval: 5s
timeout: 5s
retries: 5

# ==========================================
# Valkey Family
# ==========================================

# Valkey 8 (Current Stable)
valkey-8:
image: valkey/valkey:8-alpine
container_name: dbm-test-valkey-8
command: valkey-server --requirepass testpassword
ports:
- "63780:6379"
healthcheck:
test: ["CMD", "valkey-cli", "-a", "testpassword", "ping"]
interval: 5s
timeout: 5s
retries: 5
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export default defineConfig({
{ text: 'PostgreSQL', link: '/user-guide/sources/postgresql' },
{ text: 'MongoDB', link: '/user-guide/sources/mongodb' },
{ text: 'Redis', link: '/user-guide/sources/redis' },
{ text: 'Valkey', link: '/user-guide/sources/valkey' },
{ text: 'SQLite', link: '/user-guide/sources/sqlite' },
{ text: 'Microsoft SQL Server', link: '/user-guide/sources/mssql' }
]
Expand Down
32 changes: 32 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@

All notable changes to DBackup are documented here.

## v2.9.0 - Valkey Support, Storage Alert Fix, and Multiple Improvements
*Released: July 4, 2026*

### ✨ Features

- **Valkey**: Added Valkey as a supported database source. Uses the same RDB backup mechanism as Redis with correct version display.

### 🐛 Bug Fixes

- **storage alerts**: Fixed "Missing Backup" alert firing incorrectly when a retention policy keeps the file count stable, making the alert appear even though backups were completing successfully.

### 🎨 Improvements

- **dashboard**: The Storage Usage widget now shows the adapter-specific brand icon for each destination (matching the Destinations page) instead of a generic hard drive icon.
- **adapter form**: The add/edit dialog is now scrollable when configuration fields exceed the available screen height, with action buttons always pinned at the bottom.
- **system tasks**: Health check log cleanup now runs once a day via the "Clean Old Data" task instead of on every health check run, reducing unnecessary database load.
- **system tasks**: Removed a redundant hourly storage snapshot cleanup that duplicated the daily "Clean Old Data" cleanup.
- **database**: The internal SQLite database now runs in WAL mode with a busy timeout, so readers no longer block writers and concurrent writes wait briefly instead of failing instantly with "database is locked". Configurable via the new `SQLITE_WAL_MODE` environment variable (default: `true`).

### 📝 Documentation

- **Redis**: Updated supported version documentation from `6.x` to `2.8+` to reflect actual compatibility.
- **installation**: Documented the `-wal`/`-shm` companion files created by the internal database's WAL mode.

### 🐳 Docker

- **Image**: `skyfay/dbackup:v2.9.0`
- **Also tagged as**: `latest`, `v2`
- **CI Image**: `skyfay/dbackup:ci`
- **Platforms**: linux/amd64, linux/arm64


## v2.8.0 - Notification Templates, Per-Job Event Filters, and Multiple Bug Fixes
*Released: June 28, 2026*

Expand Down
4 changes: 2 additions & 2 deletions docs/developer-guide/advanced/healthcheck.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ if (success) {

### Retention

The service automatically deletes logs older than 48 hours to control database size.
Logs are automatically deleted once a day by the `CLEAN_OLD_LOGS` system task, not on every health check run. Default retention is 2 days, configurable via the `healthcheck.logRetentionDays` system setting.

## System Task Integration

Expand Down Expand Up @@ -237,5 +237,5 @@ const HEALTHCHECK_CRON = '*/1 * * * *';

For high-availability setups, consider:
- Reducing interval to 30 seconds
- Increasing retention beyond 48 hours
- Increasing `healthcheck.logRetentionDays` beyond the default 2 days
- Adding external monitoring integration
2 changes: 2 additions & 0 deletions docs/developer-guide/reference/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Complete reference for all environment variables in DBackup.
| :--- | :--- | :--- |
| `TRUSTED_ORIGINS` | Additional URLs for accessing DBackup (comma-separated) | - |
| `DATABASE_URL` | SQLite database file path | `file:/data/db/dbackup.db` |
| `SQLITE_WAL_MODE` | Set to `false` to disable SQLite WAL mode | `true` |
| `PORT` | Internal port the server listens on | `3000` |
| `TZ` | Server timezone (for logs and cron scheduling) | `UTC` |
| `TMPDIR` | Temporary directory for backup processing | `/tmp` |
Expand All @@ -38,6 +39,7 @@ Complete reference for all environment variables in DBackup.
```
- **PORT** changes the internal port. When using custom ports, set both `PORT` and update your port mapping accordingly
- **DATABASE_URL** has a sensible default and typically doesn't need to be set
- **SQLITE_WAL_MODE** enables [WAL (Write-Ahead Logging)](https://www.sqlite.org/wal.html) mode by default so readers and the writer don't block each other. Set to `false` only if `/data` is on a filesystem that doesn't support WAL's shared-memory locking (e.g. some network shares/NFS mounts) - this falls back to SQLite's default rollback journal
- **TMPDIR** is useful for mounting larger storage for temporary backup files (e.g., NFS)
- **TZ** only affects server-side logs. User-facing dates use the timezone from user profile settings
- **PUID/PGID** control which UID/GID the application process runs as. Set these to match your host user (e.g., `PUID=1000 PGID=1000`) to avoid volume permission issues. The entrypoint adjusts the internal user at startup
Expand Down
Loading
Loading