@dambrogia/openclaw-agents-backup 0.1.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/.eslintrc.json +24 -0
- package/.github/workflows/test.yml +40 -0
- package/BACKUP_GITIGNORE_TEMPLATE +51 -0
- package/LICENSE +21 -0
- package/README.md +342 -0
- package/SKILL.md +355 -0
- package/dist/cli.js +191 -0
- package/dist/index.js +22 -0
- package/package.json +63 -0
- package/src/agentLister.ts +23 -0
- package/src/backup.ts +190 -0
- package/src/cli.ts +176 -0
- package/src/encryptionService.ts +126 -0
- package/src/index.ts +5 -0
- package/src/restore.ts +143 -0
- package/src/types.ts +71 -0
- package/src/utils.ts +170 -0
- package/tests/agentLister.test.ts +71 -0
- package/tests/backup.test.ts +92 -0
- package/tests/encryptionService.test.ts +278 -0
- package/tests/restore.test.ts +167 -0
- package/tests/utils.test.ts +51 -0
- package/tsconfig.json +25 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# Skill: OpenClaw Agents Backup
|
|
2
|
+
|
|
3
|
+
Backup and restore OpenClaw multi-agent workspaces. Centralized backup of all agent configuration files, workspaces, and agent directories with disaster recovery capabilities.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This skill enables automated hourly backups of OpenClaw agents in multi-agent setups. It discovers all active agents via the OpenClaw API, backs up their workspace and agent directories to a centralized Git repository, and provides restore functionality for disaster recovery.
|
|
8
|
+
|
|
9
|
+
### Use Cases
|
|
10
|
+
|
|
11
|
+
- **Multi-agent deployments** — Back up multiple agents in a single OpenClaw instance
|
|
12
|
+
- **Disaster recovery** — Wipe a VPS and restore agent state to any point in history
|
|
13
|
+
- **Configuration snapshots** — Track changes to agent identity, workspace files, and session history
|
|
14
|
+
- **Compliance** — Maintain audit trail of agent configuration changes
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
This skill is designed as a TypeScript library. Install it in your project:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @dambrogia/openclaw-agents-backup
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or clone from GitHub and use as a local dependency.
|
|
25
|
+
|
|
26
|
+
## Usage Patterns
|
|
27
|
+
|
|
28
|
+
### For End Users: Agent-Driven Backups
|
|
29
|
+
|
|
30
|
+
Install the package once. Agent calls CLI commands. User tells agent what they need.
|
|
31
|
+
|
|
32
|
+
**Setup:**
|
|
33
|
+
```bash
|
|
34
|
+
npm install @dambrogia/openclaw-agents-backup
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Agent calls:**
|
|
38
|
+
```bash
|
|
39
|
+
backup-agents backup # Back up now
|
|
40
|
+
backup-agents restore # Restore latest
|
|
41
|
+
backup-agents restore --sha ABC123 # Point-in-time
|
|
42
|
+
backup-agents history # Show log
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**User interaction:**
|
|
46
|
+
- "Back up my agents" → Agent runs `backup-agents backup`
|
|
47
|
+
- "Restore my agents" → Agent runs `backup-agents restore`
|
|
48
|
+
- "Show backup history" → Agent runs `backup-agents history`
|
|
49
|
+
|
|
50
|
+
### For Developers: Direct Library Use
|
|
51
|
+
|
|
52
|
+
Install as a dependency and call functions directly in your code.
|
|
53
|
+
|
|
54
|
+
## Setup
|
|
55
|
+
|
|
56
|
+
### 1. Create Backup Repository
|
|
57
|
+
|
|
58
|
+
Initialize a private Git repository where backups will be stored:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
mkdir agents-backup
|
|
62
|
+
cd agents-backup
|
|
63
|
+
git init
|
|
64
|
+
git config user.email "backup@example.com"
|
|
65
|
+
git config user.name "Backup Agent"
|
|
66
|
+
git commit --allow-empty -m "Initial commit"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. Create Backup Configuration
|
|
70
|
+
|
|
71
|
+
In your OpenClaw workspace, create `.backupconfig.json`:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"backupRepoPath": "/path/to/agents-backup"
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The `backupRepoPath` should point to the local backup Git repository (already initialized and with a remote configured).
|
|
80
|
+
|
|
81
|
+
### 3. Schedule Hourly Backups (via Cron)
|
|
82
|
+
|
|
83
|
+
Use OpenClaw's cron functionality to schedule hourly backups:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const { performBackup } = require('@dambrogia/openclaw-agents-backup');
|
|
87
|
+
|
|
88
|
+
// Hourly backup job
|
|
89
|
+
cron.add({
|
|
90
|
+
name: 'agents-backup-hourly',
|
|
91
|
+
schedule: { kind: 'cron', expr: '0 * * * *' },
|
|
92
|
+
payload: {
|
|
93
|
+
kind: 'agentTurn',
|
|
94
|
+
message: 'Run backup',
|
|
95
|
+
timeoutSeconds: 300
|
|
96
|
+
},
|
|
97
|
+
sessionTarget: 'isolated'
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or use OpenClaw's built-in scheduling to call the backup function.
|
|
102
|
+
|
|
103
|
+
## CLI
|
|
104
|
+
|
|
105
|
+
### Commands
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
backup-agents backup # Back up all agents now
|
|
109
|
+
backup-agents restore # Restore to latest
|
|
110
|
+
backup-agents restore --sha SHA # Restore to specific commit
|
|
111
|
+
backup-agents history # Show recent backups
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## API (Library Reference)
|
|
115
|
+
|
|
116
|
+
For developers using the library directly (not required for typical CLI usage).
|
|
117
|
+
|
|
118
|
+
### Backup
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { performBackup } from '@dambrogia/openclaw-agents-backup';
|
|
122
|
+
|
|
123
|
+
const result = await performBackup('/root/.openclaw/workspace');
|
|
124
|
+
|
|
125
|
+
// Result
|
|
126
|
+
{
|
|
127
|
+
success: boolean;
|
|
128
|
+
message: string;
|
|
129
|
+
agentsProcessed: number;
|
|
130
|
+
changes: Array<{
|
|
131
|
+
agentId: string;
|
|
132
|
+
workspaceChanged: boolean;
|
|
133
|
+
agentDirChanged: boolean;
|
|
134
|
+
error?: string;
|
|
135
|
+
}>;
|
|
136
|
+
error?: string;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**What it does:**
|
|
141
|
+
1. Queries OpenClaw for all agent bindings
|
|
142
|
+
2. For each agent, creates/updates `archives/<agent-id>/`
|
|
143
|
+
3. Syncs workspace and agent directories using `rsync --archive --delete`
|
|
144
|
+
4. Stores agent metadata in `archives/<agent-id>/agent.json`
|
|
145
|
+
5. Commits changes to Git with a timestamp
|
|
146
|
+
|
|
147
|
+
### Restore
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { performRestore } from '@dambrogia/openclaw-agents-backup';
|
|
151
|
+
|
|
152
|
+
// Restore latest backup
|
|
153
|
+
const result = await performRestore(
|
|
154
|
+
'/path/to/backup-repo',
|
|
155
|
+
null, // or pass a git SHA for point-in-time restore
|
|
156
|
+
'/root/.openclaw/workspace'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Result
|
|
160
|
+
{
|
|
161
|
+
success: boolean;
|
|
162
|
+
message: string;
|
|
163
|
+
agentsRestored: number;
|
|
164
|
+
error?: string;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**What it does:**
|
|
169
|
+
1. Reads all agent archives from `archives/`
|
|
170
|
+
2. For each agent, restores workspace and agent directory to original locations
|
|
171
|
+
3. Uses rsync to sync directories with `--archive --delete`
|
|
172
|
+
|
|
173
|
+
## Backup Structure
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
agents-backup/
|
|
177
|
+
├── .git/ # Git history
|
|
178
|
+
├── archives/
|
|
179
|
+
│ ├── main/
|
|
180
|
+
│ │ ├── agent.json # Agent metadata (id, paths, timestamp)
|
|
181
|
+
│ │ ├── workspace/ # Synced from <agent.workspace>
|
|
182
|
+
│ │ │ ├── SOUL.md
|
|
183
|
+
│ │ │ ├── USER.md
|
|
184
|
+
│ │ │ ├── MEMORY.md
|
|
185
|
+
│ │ │ ├── memory/
|
|
186
|
+
│ │ │ └── ...
|
|
187
|
+
│ │ └── agentDir/ # Synced from <agent.agentDir>
|
|
188
|
+
│ │ ├── sessions/
|
|
189
|
+
│ │ └── ...
|
|
190
|
+
│ ├── secondary/
|
|
191
|
+
│ │ ├── agent.json
|
|
192
|
+
│ │ ├── workspace/
|
|
193
|
+
│ │ └── agentDir/
|
|
194
|
+
│ └── ...
|
|
195
|
+
└── .gitignore # Ignores .env*, auth-profiles.json, etc.
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Ignored Files
|
|
199
|
+
|
|
200
|
+
The following patterns are automatically ignored by Git:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
# Credentials and secrets
|
|
204
|
+
.env
|
|
205
|
+
.env.local
|
|
206
|
+
.env.*.local
|
|
207
|
+
auth-profiles.json
|
|
208
|
+
auth-profiles.*
|
|
209
|
+
|
|
210
|
+
# Dependencies
|
|
211
|
+
node_modules/
|
|
212
|
+
package-lock.json
|
|
213
|
+
yarn.lock
|
|
214
|
+
|
|
215
|
+
# Build artifacts
|
|
216
|
+
dist/
|
|
217
|
+
*.js
|
|
218
|
+
*.js.map
|
|
219
|
+
*.d.ts
|
|
220
|
+
|
|
221
|
+
# IDE
|
|
222
|
+
.vscode/
|
|
223
|
+
.idea/
|
|
224
|
+
.DS_Store
|
|
225
|
+
|
|
226
|
+
# Logs
|
|
227
|
+
*.log
|
|
228
|
+
logs/
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Why?** Sensitive credentials (GitHub PAT, API keys) should never be committed. Store these in `.env` or `.env.local` files, which are backed up but not committed to Git.
|
|
232
|
+
|
|
233
|
+
## Disaster Recovery Workflow
|
|
234
|
+
|
|
235
|
+
### Scenario: Restore agents after VPS wipe
|
|
236
|
+
|
|
237
|
+
**Step 1: Set up new VPS**
|
|
238
|
+
```bash
|
|
239
|
+
# Install OpenClaw, configure initial workspace
|
|
240
|
+
openclaw init
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Step 2: Clone backup repository**
|
|
244
|
+
```bash
|
|
245
|
+
git clone <backup-repo-url> /path/to/agents-backup
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Step 3: Create backup config**
|
|
249
|
+
```bash
|
|
250
|
+
# In /root/.openclaw/workspace/.backupconfig.json
|
|
251
|
+
echo '{"backupRepoPath": "/path/to/agents-backup"}' > /root/.openclaw/workspace/.backupconfig.json
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Step 4: Restore from backup**
|
|
255
|
+
```bash
|
|
256
|
+
# Run the restore function
|
|
257
|
+
node -e "require('@dambrogia/openclaw-agents-backup').performRestore('/path/to/agents-backup', null, '/root/.openclaw/workspace').then(console.log)"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Step 5: Verify agents**
|
|
261
|
+
```bash
|
|
262
|
+
openclaw agents list --bindings --json
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Restore to a specific point in time
|
|
266
|
+
|
|
267
|
+
Find the commit you want to restore from:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
cd /path/to/agents-backup
|
|
271
|
+
git log --oneline
|
|
272
|
+
# Pick a commit SHA
|
|
273
|
+
git checkout <commit-sha>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Then run restore as normal.
|
|
277
|
+
|
|
278
|
+
## Implementation Details
|
|
279
|
+
|
|
280
|
+
### Agent Discovery
|
|
281
|
+
|
|
282
|
+
Agents are discovered via `openclaw agents list --bindings --json`, which returns:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
[
|
|
286
|
+
{
|
|
287
|
+
"id": "main",
|
|
288
|
+
"identityName": "dambrogia-ai-dev",
|
|
289
|
+
"identityEmoji": "⚙️",
|
|
290
|
+
"workspace": "/root/.openclaw/workspace",
|
|
291
|
+
"agentDir": "/root/.openclaw/agents/main/agent",
|
|
292
|
+
"model": "anthropic/claude-haiku-4-5",
|
|
293
|
+
"bindings": 0,
|
|
294
|
+
"isDefault": true,
|
|
295
|
+
"routes": ["default (no explicit rules)"]
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Rsync Behavior
|
|
301
|
+
|
|
302
|
+
- **`--archive`** — Preserves permissions, ownership, timestamps, symlinks
|
|
303
|
+
- **`--delete`** — Removes files in destination that no longer exist in source
|
|
304
|
+
- **Dry-run check** — Detects changes before syncing to avoid unnecessary Git commits
|
|
305
|
+
|
|
306
|
+
### Git Commit Strategy
|
|
307
|
+
|
|
308
|
+
One commit per backup run with message format:
|
|
309
|
+
```
|
|
310
|
+
Backup: 2026-02-17T05:30:45.123Z
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
This allows easy time-based filtering and point-in-time recovery.
|
|
314
|
+
|
|
315
|
+
## Testing
|
|
316
|
+
|
|
317
|
+
Run the test suite:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npm test
|
|
321
|
+
npm run test:coverage
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Target: >80% code coverage
|
|
325
|
+
|
|
326
|
+
## Limitations
|
|
327
|
+
|
|
328
|
+
- **Point-in-time restore via Git SHA** — Currently documented but not fully automated. You can manually `git checkout <sha>` the backup repo before restoring.
|
|
329
|
+
- **Incremental backups** — All files are synced hourly, but Git only commits changed files
|
|
330
|
+
- **Size** — Disk usage doubles since archives contain full copies of workspace + agentDir
|
|
331
|
+
|
|
332
|
+
## Troubleshooting
|
|
333
|
+
|
|
334
|
+
### "Backup repo not initialized"
|
|
335
|
+
Ensure the backup repository exists and has been initialized with `git init`.
|
|
336
|
+
|
|
337
|
+
### "No changes detected"
|
|
338
|
+
Rsync ran but found no differences. This is normal — the next hourly run will check again.
|
|
339
|
+
|
|
340
|
+
### "Git commit failed"
|
|
341
|
+
The backup process completed but couldn't commit. Check:
|
|
342
|
+
- Git is configured in the backup repo (`git config user.email` / `user.name`)
|
|
343
|
+
- Remote is set up if you plan to push
|
|
344
|
+
- Disk space is available
|
|
345
|
+
|
|
346
|
+
### Restore shows "Archives directory not found"
|
|
347
|
+
Run backup at least once to create the `archives/` directory structure.
|
|
348
|
+
|
|
349
|
+
## Contributing
|
|
350
|
+
|
|
351
|
+
Issues and PRs welcome. Please include test cases for any new functionality.
|
|
352
|
+
|
|
353
|
+
## License
|
|
354
|
+
|
|
355
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const index_1 = require("./index");
|
|
40
|
+
/**
|
|
41
|
+
* CLI entry point for backup/restore operations
|
|
42
|
+
*/
|
|
43
|
+
async function main() {
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
const command = args[0];
|
|
46
|
+
const workspace = process.env.OPENCLAW_WORKSPACE || '/root/.openclaw/workspace';
|
|
47
|
+
const configPath = path.join(workspace, '.backupconfig.json');
|
|
48
|
+
if (!fs.existsSync(configPath)) {
|
|
49
|
+
console.error('❌ Error: .backupconfig.json not found at ' + configPath);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
53
|
+
const backupRepoPath = config.backupRepoPath;
|
|
54
|
+
try {
|
|
55
|
+
switch (command) {
|
|
56
|
+
case 'backup':
|
|
57
|
+
await handleBackup(workspace);
|
|
58
|
+
break;
|
|
59
|
+
case 'restore':
|
|
60
|
+
await handleRestore(backupRepoPath, workspace, args);
|
|
61
|
+
break;
|
|
62
|
+
case 'history':
|
|
63
|
+
await handleHistory(backupRepoPath);
|
|
64
|
+
break;
|
|
65
|
+
case '--help':
|
|
66
|
+
case '-h':
|
|
67
|
+
case undefined:
|
|
68
|
+
showHelp();
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
console.error('❌ Unknown command: ' + command);
|
|
72
|
+
showHelp();
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error(`❌ Error: ${error}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function handleBackup(workspace) {
|
|
82
|
+
console.log('🔄 Starting backup...');
|
|
83
|
+
const result = await (0, index_1.performBackup)(workspace);
|
|
84
|
+
if (result.success) {
|
|
85
|
+
console.log('✅ Backup complete');
|
|
86
|
+
console.log(` Agents processed: ${result.agentsProcessed}`);
|
|
87
|
+
console.log(` Changes: ${result.changes.filter((c) => c.workspaceChanged || c.agentDirChanged).length}`);
|
|
88
|
+
result.changes.forEach((change) => {
|
|
89
|
+
if (change.workspaceChanged || change.agentDirChanged) {
|
|
90
|
+
console.log(` - ${change.agentId}: workspace=${change.workspaceChanged}, agentDir=${change.agentDirChanged}`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.error(`❌ Backup failed: ${result.message}`);
|
|
96
|
+
if (result.error) {
|
|
97
|
+
console.error(` ${result.error}`);
|
|
98
|
+
}
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function handleRestore(backupRepoPath, workspace, args) {
|
|
103
|
+
let targetSha = null;
|
|
104
|
+
let confirmAuthOverwrite = false;
|
|
105
|
+
// Check for --sha argument
|
|
106
|
+
const shaIndex = args.indexOf('--sha');
|
|
107
|
+
if (shaIndex !== -1 && shaIndex + 1 < args.length) {
|
|
108
|
+
targetSha = args[shaIndex + 1];
|
|
109
|
+
console.log(`🔄 Restoring to commit: ${targetSha}`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log('🔄 Restoring to latest backup...');
|
|
113
|
+
}
|
|
114
|
+
// Check for --confirm-auth-overwrite flag
|
|
115
|
+
if (args.includes('--confirm-auth-overwrite')) {
|
|
116
|
+
confirmAuthOverwrite = true;
|
|
117
|
+
}
|
|
118
|
+
const result = await (0, index_1.performRestore)(backupRepoPath, targetSha, workspace, confirmAuthOverwrite);
|
|
119
|
+
if (result.success) {
|
|
120
|
+
console.log('✅ Restore complete');
|
|
121
|
+
console.log(` Agents restored: ${result.agentsRestored}`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
if (result.authOverwriteWarning) {
|
|
125
|
+
console.warn(`⚠️ ${result.message}`);
|
|
126
|
+
console.warn(`\n To restore including sensitive credentials, run:\n backup-agents restore ${targetSha ? `--sha ${targetSha}` : ''} --confirm-auth-overwrite`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.error(`❌ Restore failed: ${result.message}`);
|
|
130
|
+
if (result.error) {
|
|
131
|
+
console.error(` ${result.error}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function handleHistory(backupRepoPath) {
|
|
138
|
+
const { executeCommand } = require('./utils');
|
|
139
|
+
console.log('📜 Backup history:\n');
|
|
140
|
+
try {
|
|
141
|
+
const log = executeCommand(`cd ${backupRepoPath} && git log --oneline -20`);
|
|
142
|
+
console.log(log);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error(`❌ Failed to fetch history: ${error}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function showHelp() {
|
|
150
|
+
console.log(`
|
|
151
|
+
@dambrogia/openclaw-agents-backup CLI
|
|
152
|
+
|
|
153
|
+
Usage:
|
|
154
|
+
backup-agents [command] [options]
|
|
155
|
+
|
|
156
|
+
Commands:
|
|
157
|
+
backup Backup all agents now
|
|
158
|
+
restore [options] Restore agents
|
|
159
|
+
history Show recent backup history
|
|
160
|
+
--help, -h Show this help message
|
|
161
|
+
|
|
162
|
+
Restore Options:
|
|
163
|
+
--sha SHA Restore to specific commit
|
|
164
|
+
--confirm-auth-overwrite Allow overwriting auth-profiles.json (API tokens)
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
# Backup now
|
|
168
|
+
backup-agents backup
|
|
169
|
+
|
|
170
|
+
# Restore latest
|
|
171
|
+
backup-agents restore
|
|
172
|
+
|
|
173
|
+
# Restore to specific point in time
|
|
174
|
+
backup-agents restore --sha abc123def456
|
|
175
|
+
|
|
176
|
+
# Restore including sensitive credentials
|
|
177
|
+
backup-agents restore --confirm-auth-overwrite
|
|
178
|
+
|
|
179
|
+
# View backup history
|
|
180
|
+
backup-agents history
|
|
181
|
+
|
|
182
|
+
Environment:
|
|
183
|
+
OPENCLAW_WORKSPACE Path to OpenClaw workspace (default: /root/.openclaw/workspace)
|
|
184
|
+
BACKUP_ENCRYPTION_PASSWORD Password for encrypting/decrypting files (required)
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
main().catch((error) => {
|
|
188
|
+
console.error(`❌ Unexpected error: ${error}`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
});
|
|
191
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./backup"), exports);
|
|
19
|
+
__exportStar(require("./restore"), exports);
|
|
20
|
+
__exportStar(require("./agentLister"), exports);
|
|
21
|
+
__exportStar(require("./utils"), exports);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dambrogia/openclaw-agents-backup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Backup and restore OpenClaw agent workspaces across multi-agent setups",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"backup-agents": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"jest": {
|
|
11
|
+
"preset": "ts-jest",
|
|
12
|
+
"testEnvironment": "node",
|
|
13
|
+
"roots": [
|
|
14
|
+
"<rootDir>/tests",
|
|
15
|
+
"<rootDir>/src"
|
|
16
|
+
],
|
|
17
|
+
"testMatch": [
|
|
18
|
+
"**/__tests__/**/*.ts",
|
|
19
|
+
"**/?(*.)+(spec|test).ts"
|
|
20
|
+
],
|
|
21
|
+
"collectCoverageFrom": [
|
|
22
|
+
"src/**/*.ts",
|
|
23
|
+
"!src/**/*.d.ts",
|
|
24
|
+
"!src/**/index.ts"
|
|
25
|
+
],
|
|
26
|
+
"coverageThreshold": {
|
|
27
|
+
"global": {
|
|
28
|
+
"branches": 0,
|
|
29
|
+
"functions": 0,
|
|
30
|
+
"lines": 0,
|
|
31
|
+
"statements": 0
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc",
|
|
37
|
+
"lint": "eslint src tests --ext .ts",
|
|
38
|
+
"test": "jest",
|
|
39
|
+
"test:coverage": "jest --coverage",
|
|
40
|
+
"clean": "rm -rf dist coverage",
|
|
41
|
+
"prepare": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"openclaw",
|
|
45
|
+
"backup",
|
|
46
|
+
"restore",
|
|
47
|
+
"agents",
|
|
48
|
+
"disaster-recovery"
|
|
49
|
+
],
|
|
50
|
+
"author": "dambrogia-ai",
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/jest": "^29.5.0",
|
|
54
|
+
"@types/node": "^20.0.0",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
56
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
57
|
+
"eslint": "^8.50.0",
|
|
58
|
+
"jest": "^29.5.0",
|
|
59
|
+
"ts-jest": "^29.1.0",
|
|
60
|
+
"typescript": "^5.2.0"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {}
|
|
63
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AgentBinding } from './types';
|
|
2
|
+
import { executeCommand } from './utils';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Query OpenClaw for all agent bindings
|
|
6
|
+
*/
|
|
7
|
+
export async function listAgents(): Promise<AgentBinding[]> {
|
|
8
|
+
try {
|
|
9
|
+
const output = executeCommand('openclaw agents list --bindings --json');
|
|
10
|
+
const agents = JSON.parse(output) as AgentBinding[];
|
|
11
|
+
return agents;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`Failed to list agents: ${error}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validate that agent bindings have all required fields
|
|
19
|
+
*/
|
|
20
|
+
export function validateAgentBinding(agent: AgentBinding): boolean {
|
|
21
|
+
const required = ['id', 'workspace', 'agentDir'];
|
|
22
|
+
return required.every((field) => field in agent && agent[field as keyof AgentBinding]);
|
|
23
|
+
}
|