Minimalist web app for backing up OpenClaw configuration with automatic secret redaction, running in a hardened Docker container.
- One-click backup via web UI on
http://localhost:3100 - Confirmation prompts before Backup / Push / Download actions
- Automatic secret redaction — JSON fields matching
token,key,password, orsecretare replaced withREDACTED - Git-versioned backups — each backup is a git commit in
./backup-data/, giving you full history and diffs - Hardened container — read-only filesystem, all capabilities dropped, no-new-privileges, non-root user
| Source | Destination | Notes |
|---|---|---|
~/.openclaw/openclaw.json |
backup-data/config/openclaw.json |
Secrets redacted |
~/.openclaw/node.json |
backup-data/config/node.json |
Copied as-is |
~/.openclaw/workspace/*.md |
backup-data/workspace/ |
MEMORY, SOUL, USER, TOOLS, IDENTITY, AGENTS, HEARTBEAT |
~/.openclaw/workspace/memory/*.{md,json} |
backup-data/workspace/memory/ |
Memory notes + state json files |
~/.openclaw/agents/main/agent/auth-profiles.json |
backup-data/config/agent/auth-profiles.json |
Secrets redacted |
~/.openclaw/agents/main/agent/auth.json |
backup-data/config/agent/auth.json |
Secrets redacted |
~/.openclaw/identity/device-auth.json |
backup-data/config/identity/device-auth.json |
Secrets redacted |
~/.openclaw/scripts/**/*.{sh,md} |
backup-data/scripts/ |
Automation scripts + docs |
git clone <repo-url> && cd openclaw-backup-app
docker compose up -d --buildOpen http://localhost:3100 and click Backup Configuration.
This section explains how to do manually what the automation does.
# from repo root
npm version minor --no-git-tag-version # or patch / major
VERSION=$(node -p "require('./package.json').version")
git add package.json package-lock.json
git commit -m "chore: bump version to ${VERSION}"
git tag "v${VERSION}"
GIT_SSH_COMMAND="ssh -i ~/.ssh/openclaw-backup-bot-2026-02-21 -o StrictHostKeyChecking=accept-new" git push origin main --tagsWhat happens next:
release-publish.ymlbuilds and pushes GHCR image (vX.Y.Z+latest)release-test.ymlandrelease-docs.ymlrun automatically for minor/major tags
From GitHub UI:
- Actions → Release Publish (GHCR) → Run workflow
- Actions → Release Test Environment → Run workflow (optionally pass
tag) - Actions → Release Tutorial Docs → Run workflow (optionally pass
tag)
From CLI:
gh workflow run "Release Test Environment" -f tag=v1.8.0
gh workflow run "Release Tutorial Docs" -f tag=v1.8.0
gh run list --limit 10
gh run watch <run-id>Publish success does not replace running containers automatically. Recreate them:
backup-staging-restart # port 3100 (GHCR image)
backup-dev-restart # port 3101 (local dev build)
backup-all-restart # bothDirect compose alternative:
# staging
cd ~/.openclaw/deploy/openclaw-backup/staging
docker compose pull
docker compose up -d
# dev
cd ~/.openclaw/deploy/openclaw-backup/dev
docker compose up -d --buildVerify:
curl http://127.0.0.1:3100/api/status
curl http://127.0.0.1:3101/api/statusThe app now auto-heals this, but manual recovery is:
GIT_SSH_COMMAND="ssh -i ~/.ssh/openclaw-backup-bot-2026-02-21 -o StrictHostKeyChecking=accept-new" git -C ~/.openclaw/data/openclaw-backup fetch origin master
git -C ~/.openclaw/data/openclaw-backup reset --hard origin/masterThen click Backup Configuration and Push to GitHub again.
# generate tutorial markdown
TAG=v1.8.0 KIND=minor TEST_STATUS=pass python3 scripts/release/generate_tutorial.py
# run app locally for screenshot
# (example endpoint: http://127.0.0.1:33110)
npx -y playwright@1.52.0 install chromium
npx -y playwright@1.52.0 screenshot --device="Desktop Chrome" http://127.0.0.1:33110 docs/tutorials/v1.8.0-app.pngsystemctl --user status openclaw-backup-staging.service
systemctl --user status openclaw-backup-dev.service
systemctl --user restart openclaw-backup-staging.service
systemctl --user restart openclaw-backup-dev.service
backup-staging-logs
backup-dev-logsThis repo now uses 3 workflows instead of one:
- Publish image →
.github/workflows/release-publish.yml - Run release tests →
.github/workflows/release-test.yml - Generate tutorial docs →
.github/workflows/release-docs.yml
- Tags
vX.Y.0(minor) andvX.0.0(major): run automatically. - Patch tags
vX.Y.ZwithZ>0: publish image, while test/docs are manual by default. - Manual
workflow_dispatch: always available.
- Builds and pushes image to GHCR:
ghcr.io/alt-f1-openclaw/openclaw-backup-app:vX.Y.Zghcr.io/alt-f1-openclaw/openclaw-backup-app:latest
- Builds a test image
- Starts ephemeral container
- Validates:
- container startup +
/api/status /api/backup- push auto-resync behavior on non-fast-forward
- read-only mount flags for
/configand/workspace
- container startup +
- Generates release tutorial markdown in
docs/tutorials/ - Uploads tutorial/images as workflow artifacts
- Commits tutorial file back to
main - Docs job policy:
- major release: strict (fails pipeline if docs fail)
- minor release: warning mode (continue-on-error)
# after bumping version + commit
VERSION=$(node -p "require('./package.json').version")
git tag "v${VERSION}"
GIT_SSH_COMMAND="ssh -i ~/.ssh/openclaw-backup-bot-2026-02-21 -o StrictHostKeyChecking=accept-new" git push origin main --tagsdocker-compose.yml uses:
image: ghcr.io/alt-f1-openclaw/openclaw-backup-app:latestpull_policy: always
Important: publishing a new image does not replace an already-running container. You must recreate the container to run the new image:
docker compose pull
docker compose up -dQuick verification:
curl http://127.0.0.1:3100/api/status- Image-based (
ghcr.io/...:latest) - Production-like behavior
- Local source build
- Fast testing loop
Versioned helper scripts (recommended):
./ops/backup-dev-restart.sh
./ops/backup-staging-restart.sh
./ops/backup-all-restart.sh(Optional local aliases can call these scripts.)
If Push to GitHub hits a non-fast-forward error ([rejected] fetch first), the app now auto-heals by:
- fetching remote backup-data head,
- resetting local backup repo to remote,
- rebuilding backup files,
- creating a fresh backup commit,
- retrying push.
This avoids manual git recovery during normal app usage.
| Endpoint | Method | Description |
|---|---|---|
/api/status |
GET | Returns app version and last backup status |
/api/backup |
POST | Triggers a backup |
Example response from /api/status:
{
"version": "1.0.0",
"lastBackup": {
"time": "2026-02-21T17:00:00.000Z",
"status": "success",
"message": "Backup created"
}
}- Node.js 22+
- Docker & Docker Compose
npm install
npm startThis project uses Conventional Commits enforced by commitlint and husky.
feat: add new feature
fix: fix a bug
chore: maintenance task
docs: documentation update
refactor: code refactoring
npm run release # auto-bump based on commits (fix→patch, feat→minor)
npm run release:minor # force minor bump
npm run release:major # force major bumpThis bumps package.json version, generates CHANGELOG.md, creates a git commit and tag.
- Runtime: Node.js 22 Alpine
- Server: Express
- Git: simple-git
- Process manager: tini (proper PID 1 signal handling)
- UI: Vanilla HTML/CSS/JS, dark theme
This repository is connected to a Discord channel via GitHub Webhooks for real-time activity notifications.
┌──────────────────────┐ HTTPS POST ┌──────────────────────┐
│ │ (push, PR, issue, release) │ │
│ GitHub │ ──────────────────────────► │ Discord │
│ ALT-F1-OpenClaw/ │ │ #dev-activity │
│ openclaw-backup-app│ Payload: application/json │ (webhook) │
│ │ Endpoint: /github suffix │ │
└──────────────────────┘ └──────────────────────┘
- GitHub fires a webhook on every push, pull request, issue, or release event
- Discord receives the payload at a webhook URL with the
/githubsuffix - Discord formats the event natively (commit messages, PR titles, issue labels, etc.)
| Event | Trigger | What appears in Discord |
|---|---|---|
| Push | Code pushed to any branch | Commit hash, message, author, branch |
| Pull Request | Opened, merged, closed | PR title, author, status, link |
| Issues | Opened, closed, commented | Issue title, labels, assignee |
| Release | New release published | Version, release notes |
To replicate this webhook on a new repo:
- Go to your GitHub repo → Settings → Webhooks → Add webhook
- Payload URL:
<Discord webhook URL>/github(the/githubsuffix is required for Discord formatting) - Content type:
application/json - Secret: leave empty (the webhook URL token acts as authentication)
- Events: "Send me everything" or select specific events
- Click Add webhook
To create the Discord webhook:
- Go to the Discord channel → Edit Channel → Integrations → Webhooks
- Click New Webhook, name it
GitHub, copy the URL
┌─────────────────────────────────────────────────────┐
│ ALT-F1-004 (WSL2) │
│ │
│ OpenClaw Gateway (:18789) │
│ ├── Agent (Claude Opus 4) │
│ ├── Discord Bot ──► #bot-openclaw-setup │
│ │ #bot-general │
│ │ #cron-logs │
│ │ #dev-activity ◄── GitHub WHK │
│ └── Backup App (Docker :3100) ──► this repo │
│ │
│ GitHub (ALT-F1-OpenClaw org) │
│ ├── openclaw-backup-app ──webhook──► #dev-activity │
│ ├── openclaw-backup-data (backup storage) │
│ └── altf1be-hubspot-openclaw ──webhook──► same │
└─────────────────────────────────────────────────────┘
The Docker container is hardened with:
read_only: true— immutable filesystem (except/backupand/tmp)cap_drop: ALL— all Linux capabilities removedno-new-privileges— prevents privilege escalationuser: 1000:1000— runs as non-root, matching host user- Config volumes mounted as read-only (
:ro)



