Verified local backup & restore for Shelly Gen2+ devices, scripts included
Project description
shelly-backup
Verified local backup & restore for Shelly Gen2+ devices — scripts included.
Backs up the full configuration and the complete source of every script from Shelly Gen2/Gen3/Gen4 devices over the local RPC API, and restores it. No cloud, no account, no agent on the device.
Unofficial project. Not affiliated with, endorsed by, or supported by Allterco Robotics EOOD. "Shelly" is their trademark, used here only to say what these tools talk to. Use at your own risk — see the warranty disclaimer in LICENSE.
Why this exists
Shelly Cloud does not back up locally-stored scripts. So people reach for a local tool — and the ones I looked at get scripts subtly, dangerously wrong.
Script.GetCode is a chunked RPC. It returns a slice of the source plus a
left field saying how many bytes remain. Call it once and read data, and you
get a perfectly well-formed response containing a plausible-looking prefix of
your script. Every script longer than one RPC frame is silently truncated:
real script size: 13489 bytes
naive single GetCode gives: 1024 bytes <-- looks fine, is not
shelly-backup gives: 13489 bytes
The restore path has the mirror bug: pushing the whole body in one
Script.PutCode fails for anything over the frame limit, and if the tool
discards that error, your script restores empty.
A backup you trust that contains half a script is worse than no backup at all. So this tool is built around one guarantee:
Every script is read in full, checksummed, and — on restore — read back off the device and compared before we call it a success. Anything less is an error, loudly, with a non-zero exit code.
Install
Requires Python 3.11+.
uv tool install shelly-backup # recommended -- installs the CLI in its own env
pip install shelly-backup # fallback
From source:
git clone https://github.com/gmasse/shelly-backup
cd shelly-backup
uv sync --extra dev # fallback: pip install -e ".[dev]"
uv run shelly-backup --help
Use
cp devices.example.yaml devices.yaml # add your device IPs
shelly-backup backup # -> backups/<device>/
shelly-backup verify # re-check checksums on disk
shelly-backup restore backups/kitchen --host 192.168.1.50 # dry run
shelly-backup restore backups/kitchen --host 192.168.1.50 --yes # apply
Passwords
If you enabled device auth, -p asks for the password once and uses it for every
device that needs one (the username is always admin):
shelly-backup backup -p # asks once, echoes nothing
pass show shelly | shelly-backup backup -p # reads one line from the pipe
SHELLY_PASSWORD=... shelly-backup backup # for cron
shelly-backup restore backups/kitchen --host 192.168.1.50 -p
Where a password comes from, highest first:
| Source | Applies to |
|---|---|
password: in devices.yaml |
that device only — a per-device override |
-p / --ask-password |
every device without one in the inventory |
SHELLY_PASSWORD |
same, without asking — the non-interactive path |
Without -p, a device that turns out to need a password is asked about only if
you are at a terminal. Under cron there is nobody to ask, so it fails with a
non-zero exit code and a message rather than blocking the backup forever. There
is deliberately no flag that takes a password as a value: that would put it in
your shell history and in ps output for every user on the machine. Passwords
are never echoed, logged, or written to a backup.
What a backup looks like
Git-friendly by design — scripts are real .js files, so git diff shows what
actually changed in your automation:
backups/kitchen/
├── manifest.json device info, per-script SHA-256, timestamp
├── config.json Shelly.GetConfig — every component
├── kvs.json key-value store, if present
└── scripts/
├── 01_solar.js complete source
├── 01_solar.json id, name, enable, running, sha256, bytes
└── 02_boiler.js
Commit backups/ to a private repo and you get versioned config history for
free.
Safety decisions on restore
| Behaviour | Why |
|---|---|
| Dry run by default | --yes required to write anything. |
| Network components skipped | Restoring wifi/eth/cloud/mqtt can lock you out — and since the API redacts passwords, a restored wifi block would carry an empty one. --include-network to override. |
| Scripts matched by name, updated in place | Blindly calling Script.Create duplicates every script on a device that already has them. |
| Refuses on model mismatch | Won't push a Pro 2PM config onto a Mini 1PM. |
| Checksums verified before send, and after write | Catches a corrupt backup and a bad upload. |
| No silent failures | Every error is reported and sets the exit code. |
What is not in a backup
Passwords. The device API redacts the Wi-Fi password and the auth password, so they cannot be captured — or restored. Record them separately.
Supported
Gen2+ devices sharing the JSON-RPC API — Pro 1PM/2PM, Plus series, Mini 1PM G3, Gen4. Gen1 devices use a different API and are out of scope.
Development
uv sync --extra dev
uv run pytest -v
# the lint gate CI enforces
uv run ruff check .
uv run ruff format --check .
uv run mypy
Without uv: pip install -e ".[dev]", then run the same commands directly.
The test suite runs against an in-memory Shelly that chunks exactly like real
firmware. test_naive_single_call_truncates pins the upstream bug explicitly,
so nobody can "simplify" the chunking away later.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shelly_backup-0.2.0.tar.gz.
File metadata
- Download URL: shelly_backup-0.2.0.tar.gz
- Upload date:
- Size: 26.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16f7d856bbb43f0e548da9ba8f6ae9fb4f8f5ec783e9fe93d0c418c3ab6889d4
|
|
| MD5 |
b132f8689a89e8132da2a0c1faf57c63
|
|
| BLAKE2b-256 |
cb9e5adf86392b677000a56f240f90b58091fd8eeba08323495e8883912fb970
|
File details
Details for the file shelly_backup-0.2.0-py3-none-any.whl.
File metadata
- Download URL: shelly_backup-0.2.0-py3-none-any.whl
- Upload date:
- Size: 19.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f34ef818ba2d607ad61ca01a1ec6de99a7aeda8034a9375cf3ba8f41d4810966
|
|
| MD5 |
92df9948c9a379a454fbea91334b006a
|
|
| BLAKE2b-256 |
57c98b9a0d03b328db24526ad4ce69208d41f42c2798e6ff8ddedae61c2d6857
|