openclaw-workspace-sync 2.1.4 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -10
- package/README.src.md +229 -10
- package/TROUBLESHOOTING.md +20 -0
- package/dist/backup-manager.d.ts +67 -0
- package/dist/backup-manager.d.ts.map +1 -0
- package/dist/backup-manager.js +520 -0
- package/dist/backup-manager.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +185 -3
- package/dist/index.js.map +1 -1
- package/dist/sync-manager.d.ts +3 -1
- package/dist/sync-manager.d.ts.map +1 -1
- package/dist/sync-manager.js +18 -2
- package/dist/sync-manager.js.map +1 -1
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/diagrams/mode-3.svg +42 -0
- package/openclaw.plugin.json +129 -1
- package/package.json +1 -1
- package/skills/workspace-sync/SKILL.md +60 -10
package/README.md
CHANGED
|
@@ -56,13 +56,26 @@ On startup, the plugin bootstraps both directories:
|
|
|
56
56
|
|
|
57
57
|
Because the push explicitly excludes `_inbox/**` and `_outbox/**`, there is no risk of sync loops or accidental overwrites. Files only flow in one direction through each channel.
|
|
58
58
|
|
|
59
|
+
#### Inbox notifications (optional)
|
|
60
|
+
|
|
61
|
+
By default, mailbox mode is silent — files land in `_inbox` without waking the agent. This keeps costs at zero.
|
|
62
|
+
|
|
63
|
+
If you want the agent to react when files arrive, set `"notifyOnInbox": true`. After each drain that moves files, the plugin injects a system event like:
|
|
64
|
+
|
|
65
|
+
> `[workspace-sync] New files in _inbox: report.pdf, data.csv`
|
|
66
|
+
|
|
67
|
+
This wakes the agent on its next heartbeat. The agent sees the message and can process the files — for example, reading, summarizing, or filing them.
|
|
68
|
+
|
|
69
|
+
**This costs credits.** Each notification triggers an agent turn. Only enable it if you want the agent to actively respond to incoming files.
|
|
70
|
+
|
|
59
71
|
```json
|
|
60
72
|
{
|
|
61
73
|
"mode": "mailbox",
|
|
62
74
|
"provider": "dropbox",
|
|
63
75
|
"remotePath": "",
|
|
64
76
|
"localPath": "/",
|
|
65
|
-
"interval":
|
|
77
|
+
"interval": 180,
|
|
78
|
+
"notifyOnInbox": true
|
|
66
79
|
}
|
|
67
80
|
```
|
|
68
81
|
|
|
@@ -208,15 +221,28 @@ Add to your `openclaw.json`:
|
|
|
208
221
|
"openclaw-workspace-sync": {
|
|
209
222
|
"enabled": true,
|
|
210
223
|
"config": {
|
|
211
|
-
"
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
"sync": {
|
|
225
|
+
"provider": "dropbox",
|
|
226
|
+
"mode": "mailbox",
|
|
227
|
+
"remotePath": "",
|
|
228
|
+
"localPath": "/",
|
|
229
|
+
"interval": 60,
|
|
230
|
+
"timeout": 1800,
|
|
231
|
+
"onSessionStart": true,
|
|
232
|
+
"onSessionEnd": false,
|
|
233
|
+
"exclude": [".git/**", "node_modules/**", "*.log"]
|
|
234
|
+
},
|
|
235
|
+
"backup": {
|
|
236
|
+
"enabled": true,
|
|
237
|
+
"provider": "s3",
|
|
238
|
+
"bucket": "my-backups",
|
|
239
|
+
"prefix": "habibi/",
|
|
240
|
+
"interval": 86400,
|
|
241
|
+
"encrypt": true,
|
|
242
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
243
|
+
"include": ["workspace", "config", "cron", "memory"],
|
|
244
|
+
"retain": 7
|
|
245
|
+
}
|
|
220
246
|
}
|
|
221
247
|
}
|
|
222
248
|
}
|
|
@@ -224,6 +250,8 @@ Add to your `openclaw.json`:
|
|
|
224
250
|
}
|
|
225
251
|
```
|
|
226
252
|
|
|
253
|
+
> **Flat format still works.** Putting `provider`, `mode`, etc. at the config root (without `sync`) is supported for backwards compatibility. The nested `{ sync, backup }` format is recommended for clarity.
|
|
254
|
+
|
|
227
255
|
### Config reference
|
|
228
256
|
|
|
229
257
|
| Key | Type | Default | Description |
|
|
@@ -232,6 +260,7 @@ Add to your `openclaw.json`:
|
|
|
232
260
|
| `mode` | string | **required** | `mailbox` \| `mirror` \| `bisync` — see [Sync modes](#sync-modes-breaking-change-in-v20) |
|
|
233
261
|
| `ingest` | boolean | `false` | Enable local inbox for sending files to the agent (mirror mode only) |
|
|
234
262
|
| `ingestPath` | string | `"inbox"` | Local subfolder name for ingestion (relative to `localPath`) |
|
|
263
|
+
| `notifyOnInbox` | boolean | `false` | Wake the agent when files arrive in `_inbox` (mailbox mode). Off by default — enabling this costs LLM credits per notification. |
|
|
235
264
|
| `remotePath` | string | `"openclaw-share"` | Folder name in cloud storage |
|
|
236
265
|
| `localPath` | string | `"shared"` | Subfolder within workspace to sync |
|
|
237
266
|
| `interval` | number | `0` | Background sync interval in seconds (0 = manual only, min 60) |
|
|
@@ -447,6 +476,14 @@ Benefits:
|
|
|
447
476
|
- If token is compromised, blast radius is limited
|
|
448
477
|
- Clean separation — sync folder lives under `Apps/<your-app-name>/`
|
|
449
478
|
|
|
479
|
+
### Dropbox rate limiting
|
|
480
|
+
|
|
481
|
+
Dropbox enforces API rate limits (`too_many_requests`). If your workspace has many files (10k+), each sync cycle can consume a large number of API calls just for checking. To avoid hitting limits:
|
|
482
|
+
|
|
483
|
+
- **Set `interval` high enough** for the sync to complete between cycles. A workspace with ~40k files takes ~2 minutes to scan. An `interval` of 180 (3 min) is the minimum; 300 (5 min) is safer.
|
|
484
|
+
- **Use `exclude` patterns liberally** — skip `node_modules`, `.git`, `__pycache__`, build output, and anything you don't need synced. Fewer files = fewer API calls.
|
|
485
|
+
- **If you see `too_many_requests` errors** in the logs, increase the interval and add more excludes.
|
|
486
|
+
|
|
450
487
|
## Understanding sync safety
|
|
451
488
|
|
|
452
489
|
Cloud sync involves two copies of your data. When things go wrong, one side can overwrite the other. Here is what to keep in mind:
|
|
@@ -541,6 +578,171 @@ These recommendations apply regardless of whether you use this plugin. Cloud syn
|
|
|
541
578
|
- **Encryption**: Consider [rclone crypt](https://rclone.org/crypt/) for sensitive data
|
|
542
579
|
- **App folder**: Use Dropbox app folder access for minimal permissions
|
|
543
580
|
|
|
581
|
+
## Encrypted backups
|
|
582
|
+
|
|
583
|
+
Back up your entire agent system — workspace, config, cron jobs, memory, sessions — as encrypted snapshots to your own cloud storage. Your bucket, your encryption key, zero monthly fees.
|
|
584
|
+
|
|
585
|
+
### How it works
|
|
586
|
+
|
|
587
|
+
<p align="center">
|
|
588
|
+
<img src="https://raw.githubusercontent.com/ashbrener/openclaw-workspace-sync/main/docs/diagrams/mode-3.svg" alt="backup pipeline diagram" width="700" />
|
|
589
|
+
</p>
|
|
590
|
+
|
|
591
|
+
1. **Streams directly** — `tar | [openssl enc] | rclone rcat` piped straight to the remote. Zero local temp files, zero extra disk needed. A 10 GB workspace on a 1 GB free volume? No problem.
|
|
592
|
+
2. Optionally encrypts with AES-256 (client-side, before upload) via `openssl`
|
|
593
|
+
3. Uploads via rclone to any supported provider (S3, R2, Backblaze B2, Dropbox, etc.)
|
|
594
|
+
4. Prunes old snapshots based on your retention policy
|
|
595
|
+
|
|
596
|
+
> **Disk-constrained?** Because backups stream directly, you don't need any free disk space for the backup itself. Only the restore downloads to a staging directory.
|
|
597
|
+
|
|
598
|
+
### Configuration
|
|
599
|
+
|
|
600
|
+
Add `sync` and `backup` blocks to your plugin config. The backup provider can differ from your sync provider — sync to Dropbox for live mirror, backup to S3 for disaster recovery:
|
|
601
|
+
|
|
602
|
+
```json
|
|
603
|
+
{
|
|
604
|
+
"sync": {
|
|
605
|
+
"provider": "dropbox",
|
|
606
|
+
"mode": "mailbox",
|
|
607
|
+
"remotePath": "",
|
|
608
|
+
"interval": 180
|
|
609
|
+
},
|
|
610
|
+
"backup": {
|
|
611
|
+
"enabled": true,
|
|
612
|
+
"provider": "s3",
|
|
613
|
+
"bucket": "my-backups",
|
|
614
|
+
"prefix": "habibi/",
|
|
615
|
+
"interval": 86400,
|
|
616
|
+
"encrypt": true,
|
|
617
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
618
|
+
"include": ["workspace", "config", "cron", "memory"],
|
|
619
|
+
"retain": { "daily": 7, "weekly": 4 }
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Backup config reference
|
|
625
|
+
|
|
626
|
+
| Key | Type | Default | Description |
|
|
627
|
+
|-----|------|---------|-------------|
|
|
628
|
+
| `enabled` | boolean | `false` | Enable scheduled backups |
|
|
629
|
+
| `provider` | string | parent provider | Cloud provider for backup storage |
|
|
630
|
+
| `bucket` | string | — | S3/R2 bucket name |
|
|
631
|
+
| `prefix` | string | `""` | Path prefix within the bucket (e.g., `habibi/`) |
|
|
632
|
+
| `interval` | number | `86400` | Backup interval in seconds (86400 = daily; clamped to min 300) |
|
|
633
|
+
| `encrypt` | boolean | `false` | Encrypt snapshots with AES-256 before upload |
|
|
634
|
+
| `passphrase` | string | — | Encryption passphrase (use `${BACKUP_PASSPHRASE}` env var) |
|
|
635
|
+
| `include` | string[] | see below | What to back up |
|
|
636
|
+
| `retain` | number or object | `7` | Retention: `7` = keep 7 latest, or `{ daily: 7, weekly: 4 }` |
|
|
637
|
+
| `exclude` | string[] | parent excludes | Glob patterns to exclude from workspace backup |
|
|
638
|
+
|
|
639
|
+
### Include options
|
|
640
|
+
|
|
641
|
+
| Item | What gets backed up |
|
|
642
|
+
|------|---------------------|
|
|
643
|
+
| `workspace` | The workspace directory (agent files, projects, etc.) |
|
|
644
|
+
| `config` | `openclaw.json` |
|
|
645
|
+
| `cron` | Cron job schedules and state |
|
|
646
|
+
| `memory` | Memory files (MEMORY.md, etc.) |
|
|
647
|
+
| `sessions` | Session metadata and store |
|
|
648
|
+
| `credentials` | Auth profile credentials |
|
|
649
|
+
| `skills` | Skill files |
|
|
650
|
+
| `hooks` | Webhook configurations and state (Gmail watch, custom hooks) |
|
|
651
|
+
| `extensions` | Installed plugins/extensions (for reproducible restores) |
|
|
652
|
+
| `env` | Environment variables file (`.env`) |
|
|
653
|
+
| `agents` | Multi-agent state (per-agent sessions, subagent registry) |
|
|
654
|
+
| `pages` | Custom pages served by the gateway |
|
|
655
|
+
| `transcripts` | Full conversation logs (JSONL session transcripts) |
|
|
656
|
+
|
|
657
|
+
Default: `["workspace", "config", "cron", "memory"]`
|
|
658
|
+
|
|
659
|
+
### CLI commands
|
|
660
|
+
|
|
661
|
+
```bash
|
|
662
|
+
# Create a backup now
|
|
663
|
+
openclaw workspace-sync backup now
|
|
664
|
+
|
|
665
|
+
# List available snapshots
|
|
666
|
+
openclaw workspace-sync backup list
|
|
667
|
+
|
|
668
|
+
# Restore the latest snapshot
|
|
669
|
+
openclaw workspace-sync backup restore
|
|
670
|
+
|
|
671
|
+
# Restore a specific snapshot
|
|
672
|
+
openclaw workspace-sync backup restore --snapshot backup-20260310T020000Z.tar.gz.enc
|
|
673
|
+
|
|
674
|
+
# Restore to a specific directory (safe — doesn't overwrite live data)
|
|
675
|
+
openclaw workspace-sync backup restore --to /tmp/restore-test
|
|
676
|
+
|
|
677
|
+
# Check backup service status
|
|
678
|
+
openclaw workspace-sync backup status
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Restore safety
|
|
682
|
+
|
|
683
|
+
By default, `restore` extracts to a staging directory (`~/.openclaw/.backup-restore/`), not directly over your live workspace. This lets you inspect the contents before copying them into place. Use `--to` to control where files land.
|
|
684
|
+
|
|
685
|
+
### Provider examples
|
|
686
|
+
|
|
687
|
+
**Cloudflare R2 (free tier: 10GB):**
|
|
688
|
+
|
|
689
|
+
```json
|
|
690
|
+
{
|
|
691
|
+
"backup": {
|
|
692
|
+
"enabled": true,
|
|
693
|
+
"provider": "s3",
|
|
694
|
+
"encrypt": true,
|
|
695
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
696
|
+
"s3": {
|
|
697
|
+
"endpoint": "https://<account-id>.r2.cloudflarestorage.com",
|
|
698
|
+
"bucket": "openclaw-backups",
|
|
699
|
+
"region": "auto",
|
|
700
|
+
"accessKeyId": "${R2_ACCESS_KEY}",
|
|
701
|
+
"secretAccessKey": "${R2_SECRET_KEY}"
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
**AWS S3:**
|
|
708
|
+
|
|
709
|
+
```json
|
|
710
|
+
{
|
|
711
|
+
"backup": {
|
|
712
|
+
"enabled": true,
|
|
713
|
+
"provider": "s3",
|
|
714
|
+
"encrypt": true,
|
|
715
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
716
|
+
"s3": {
|
|
717
|
+
"bucket": "my-openclaw-backups",
|
|
718
|
+
"region": "us-east-1",
|
|
719
|
+
"accessKeyId": "${AWS_ACCESS_KEY_ID}",
|
|
720
|
+
"secretAccessKey": "${AWS_SECRET_ACCESS_KEY}"
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
**Backblaze B2:**
|
|
727
|
+
|
|
728
|
+
```json
|
|
729
|
+
{
|
|
730
|
+
"backup": {
|
|
731
|
+
"enabled": true,
|
|
732
|
+
"provider": "s3",
|
|
733
|
+
"encrypt": true,
|
|
734
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
735
|
+
"s3": {
|
|
736
|
+
"endpoint": "https://s3.us-west-002.backblazeb2.com",
|
|
737
|
+
"bucket": "openclaw-backups",
|
|
738
|
+
"region": "us-west-002",
|
|
739
|
+
"accessKeyId": "${B2_KEY_ID}",
|
|
740
|
+
"secretAccessKey": "${B2_APP_KEY}"
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
544
746
|
## Development
|
|
545
747
|
|
|
546
748
|
```bash
|
package/README.src.md
CHANGED
|
@@ -72,13 +72,26 @@ On startup, the plugin bootstraps both directories:
|
|
|
72
72
|
|
|
73
73
|
Because the push explicitly excludes `_inbox/**` and `_outbox/**`, there is no risk of sync loops or accidental overwrites. Files only flow in one direction through each channel.
|
|
74
74
|
|
|
75
|
+
#### Inbox notifications (optional)
|
|
76
|
+
|
|
77
|
+
By default, mailbox mode is silent — files land in `_inbox` without waking the agent. This keeps costs at zero.
|
|
78
|
+
|
|
79
|
+
If you want the agent to react when files arrive, set `"notifyOnInbox": true`. After each drain that moves files, the plugin injects a system event like:
|
|
80
|
+
|
|
81
|
+
> `[workspace-sync] New files in _inbox: report.pdf, data.csv`
|
|
82
|
+
|
|
83
|
+
This wakes the agent on its next heartbeat. The agent sees the message and can process the files — for example, reading, summarizing, or filing them.
|
|
84
|
+
|
|
85
|
+
**This costs credits.** Each notification triggers an agent turn. Only enable it if you want the agent to actively respond to incoming files.
|
|
86
|
+
|
|
75
87
|
```json
|
|
76
88
|
{
|
|
77
89
|
"mode": "mailbox",
|
|
78
90
|
"provider": "dropbox",
|
|
79
91
|
"remotePath": "",
|
|
80
92
|
"localPath": "/",
|
|
81
|
-
"interval":
|
|
93
|
+
"interval": 180,
|
|
94
|
+
"notifyOnInbox": true
|
|
82
95
|
}
|
|
83
96
|
```
|
|
84
97
|
|
|
@@ -249,15 +262,28 @@ Add to your `openclaw.json`:
|
|
|
249
262
|
"openclaw-workspace-sync": {
|
|
250
263
|
"enabled": true,
|
|
251
264
|
"config": {
|
|
252
|
-
"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
265
|
+
"sync": {
|
|
266
|
+
"provider": "dropbox",
|
|
267
|
+
"mode": "mailbox",
|
|
268
|
+
"remotePath": "",
|
|
269
|
+
"localPath": "/",
|
|
270
|
+
"interval": 60,
|
|
271
|
+
"timeout": 1800,
|
|
272
|
+
"onSessionStart": true,
|
|
273
|
+
"onSessionEnd": false,
|
|
274
|
+
"exclude": [".git/**", "node_modules/**", "*.log"]
|
|
275
|
+
},
|
|
276
|
+
"backup": {
|
|
277
|
+
"enabled": true,
|
|
278
|
+
"provider": "s3",
|
|
279
|
+
"bucket": "my-backups",
|
|
280
|
+
"prefix": "habibi/",
|
|
281
|
+
"interval": 86400,
|
|
282
|
+
"encrypt": true,
|
|
283
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
284
|
+
"include": ["workspace", "config", "cron", "memory"],
|
|
285
|
+
"retain": 7
|
|
286
|
+
}
|
|
261
287
|
}
|
|
262
288
|
}
|
|
263
289
|
}
|
|
@@ -265,6 +291,8 @@ Add to your `openclaw.json`:
|
|
|
265
291
|
}
|
|
266
292
|
```
|
|
267
293
|
|
|
294
|
+
> **Flat format still works.** Putting `provider`, `mode`, etc. at the config root (without `sync`) is supported for backwards compatibility. The nested `{ sync, backup }` format is recommended for clarity.
|
|
295
|
+
|
|
268
296
|
### Config reference
|
|
269
297
|
|
|
270
298
|
| Key | Type | Default | Description |
|
|
@@ -273,6 +301,7 @@ Add to your `openclaw.json`:
|
|
|
273
301
|
| `mode` | string | **required** | `mailbox` \| `mirror` \| `bisync` — see [Sync modes](#sync-modes-breaking-change-in-v20) |
|
|
274
302
|
| `ingest` | boolean | `false` | Enable local inbox for sending files to the agent (mirror mode only) |
|
|
275
303
|
| `ingestPath` | string | `"inbox"` | Local subfolder name for ingestion (relative to `localPath`) |
|
|
304
|
+
| `notifyOnInbox` | boolean | `false` | Wake the agent when files arrive in `_inbox` (mailbox mode). Off by default — enabling this costs LLM credits per notification. |
|
|
276
305
|
| `remotePath` | string | `"openclaw-share"` | Folder name in cloud storage |
|
|
277
306
|
| `localPath` | string | `"shared"` | Subfolder within workspace to sync |
|
|
278
307
|
| `interval` | number | `0` | Background sync interval in seconds (0 = manual only, min 60) |
|
|
@@ -488,6 +517,14 @@ Benefits:
|
|
|
488
517
|
- If token is compromised, blast radius is limited
|
|
489
518
|
- Clean separation — sync folder lives under `Apps/<your-app-name>/`
|
|
490
519
|
|
|
520
|
+
### Dropbox rate limiting
|
|
521
|
+
|
|
522
|
+
Dropbox enforces API rate limits (`too_many_requests`). If your workspace has many files (10k+), each sync cycle can consume a large number of API calls just for checking. To avoid hitting limits:
|
|
523
|
+
|
|
524
|
+
- **Set `interval` high enough** for the sync to complete between cycles. A workspace with ~40k files takes ~2 minutes to scan. An `interval` of 180 (3 min) is the minimum; 300 (5 min) is safer.
|
|
525
|
+
- **Use `exclude` patterns liberally** — skip `node_modules`, `.git`, `__pycache__`, build output, and anything you don't need synced. Fewer files = fewer API calls.
|
|
526
|
+
- **If you see `too_many_requests` errors** in the logs, increase the interval and add more excludes.
|
|
527
|
+
|
|
491
528
|
## Understanding sync safety
|
|
492
529
|
|
|
493
530
|
Cloud sync involves two copies of your data. When things go wrong, one side can overwrite the other. Here is what to keep in mind:
|
|
@@ -582,6 +619,188 @@ These recommendations apply regardless of whether you use this plugin. Cloud syn
|
|
|
582
619
|
- **Encryption**: Consider [rclone crypt](https://rclone.org/crypt/) for sensitive data
|
|
583
620
|
- **App folder**: Use Dropbox app folder access for minimal permissions
|
|
584
621
|
|
|
622
|
+
## Encrypted backups
|
|
623
|
+
|
|
624
|
+
Back up your entire agent system — workspace, config, cron jobs, memory, sessions — as encrypted snapshots to your own cloud storage. Your bucket, your encryption key, zero monthly fees.
|
|
625
|
+
|
|
626
|
+
### How it works
|
|
627
|
+
|
|
628
|
+
```mermaid
|
|
629
|
+
flowchart TD
|
|
630
|
+
subgraph GW["Gateway"]
|
|
631
|
+
WS["/workspace"]
|
|
632
|
+
CFG["config / cron / memory"]
|
|
633
|
+
end
|
|
634
|
+
TAR["tar cz"]
|
|
635
|
+
ENC["openssl enc\n(AES-256)"]
|
|
636
|
+
RCAT["rclone rcat"]
|
|
637
|
+
subgraph REMOTE["Your Bucket (S3 / R2 / B2)"]
|
|
638
|
+
SNAP["backup-2026…Z.tar.gz.enc"]
|
|
639
|
+
OLD["older snapshots"]
|
|
640
|
+
end
|
|
641
|
+
WS --> TAR
|
|
642
|
+
CFG --> TAR
|
|
643
|
+
TAR -- "pipe" --> ENC
|
|
644
|
+
ENC -- "pipe" --> RCAT
|
|
645
|
+
RCAT --> SNAP
|
|
646
|
+
SNAP -. "retention prune" .-> OLD
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
1. **Streams directly** — `tar | [openssl enc] | rclone rcat` piped straight to the remote. Zero local temp files, zero extra disk needed. A 10 GB workspace on a 1 GB free volume? No problem.
|
|
650
|
+
2. Optionally encrypts with AES-256 (client-side, before upload) via `openssl`
|
|
651
|
+
3. Uploads via rclone to any supported provider (S3, R2, Backblaze B2, Dropbox, etc.)
|
|
652
|
+
4. Prunes old snapshots based on your retention policy
|
|
653
|
+
|
|
654
|
+
> **Disk-constrained?** Because backups stream directly, you don't need any free disk space for the backup itself. Only the restore downloads to a staging directory.
|
|
655
|
+
|
|
656
|
+
### Configuration
|
|
657
|
+
|
|
658
|
+
Add `sync` and `backup` blocks to your plugin config. The backup provider can differ from your sync provider — sync to Dropbox for live mirror, backup to S3 for disaster recovery:
|
|
659
|
+
|
|
660
|
+
```json
|
|
661
|
+
{
|
|
662
|
+
"sync": {
|
|
663
|
+
"provider": "dropbox",
|
|
664
|
+
"mode": "mailbox",
|
|
665
|
+
"remotePath": "",
|
|
666
|
+
"interval": 180
|
|
667
|
+
},
|
|
668
|
+
"backup": {
|
|
669
|
+
"enabled": true,
|
|
670
|
+
"provider": "s3",
|
|
671
|
+
"bucket": "my-backups",
|
|
672
|
+
"prefix": "habibi/",
|
|
673
|
+
"interval": 86400,
|
|
674
|
+
"encrypt": true,
|
|
675
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
676
|
+
"include": ["workspace", "config", "cron", "memory"],
|
|
677
|
+
"retain": { "daily": 7, "weekly": 4 }
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Backup config reference
|
|
683
|
+
|
|
684
|
+
| Key | Type | Default | Description |
|
|
685
|
+
|-----|------|---------|-------------|
|
|
686
|
+
| `enabled` | boolean | `false` | Enable scheduled backups |
|
|
687
|
+
| `provider` | string | parent provider | Cloud provider for backup storage |
|
|
688
|
+
| `bucket` | string | — | S3/R2 bucket name |
|
|
689
|
+
| `prefix` | string | `""` | Path prefix within the bucket (e.g., `habibi/`) |
|
|
690
|
+
| `interval` | number | `86400` | Backup interval in seconds (86400 = daily; clamped to min 300) |
|
|
691
|
+
| `encrypt` | boolean | `false` | Encrypt snapshots with AES-256 before upload |
|
|
692
|
+
| `passphrase` | string | — | Encryption passphrase (use `${BACKUP_PASSPHRASE}` env var) |
|
|
693
|
+
| `include` | string[] | see below | What to back up |
|
|
694
|
+
| `retain` | number or object | `7` | Retention: `7` = keep 7 latest, or `{ daily: 7, weekly: 4 }` |
|
|
695
|
+
| `exclude` | string[] | parent excludes | Glob patterns to exclude from workspace backup |
|
|
696
|
+
|
|
697
|
+
### Include options
|
|
698
|
+
|
|
699
|
+
| Item | What gets backed up |
|
|
700
|
+
|------|---------------------|
|
|
701
|
+
| `workspace` | The workspace directory (agent files, projects, etc.) |
|
|
702
|
+
| `config` | `openclaw.json` |
|
|
703
|
+
| `cron` | Cron job schedules and state |
|
|
704
|
+
| `memory` | Memory files (MEMORY.md, etc.) |
|
|
705
|
+
| `sessions` | Session metadata and store |
|
|
706
|
+
| `credentials` | Auth profile credentials |
|
|
707
|
+
| `skills` | Skill files |
|
|
708
|
+
| `hooks` | Webhook configurations and state (Gmail watch, custom hooks) |
|
|
709
|
+
| `extensions` | Installed plugins/extensions (for reproducible restores) |
|
|
710
|
+
| `env` | Environment variables file (`.env`) |
|
|
711
|
+
| `agents` | Multi-agent state (per-agent sessions, subagent registry) |
|
|
712
|
+
| `pages` | Custom pages served by the gateway |
|
|
713
|
+
| `transcripts` | Full conversation logs (JSONL session transcripts) |
|
|
714
|
+
|
|
715
|
+
Default: `["workspace", "config", "cron", "memory"]`
|
|
716
|
+
|
|
717
|
+
### CLI commands
|
|
718
|
+
|
|
719
|
+
```bash
|
|
720
|
+
# Create a backup now
|
|
721
|
+
openclaw workspace-sync backup now
|
|
722
|
+
|
|
723
|
+
# List available snapshots
|
|
724
|
+
openclaw workspace-sync backup list
|
|
725
|
+
|
|
726
|
+
# Restore the latest snapshot
|
|
727
|
+
openclaw workspace-sync backup restore
|
|
728
|
+
|
|
729
|
+
# Restore a specific snapshot
|
|
730
|
+
openclaw workspace-sync backup restore --snapshot backup-20260310T020000Z.tar.gz.enc
|
|
731
|
+
|
|
732
|
+
# Restore to a specific directory (safe — doesn't overwrite live data)
|
|
733
|
+
openclaw workspace-sync backup restore --to /tmp/restore-test
|
|
734
|
+
|
|
735
|
+
# Check backup service status
|
|
736
|
+
openclaw workspace-sync backup status
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Restore safety
|
|
740
|
+
|
|
741
|
+
By default, `restore` extracts to a staging directory (`~/.openclaw/.backup-restore/`), not directly over your live workspace. This lets you inspect the contents before copying them into place. Use `--to` to control where files land.
|
|
742
|
+
|
|
743
|
+
### Provider examples
|
|
744
|
+
|
|
745
|
+
**Cloudflare R2 (free tier: 10GB):**
|
|
746
|
+
|
|
747
|
+
```json
|
|
748
|
+
{
|
|
749
|
+
"backup": {
|
|
750
|
+
"enabled": true,
|
|
751
|
+
"provider": "s3",
|
|
752
|
+
"encrypt": true,
|
|
753
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
754
|
+
"s3": {
|
|
755
|
+
"endpoint": "https://<account-id>.r2.cloudflarestorage.com",
|
|
756
|
+
"bucket": "openclaw-backups",
|
|
757
|
+
"region": "auto",
|
|
758
|
+
"accessKeyId": "${R2_ACCESS_KEY}",
|
|
759
|
+
"secretAccessKey": "${R2_SECRET_KEY}"
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
**AWS S3:**
|
|
766
|
+
|
|
767
|
+
```json
|
|
768
|
+
{
|
|
769
|
+
"backup": {
|
|
770
|
+
"enabled": true,
|
|
771
|
+
"provider": "s3",
|
|
772
|
+
"encrypt": true,
|
|
773
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
774
|
+
"s3": {
|
|
775
|
+
"bucket": "my-openclaw-backups",
|
|
776
|
+
"region": "us-east-1",
|
|
777
|
+
"accessKeyId": "${AWS_ACCESS_KEY_ID}",
|
|
778
|
+
"secretAccessKey": "${AWS_SECRET_ACCESS_KEY}"
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
**Backblaze B2:**
|
|
785
|
+
|
|
786
|
+
```json
|
|
787
|
+
{
|
|
788
|
+
"backup": {
|
|
789
|
+
"enabled": true,
|
|
790
|
+
"provider": "s3",
|
|
791
|
+
"encrypt": true,
|
|
792
|
+
"passphrase": "${BACKUP_PASSPHRASE}",
|
|
793
|
+
"s3": {
|
|
794
|
+
"endpoint": "https://s3.us-west-002.backblazeb2.com",
|
|
795
|
+
"bucket": "openclaw-backups",
|
|
796
|
+
"region": "us-west-002",
|
|
797
|
+
"accessKeyId": "${B2_KEY_ID}",
|
|
798
|
+
"secretAccessKey": "${B2_APP_KEY}"
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
585
804
|
## Development
|
|
586
805
|
|
|
587
806
|
```bash
|
package/TROUBLESHOOTING.md
CHANGED
|
@@ -85,6 +85,26 @@ rclone sync <remote>:<path> /data/workspace/ --config <config-path> --verbose
|
|
|
85
85
|
# Ctrl+B, D to detach; tmux attach -t sync to reconnect
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
### Dropbox rate limiting (`too_many_requests`)
|
|
89
|
+
|
|
90
|
+
**Cause:** Dropbox enforces API rate limits. Each sync cycle checks every file in the workspace, so large workspaces (10k+ files) use many API calls. If the sync interval is shorter than the scan time, cycles overlap and requests pile up.
|
|
91
|
+
|
|
92
|
+
**Symptoms:** Logs show `NOTICE: Error too_many_requests/... Trying again in 300 seconds`. Sync stalls for 5 minutes waiting for the retry.
|
|
93
|
+
|
|
94
|
+
**Fix:**
|
|
95
|
+
1. Increase `interval` — if a scan takes ~2 minutes, set interval to at least 180 (3 min), ideally 300 (5 min)
|
|
96
|
+
2. Add more `exclude` patterns to reduce the number of files scanned:
|
|
97
|
+
```json
|
|
98
|
+
"exclude": [
|
|
99
|
+
"**/node_modules/**",
|
|
100
|
+
"**/.git/**",
|
|
101
|
+
"**/__pycache__/**",
|
|
102
|
+
"**/dist/**",
|
|
103
|
+
"**/.cache/**"
|
|
104
|
+
]
|
|
105
|
+
```
|
|
106
|
+
3. If already rate-limited, wait 5–10 minutes before the next cycle — rclone retries automatically
|
|
107
|
+
|
|
88
108
|
### "directory not found" with Dropbox app folder
|
|
89
109
|
|
|
90
110
|
**Cause:** Your `remotePath` is set to the app folder name (e.g. `"openclaw-sync"`) instead of `""`. With Dropbox app folders, the app folder IS the root — rclone sees it as `/`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backup manager — encrypted snapshot backups to cloud storage.
|
|
3
|
+
*
|
|
4
|
+
* Streams tar | [openssl enc] | rclone rcat directly to the remote —
|
|
5
|
+
* zero local disk usage. Optionally encrypts with AES-256 via openssl.
|
|
6
|
+
* Prunes old snapshots based on retention policy.
|
|
7
|
+
*/
|
|
8
|
+
import type { WorkspaceSyncConfig } from "./types.js";
|
|
9
|
+
type Logger = {
|
|
10
|
+
debug?: (msg: string) => void;
|
|
11
|
+
info: (msg: string) => void;
|
|
12
|
+
warn: (msg: string) => void;
|
|
13
|
+
error: (msg: string) => void;
|
|
14
|
+
};
|
|
15
|
+
export type BackupResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
snapshotName: string;
|
|
18
|
+
encrypted: boolean;
|
|
19
|
+
sizeBytes: number;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
error: string;
|
|
23
|
+
};
|
|
24
|
+
export type RestoreResult = {
|
|
25
|
+
ok: true;
|
|
26
|
+
snapshotName: string;
|
|
27
|
+
restoredTo: string;
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function runBackup(params: {
|
|
33
|
+
syncConfig: WorkspaceSyncConfig;
|
|
34
|
+
workspaceDir: string;
|
|
35
|
+
stateDir: string;
|
|
36
|
+
logger: Logger;
|
|
37
|
+
}): Promise<BackupResult>;
|
|
38
|
+
export declare function listBackupSnapshots(params: {
|
|
39
|
+
syncConfig: WorkspaceSyncConfig;
|
|
40
|
+
stateDir: string;
|
|
41
|
+
logger: Logger;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
ok: true;
|
|
44
|
+
snapshots: string[];
|
|
45
|
+
} | {
|
|
46
|
+
ok: false;
|
|
47
|
+
error: string;
|
|
48
|
+
}>;
|
|
49
|
+
export declare function runRestore(params: {
|
|
50
|
+
syncConfig: WorkspaceSyncConfig;
|
|
51
|
+
workspaceDir: string;
|
|
52
|
+
stateDir: string;
|
|
53
|
+
snapshotName: string | "latest";
|
|
54
|
+
restoreTo: string;
|
|
55
|
+
logger: Logger;
|
|
56
|
+
}): Promise<RestoreResult>;
|
|
57
|
+
export declare function startBackupManager(syncConfig: WorkspaceSyncConfig, workspaceDir: string, stateDir: string, logger: Logger): void;
|
|
58
|
+
export declare function stopBackupManager(): void;
|
|
59
|
+
export declare function getBackupManagerStatus(): {
|
|
60
|
+
running: boolean;
|
|
61
|
+
lastBackupAt: Date | null;
|
|
62
|
+
lastBackupOk: boolean | null;
|
|
63
|
+
backupCount: number;
|
|
64
|
+
errorCount: number;
|
|
65
|
+
};
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=backup-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-manager.d.ts","sourceRoot":"","sources":["../src/backup-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAiD,mBAAmB,EAAyB,MAAM,YAAY,CAAC;AAa5H,KAAK,MAAM,GAAG;IACZ,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B,CAAC;AAiTF,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,wBAAsB,SAAS,CAAC,MAAM,EAAE;IACtC,UAAU,EAAE,mBAAmB,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,YAAY,CAAC,CAiFxB;AA2BD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,mBAAmB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAe5E;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE;IACvC,UAAU,EAAE,mBAAmB,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,QAAQ,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,aAAa,CAAC,CAoGzB;AAoED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,mBAAmB,EAC/B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,IAAI,CAyBN;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED,wBAAgB,sBAAsB,IAAI;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAQA"}
|