signalk-backup
v0.9.3
Published
Scheduled deduplicated backups of SignalK config to local storage and Google Drive (via Kopia + rclone)
Maintainers
Readme
signalk-backup
Scheduled deduplicated backups of your SignalK config (and optionally history) to local storage, USB / NAS, or Google Drive — with a tree-browser that lets you restore individual files from any snapshot, anywhere your user can write.
The plugin runs a React webapp inside the SignalK Admin UI and orchestrates a separate backup-engine container (signalk-backup-server). All Kopia snapshot work happens in the container; the plugin handles the UI, the database-export tick, and host-side file writes for restores that target paths the container can't see.
Features
- Content-addressable deduplication — Kopia stores file blobs once across all snapshots; identical config files between hourly/daily/weekly retention tiers cost nothing
- Scheduled backups — hourly / daily / weekly / on-startup tiers, each with independent retention
- Cloud + local destinations — Google Drive (rclone,
drive.filescope, opt-in OAuth), USB / NAS via local-fs, or SMB share. Snapshots stay in the local Kopia repo even when cloud sync is configured - Selective restore — browse any backup's file tree, download a single file or whole subdirectory, or restore in-place. The trailing slash on a custom path means "land inside as a directory", so
tmp/plus a file source writestmp/<filename>instead of overwritingtmpas a file - Restore-anywhere — custom-path restores stream through the plugin and write under the SignalK user's permissions, so you can land restored bytes in
/tmp,/media/usb/..., or your home directory — anywhere your user can write - Restore with safety backup — every full restore creates a snapshot of the current state first; partial restores stash the existing target as a sibling before overwriting. Both flows can be rolled back automatically on failure
- Database export pipeline — opt-in exporters for signalk-questdb, signalk-grafana, and signalk-database periodically write consistent snapshots (parquet, SQLite checkpoint, provisioning YAML) to a staging dir that Kopia includes in every snapshot. Live DB files are excluded from filesystem backup to avoid torn-write hazards. The dedicated "Database exports" tab also extracts the export subtree from any historical backup without pulling the whole snapshot — a direct answer to "QuestDB shards bloat the full zip"
- First-run safe default — installs with a daily local-only schedule already seeded; no surprise data egress
- Update detection — checks ghcr.io for new container images via signalk-container's centralized update service
- SignalK delta publishing — each scheduled run emits
server.backup.*metric paths andnotifications.server.backup.*alarms on the SignalK delta stream, so KIP / Freeboard / dashboards see backup health natively (see SignalK paths published below)
Requirements
- SignalK Server ≥ 2.24.0
- Node 22 or newer (24 recommended)
- signalk-container ≥ 1.6.0 (provides the container runtime)
- Podman or Docker on the host
Install
# Via the SignalK Admin UI: Server → Appstore → "signalk-backup"
# (auto-enables on install — first-run schedules a daily local backup)The plugin pulls ghcr.io/dirkwa/signalk-backup-server at a server version it pins itself (see src/config/image-tag.ts). Plugin releases and server releases are decoupled — you can pin or float either independently via the plugin's imageTag setting.
How it works
Browser
└── /signalk-backup/ ← plugin webapp (React)
└── /plugins/signalk-backup/api/* ← plugin's reverse proxy
├── /api/db-export/staging ← plugin-local (file lister)
├── /api/restore-partial-host ← plugin-local (writes to host fs)
└── /api/backups/*, /api/cloud/*, … ← proxied to backup-server container
└── http://127.0.0.1:<port>/api/*
(Kopia snapshots, restore-with-rollback,
partial-restore state machine, cloud sync)The plugin is most of the user experience:
- The webapp at
/signalk-backup/is a React SPA bundled by Vite. It runs in the SignalK admin origin and reads/writes viafetch('/plugins/signalk-backup/api/*'). No CORS dance, no separate auth — it inherits SignalK's. - The plugin process owns three things directly: the database-export scheduler tick, an HTTP route that lists / streams files from the staging tree (so the webapp can show "live" exports), and an HTTP route that performs host-side file writes for custom-path restores.
- Everything else — Kopia snapshots, restore-with-rollback, cloud sync, scheduling — runs in the signalk-backup-server container. The plugin reverse-proxies
/api/*through to it, and adds a small set of explicit overrides above the catch-all proxy.
The webapp has four tabs:
- Dashboard — status, recent backups, scheduler next-run times
- Backups — create, list, download, restore (full or selective), upload a ZIP, delete
- Database exports — live staging table (one row per exporter file currently on disk) plus a "pick a backup → browse the database-exports subtree" extractor for grabbing historical shards without pulling the whole snapshot
- Cloud sync — provider chooser (Google Drive / local / SMB), credentials, sync mode, on-demand sync
Configuration
Most settings live in the webapp under Settings. The plugin's SignalK Admin UI config panel is intentionally small and handles only deployment-level options (the schema in src/config/schema.ts is the source of truth — update this list if you bump it):
managedContainer— let the plugin manage the backup-server container (default), or point at an external instanceimageTag— pin a specific version or useauto(default — resolves to the BACKUP_SERVER_VERSION constant)externalUrl— only used whenmanagedContainer: falseemitSignalKDeltas— publish backup health to the SignalK delta stream (default on; disable if you don't want these paths in your delta feed)databaseExport.{questdb, grafana, signalkDatabase}— enable per-exporter (default off; users opt in once their DB plugin is producing data)databaseExport.intervalMinutes— how often the export tick runs (5–1440)
Backup password
Your backups are always encrypted. Out of the box the engine uses a built-in default password, so backups work immediately with no setup. You can set your own password from the webapp under Settings at any time.
Changing the password re-keys your existing repository in place — your existing backups are preserved, not lost. The change is rolled back automatically if anything goes wrong, so the previous password keeps working and you can never be locked out of your own backups.
If you use cloud sync (Google Drive or SMB), the cloud copy shares the same password. After you change it, the plugin starts one sync to bring the cloud copy up to date — you don't need to re-upload everything. Until that sync finishes, don't restore from the cloud or delete your old cloud backups (they're your safety net if the sync is interrupted).
If you ever see a found existing data in storage location error, your backups are safe — the engine just couldn't open the repository (usually a password mismatch). See Repository recovery in the backup-server docs.
SignalK paths published
When emitSignalKDeltas is on (default), the plugin publishes one delta per scheduled backup run (manual backups stay out of the delta stream by design). All paths sit under vessels.<selfId>:
| Path | Type | Units | Meaning |
|---|---|---|---|
| server.backup.lastRunTimestamp | RFC 3339 | — | When the last scheduled run started |
| server.backup.lastStatus | string | — | success or failure (failure if either local Kopia or cloud sync failed) |
| server.backup.lastRunTier | string | — | hourly / daily / weekly / startup |
| server.backup.lastRunBytes | number | B | Snapshot size on disk |
| server.backup.nextScheduledTimestamp | RFC 3339 | — | When the next scheduled run is due (earliest of all enabled tiers) |
Notification paths (alarm shape: state / method / message / timestamp):
| Path | State | Cleared when |
|---|---|---|
| notifications.server.backup.failed | alert (message names the failing destination) | next scheduled run succeeds |
| notifications.server.backup.overdue | warn (last success > 2× tier interval) | a scheduled run for that tier succeeds |
| notifications.server.backup.storageLow | warn < 10% free, alert < 5% | free space ≥ 12% (hysteresis) |
meta (units, displayName, description) is emitted once on the first event so the data browser renders column labels correctly. To get human-readable byte values (MB / GiB / etc.) in the data browser, your SignalK server needs unit-preferences support for B — proposed upstream in SignalK/signalk-server#2696. Until that lands, the raw byte count is still emitted; clients can convert client-side.
License
Apache-2.0
See also
- signalk-backup-server — the container image
- signalk-container — runtime substrate
- Kopia — backup engine
- rclone — cloud transport
