Skip to main content

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

Metadata

Release files for shelly-backup 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for shelly-backup 0.2.0
File Size Uploaded
shelly_backup-0.2.0.tar.gz 26.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for shelly-backup 0.2.0
File Interpreter ABI Platform
shelly_backup-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 46.2 kB

Release files / shelly_backup-0.2.0.tar.gz

Download URL shelly_backup-0.2.0.tar.gz
Size 26.8 kB
Tags Source
SHA-256 checksum
How to use checksums
16f7d856bbb43f0e548da9ba8f6ae9fb4f8f5ec783e9fe93d0c418c3ab6889d4
BLAKE2b-256 checksum
How to use checksums
cb9e5adf86392b677000a56f240f90b58091fd8eeba08323495e8883912fb970
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / shelly_backup-0.2.0-py3-none-any.whl

Download URL shelly_backup-0.2.0-py3-none-any.whl
Size 19.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f34ef818ba2d607ad61ca01a1ec6de99a7aeda8034a9375cf3ba8f41d4810966
BLAKE2b-256 checksum
How to use checksums
57c98b9a0d03b328db24526ad4ce69208d41f42c2798e6ff8ddedae61c2d6857
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page