Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shelly_backup-0.2.0.tar.gz (26.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shelly_backup-0.2.0-py3-none-any.whl (19.4 kB view details)

Uploaded Python 3

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

Hashes for shelly_backup-0.2.0.tar.gz
Algorithm Hash digest
SHA256 16f7d856bbb43f0e548da9ba8f6ae9fb4f8f5ec783e9fe93d0c418c3ab6889d4
MD5 b132f8689a89e8132da2a0c1faf57c63
BLAKE2b-256 cb9e5adf86392b677000a56f240f90b58091fd8eeba08323495e8883912fb970

See more details on using hashes here.

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

Hashes for shelly_backup-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f34ef818ba2d607ad61ca01a1ec6de99a7aeda8034a9375cf3ba8f41d4810966
MD5 92df9948c9a379a454fbea91334b006a
BLAKE2b-256 57c98b9a0d03b328db24526ad4ce69208d41f42c2798e6ff8ddedae61c2d6857

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page