Skip to content

Commit 39ba3cd

Browse files
committed
feat: add export-db command and automatic migration backup/rollback
1 parent 403b01e commit 39ba3cd

12 files changed

Lines changed: 722 additions & 41 deletions

File tree

README.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Glob patterns • tar.gz compression • Local + S3 storage • Docker volumes
1717

1818
[![GitHub Sponsors](https://img.shields.io/badge/Sponsor-GitHub-ea4aaa?style=for-the-badge&logo=githubsponsors)](https://github.com/sponsors/Climactic)
1919
[![Ko-fi](https://img.shields.io/badge/Support-Ko--fi-ff5e5b?style=for-the-badge&logo=ko-fi)](https://ko-fi.com/ClimacticCo)
20+
[![Discord](https://img.shields.io/badge/Chat-Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/QzpEuEraqK)
2021

2122
</div>
2223

@@ -168,13 +169,14 @@ backitup verify --all # Verify integrity
168169

169170
All commands support `-c, --config <path>` to specify a config file and `-h, --help` for detailed usage.
170171

171-
| Command | Description | Docs |
172-
| ------------------ | ----------------------- | -------------------------------------- |
173-
| `backitup backup` | Create a backup | [backup.md](docs/commands/backup.md) |
174-
| `backitup start` | Start scheduler daemon | [start.md](docs/commands/start.md) |
175-
| `backitup list` | List existing backups | [list.md](docs/commands/list.md) |
176-
| `backitup cleanup` | Clean old backups | [cleanup.md](docs/commands/cleanup.md) |
177-
| `backitup verify` | Verify backup integrity | [verify.md](docs/commands/verify.md) |
172+
| Command | Description | Docs |
173+
| -------------------- | ------------------------ | ------------------------------------------ |
174+
| `backitup backup` | Create a backup | [backup.md](docs/commands/backup.md) |
175+
| `backitup start` | Start scheduler daemon | [start.md](docs/commands/start.md) |
176+
| `backitup list` | List existing backups | [list.md](docs/commands/list.md) |
177+
| `backitup cleanup` | Clean old backups | [cleanup.md](docs/commands/cleanup.md) |
178+
| `backitup verify` | Verify backup integrity | [verify.md](docs/commands/verify.md) |
179+
| `backitup export-db` | Export the database file | [export-db.md](docs/commands/export-db.md) |
178180

179181
```bash
180182
backitup start # Start scheduler daemon
@@ -198,6 +200,8 @@ backitup list --format json # Output as JSON or CSV
198200
backitup verify --all # Verify all backup checksums
199201
backitup verify <backup-id> # Verify specific backup
200202
backitup verify --all --fix # Update DB for missing files
203+
204+
backitup export-db ./backup.db # Export database to file
201205
```
202206

203207
---
@@ -360,6 +364,7 @@ backitup backup -s daily --docker-volume postgres_data \
360364
```
361365

362366
**Important notes:**
367+
363368
- Containers with `restart: always` or `restart: unless-stopped` policies may auto-restart after being stopped. BackItUp detects this and logs a warning.
364369
- If a container fails to restart after all retries, the backup still succeeds but a warning is logged.
365370
- Per-volume settings override global settings, allowing fine-grained control.
@@ -411,11 +416,11 @@ docker run --rm -v postgres_data_restored:/data -v /tmp/restore:/backup alpine \
411416

412417
The Docker image uses three volume mount points:
413418

414-
| Mount Point | Purpose |
415-
|-------------|---------|
416-
| `/config` | Config file (`backitup.config.yaml`) and database |
417-
| `/data` | Source files to backup (or mount your own paths) |
418-
| `/backups` | Local backup storage destination |
419+
| Mount Point | Purpose |
420+
| ----------- | ------------------------------------------------- |
421+
| `/config` | Config file (`backitup.config.yaml`) and database |
422+
| `/data` | Source files to backup (or mount your own paths) |
423+
| `/backups` | Local backup storage destination |
419424

420425
```bash
421426
docker run -d --name backitup \
@@ -561,6 +566,7 @@ WantedBy=multi-user.target
561566
| [list](docs/commands/list.md) | List existing backups |
562567
| [cleanup](docs/commands/cleanup.md) | Remove old backups |
563568
| [verify](docs/commands/verify.md) | Verify backup integrity |
569+
| [export-db](docs/commands/export-db.md) | Export the database file |
564570

565571
---
566572

docs/commands/backup.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ backitup backup -s manual \
137137
```
138138

139139
**Notes:**
140+
140141
- Containers are automatically restarted after backup completes
141142
- Containers with `restart: always` policy may auto-restart (a warning is logged)
142143
- If restart fails after all retries, backup succeeds but a warning is logged

docs/commands/export-db.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# export-db
2+
3+
Export the BackItUp SQLite database file to a specified location.
4+
5+
## Usage
6+
7+
```bash
8+
backitup export-db <output-path> [OPTIONS]
9+
```
10+
11+
## Arguments
12+
13+
| Argument | Description |
14+
| --------------- | -------------------------------------------- |
15+
| `<output-path>` | Path where the database backup will be saved |
16+
17+
## Options
18+
19+
| Option | Short | Description |
20+
| ----------------- | ----- | ------------------------------------------------------- |
21+
| `--config <path>` | `-c` | Path to config file (default: `./backitup.config.yaml`) |
22+
| `--verbose` | `-v` | Verbose output |
23+
| `--help` | `-h` | Show help message |
24+
25+
## Description
26+
27+
The `export-db` command creates a copy of the BackItUp SQLite database file. The database stores:
28+
29+
- Backup records (IDs, timestamps, sizes, checksums)
30+
- File counts and metadata
31+
- Storage locations (local paths, S3 keys)
32+
- Deletion logs for cleanup tracking
33+
34+
This is useful for:
35+
36+
- **Backup history preservation** — Keep your backup metadata safe
37+
- **Migration** — Move BackItUp to a new machine while retaining history
38+
- **Disaster recovery** — Restore backup tracking after data loss
39+
- **Snapshots** — Create checkpoints before major changes
40+
41+
## Examples
42+
43+
### Basic Usage
44+
45+
```bash
46+
# Export to a specific file
47+
backitup export-db ./backitup-db-backup.sqlite
48+
49+
# Export with timestamp in filename
50+
backitup export-db /backups/db/backitup-$(date +%Y%m%d).sqlite
51+
```
52+
53+
### With Custom Config
54+
55+
```bash
56+
# Use a specific config file
57+
backitup export-db ~/backup.sqlite -c /etc/backitup/config.yaml
58+
```
59+
60+
### Automated Backup Script
61+
62+
```bash
63+
#!/bin/bash
64+
# backup-everything.sh
65+
66+
# Export the database first
67+
backitup export-db /backups/meta/backitup-db-$(date +%Y%m%d-%H%M%S).sqlite
68+
69+
# Then run the regular backup
70+
backitup backup -s daily
71+
```
72+
73+
### Periodic Database Backup with Cron
74+
75+
```bash
76+
# Add to crontab: crontab -e
77+
# Export database daily at midnight
78+
0 0 * * * /usr/local/bin/backitup export-db /backups/db/backitup-$(date +\%Y\%m\%d).sqlite
79+
```
80+
81+
## Output
82+
83+
On success, the command displays a summary:
84+
85+
```
86+
◆ export-db
87+
88+
● Database exported
89+
90+
│ ╭─────────────────────────────────────────────────────────╮
91+
│ │ Export Summary │
92+
│ ├─────────────────────────────────────────────────────────┤
93+
│ │ Source: /home/user/.local/share/backitup/backitup.db │
94+
│ │ Output: /backups/backitup-db-backup.sqlite │
95+
│ │ Size: 156 KB │
96+
│ ╰─────────────────────────────────────────────────────────╯
97+
98+
◇ Database export complete!
99+
```
100+
101+
## Notes
102+
103+
- The command copies the database file as-is; no data transformation is performed
104+
- If the database doesn't exist yet (no backups created), the command will fail with an informative message
105+
- The output directory is created automatically if it doesn't exist
106+
- Existing files at the output path will be overwritten
107+
108+
## See Also
109+
110+
- [backup](backup.md) - Create backups
111+
- [list](list.md) - List existing backups
112+
- [Configuration Reference](../configuration.md) - Database path configuration

docs/configuration.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,15 @@ Patterns use glob syntax to include or exclude files. Patterns starting with `!`
159159

160160
**Examples:**
161161

162-
| Pattern | Description |
163-
| ------- | ----------- |
164-
| `**/*` | All files (default) |
165-
| `**/*.ts` | All TypeScript files |
166-
| `src/**` | Everything in src directory |
167-
| `!**/node_modules/**` | Exclude node_modules anywhere |
168-
| `!logs/**` | Exclude the logs directory |
169-
| `!*.log` | Exclude .log files in root only |
170-
| `!**/*.log` | Exclude all .log files |
162+
| Pattern | Description |
163+
| --------------------- | ------------------------------- |
164+
| `**/*` | All files (default) |
165+
| `**/*.ts` | All TypeScript files |
166+
| `src/**` | Everything in src directory |
167+
| `!**/node_modules/**` | Exclude node_modules anywhere |
168+
| `!logs/**` | Exclude the logs directory |
169+
| `!*.log` | Exclude .log files in root only |
170+
| `!**/*.log` | Exclude all .log files |
171171

172172
**Common mistake:** Using absolute paths in exclude patterns.
173173

@@ -266,12 +266,12 @@ Docker volume backup configuration.
266266

267267
**Container Stop Settings (`containerStop`):**
268268

269-
| Field | Type | Default | Description |
270-
| ------------------- | ------- | ------- | ----------------------------------------- |
271-
| `stopContainers` | boolean | `false` | Stop containers before backup |
272-
| `stopTimeout` | number | `30` | Seconds to wait for graceful stop |
273-
| `restartRetries` | number | `3` | Number of restart attempts if fails |
274-
| `restartRetryDelay` | number | `1000` | Milliseconds between restart retry |
269+
| Field | Type | Default | Description |
270+
| ------------------- | ------- | ------- | ----------------------------------- |
271+
| `stopContainers` | boolean | `false` | Stop containers before backup |
272+
| `stopTimeout` | number | `30` | Seconds to wait for graceful stop |
273+
| `restartRetries` | number | `3` | Number of restart attempts if fails |
274+
| `restartRetryDelay` | number | `1000` | Milliseconds between restart retry |
275275

276276
**Volume Entry:**
277277

docs/inline-config.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ BackItUp supports passing configuration options directly via command-line flags.
6464

6565
### Docker
6666

67-
| Option | Description |
68-
| ---------------------------- | ----------------------------------------- |
69-
| `--docker` | Enable Docker volume backups |
70-
| `--no-docker` | Disable Docker volume backups |
71-
| `--docker-volume <name>` | Docker volume to backup (repeatable) |
72-
| `--stop-containers` | Stop containers before volume backup |
73-
| `--no-stop-containers` | Don't stop containers (default behavior) |
74-
| `--stop-timeout <seconds>` | Timeout for graceful stop (default: 30) |
75-
| `--restart-retries <n>` | Retry attempts for restart (default: 3) |
67+
| Option | Description |
68+
| -------------------------- | ---------------------------------------- |
69+
| `--docker` | Enable Docker volume backups |
70+
| `--no-docker` | Disable Docker volume backups |
71+
| `--docker-volume <name>` | Docker volume to backup (repeatable) |
72+
| `--stop-containers` | Stop containers before volume backup |
73+
| `--no-stop-containers` | Don't stop containers (default behavior) |
74+
| `--stop-timeout <seconds>` | Timeout for graceful stop (default: 30) |
75+
| `--restart-retries <n>` | Retry attempts for restart (default: 3) |
7676

7777
## Usage Modes
7878

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
"url": "https://climactic.co"
99
},
1010
"funding": "https://github.com/sponsors/Climactic",
11-
"type": "module",
12-
"module": "src/index.ts",
1311
"bin": {
1412
"backitup": "./src/index.ts"
1513
},
14+
"type": "module",
15+
"module": "src/index.ts",
1616
"scripts": {
1717
"start": "bun run src/index.ts start",
1818
"dev": "bun --watch src/index.ts",

src/cli/commands/export-db.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { copyFile } from "node:fs/promises";
2+
import { dirname, resolve } from "node:path";
3+
import { parseArgs } from "node:util";
4+
import { findAndLoadConfig } from "../../config/loader";
5+
import { setLogLevel } from "../../utils/logger";
6+
import { color, ui } from "../ui";
7+
8+
export async function exportDbCommand(args: string[]): Promise<number> {
9+
const { values, positionals } = parseArgs({
10+
args,
11+
options: {
12+
config: { type: "string", short: "c" },
13+
verbose: { type: "boolean", short: "v", default: false },
14+
help: { type: "boolean", short: "h", default: false },
15+
},
16+
allowPositionals: true,
17+
});
18+
19+
if (values.help) {
20+
printHelp();
21+
return 0;
22+
}
23+
24+
if (values.verbose) {
25+
setLogLevel("debug");
26+
}
27+
28+
// Get output path from positional argument
29+
const outputPath = positionals[0];
30+
if (!outputPath) {
31+
ui.error("Missing required argument: <output-path>");
32+
ui.info("Run 'backitup export-db --help' for usage information.");
33+
return 1;
34+
}
35+
36+
try {
37+
const config = await findAndLoadConfig(values.config);
38+
const dbPath = config.database.path;
39+
40+
ui.banner("export-db");
41+
42+
// Check if database file exists
43+
const dbFile = Bun.file(dbPath);
44+
if (!(await dbFile.exists())) {
45+
ui.error(`Database file not found: ${dbPath}`);
46+
ui.info("No backups have been created yet, so there's nothing to export.");
47+
return 1;
48+
}
49+
50+
const resolvedOutput = resolve(outputPath);
51+
const s = ui.spinner();
52+
s.start("Exporting database...");
53+
54+
// Ensure output directory exists
55+
await Bun.write(dirname(resolvedOutput) + "/.keep", "");
56+
await Bun.file(dirname(resolvedOutput) + "/.keep").delete();
57+
58+
// Copy the database file
59+
await copyFile(dbPath, resolvedOutput);
60+
61+
const stats = await Bun.file(resolvedOutput).size;
62+
s.stop("Database exported");
63+
64+
ui.note(
65+
`Source: ${dbPath}\nOutput: ${resolvedOutput}\nSize: ${formatBytes(stats)}`,
66+
"Export Summary",
67+
);
68+
69+
ui.outro("Database export complete!");
70+
return 0;
71+
} catch (error) {
72+
ui.error(`Export failed: ${(error as Error).message}`);
73+
if (values.verbose) {
74+
console.error(error);
75+
}
76+
return 1;
77+
}
78+
}
79+
80+
function formatBytes(bytes: number): string {
81+
if (bytes === 0) return "0 B";
82+
const k = 1024;
83+
const sizes = ["B", "KB", "MB", "GB"];
84+
const i = Math.floor(Math.log(bytes) / Math.log(k));
85+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
86+
}
87+
88+
function printHelp(): void {
89+
console.log(`
90+
${color.bold("backitup export-db")} - Export the database file
91+
92+
${color.dim("USAGE:")}
93+
backitup export-db <output-path> [OPTIONS]
94+
95+
${color.dim("ARGUMENTS:")}
96+
<output-path> Path where the database backup will be saved
97+
98+
${color.dim("OPTIONS:")}
99+
-c, --config <path> Path to config file (default: ./backitup.config.yaml)
100+
-v, --verbose Verbose output
101+
-h, --help Show this help message
102+
103+
${color.dim("DESCRIPTION:")}
104+
Creates a copy of the BackItUp SQLite database file. This is useful for:
105+
- Backing up your backup history and metadata
106+
- Migrating to a new machine
107+
- Creating snapshots before major changes
108+
109+
${color.dim("EXAMPLES:")}
110+
backitup export-db ./backitup-db-backup.sqlite
111+
backitup export-db /backups/db/backitup-$(date +%Y%m%d).sqlite
112+
backitup export-db ~/backitup-db.sqlite -c /etc/backitup/config.yaml
113+
`);
114+
}

0 commit comments

Comments
 (0)