-
Notifications
You must be signed in to change notification settings - Fork 0
FAQ
Distilled from CHANGELOG entries, ADRs, and real operator questions. If yours isn't here, file it as an issue using the Feature request template (label it docs or question) and the answer can land here.
Since v0.3.0, SHELLYADMIN_ENCRYPTION_KEY (or SHELLYADMIN_ENCRYPTION_KEY_FILE) is mandatory. The binary refuses to boot without it, because storing the encryption key next to the encrypted credentials it protects defeats the at-rest encryption (a volume snapshot would carry both halves).
Generate one once and reuse it for the lifetime of the deployment:
openssl rand -base64 32Store it in your secrets backend (Docker secret, sops file, etc.), expose via SHELLYADMIN_ENCRYPTION_KEY_FILE for production. A new key orphans all previously stored device credentials, so a rotation is a database wipe — plan it as one.
See ADR-0013.
Since v0.4.0 (ADR-0017) the operator login lives in the database, not in an env var. A fresh instance with no admin_credentials row renders /setup and waits for you to create the account in the browser.
If you'd rather pre-seed (for unattended provisioning, CI, etc.), set SHELLYADMIN_PASS_HASH on the first boot — the hash is imported into the DB once, then ignored. Generate it with docker run --rm ghcr.io/buliwyf42/shellyadmin:latest hash-password '<plaintext>'.
To rotate the password later, use Settings → Operator Account in the UI. To recover from a forgotten password, docker exec shellyadmin shellyctl reset-auth --force clears the row and drops back into setup mode.
Two things:
-
SHELLYADMIN_PASS(plaintext) was removed inv0.2.0. OnlySHELLYADMIN_PASS_HASH(argon2id PHC) works. -
v0.4.0moved the credential into the database (see above). Your existingSHELLYADMIN_PASS_HASHwill be imported into the DB on the firstv0.4.0+boot, then ignored. After that the DB is authoritative; you can remove the env var.
Functionally nothing — both produce the same single container. The Compose file in docker/docker-compose.yml is the recommended production shape because it carries the hardening flags (read_only: true, cap_drop: [ALL], cap_add: [CHOWN, DAC_OVERRIDE, SETGID, SETUID, KILL], no-new-privileges: true, pids_limit: 256, init: true) that a plain docker run example omits.
Gen1 devices speak HTTP-GET CoAP-style; everything Gen2+ speaks JSON-RPC 2.0 over POST /rpc. The bulk-action / provisioning / firmware-check architecture is built against the JSON-RPC surface, and the Gen1 protocol would need a parallel adapter for every feature. The project explicitly targets Gen2+ to keep the surface coherent.
Shelly devices use a non-standard JSON-RPC error code (404 instead of the standard -32601) when a method isn't supported on a specific model. ShellyAdmin handles both. If you see "Method Not Found" on something common like Shelly.Update, the device's firmware is probably older than 1.0.0 — bring it forward in the device's own web UI first.
The Schedule-based auto-update mechanism (ADR-0009) needs firmware 1.2.0 or later on the device. Older firmware doesn't expose the Schedule.Create job shape we use. Update the device firmware first, then the toggle appears.
Two paths. Pick one:
-
Env var (precedence, recommended for headless deploys): set
SHELLYADMIN_MCP_TOKEN=<random-string>in the container env. The HTTP listener binds on:8081automatically. - Settings UI: navigate to Settings → MCP Server → enable, paste a token. Encrypted at rest. The env var wins if both are set.
Clients can authenticate with either Authorization: Bearer <token> or the URL-path form http://host:8081/<token>/ — convenient for mcp-remote which doesn't natively expose a headers field.
- HTTP (default): remote clients over the network. Auth via the token. Use this for Claude Desktop on a different machine, Home Assistant integrations, scripts that hit the API.
-
stdio: only for Claude Desktop running on the same host as the container, via
shellyctl mcpsubcommand. No transport-level auth — the spawning process IS the trust boundary. Practical if you want the lightest possible local setup.
By design (ADR-0011, ADR-0014). State-changing tools (refresh, scan, firmware install, bulk actions) exist but are confirm-gated: every state-changing call returns a typed preview when called without confirm: true. The LLM is expected to summarise the preview to the operator and only re-call with confirm: true after explicit approval.
Cookie sessions are for browser users (SPA), bound by CSRF. Personal Access Tokens (PATs, since v0.3.0) are for headless callers (Home Assistant, cron, scripts) — bearer-token authentication that skips CSRF because the token itself is the proof-of-intent.
PATs have scopes (admin, devices:read/write, firmware:read/write, provision, settings:read/write) and individual revoke. Minting / listing / revoking PATs requires a cookie session — a PAT-authed caller cannot escalate by minting more PATs.
Settings → Two-Factor Authentication → Enroll. Scan the QR code with any RFC 6238 authenticator (Authy, 1Password, Bitwarden, the OS Keychain, etc.). Ten single-use backup codes are issued at enrollment — download and store them somewhere out-of-band. Wrong codes increment the same lockout counter as wrong passwords.
The token only substitutes against the configured device name (Shelly.GetConfig → sys.device.name), not the auto-generated shelly-plus1-XXXXXX hostname. If the device hasn't been named, the token has nothing to substitute and stays literal. Set a name on the device first.
Two common reasons:
-
Empty vs missing.
ssl_ca: ""andssl_caabsent are equivalent on the device, but a rule withssl_ca: ""won't match a device where the field is absent. Useop: newith an empty value, orop: exists: false. -
ssl_caaccepts only four literal values:"","*","ca.pem","user_ca.pem". Anything else (custom hostname patterns, etc.) is silently rejected by the device.
Direct passthrough to Shelly's JSON-RPC. Each key is a method name (Sys.SetConfig, Mqtt.SetConfig, etc.) and each value is the params object. Use this when a configuration surface isn't yet covered by a dedicated section in the guided form — Zigbee operations, exotic schedule entries, FW-2.0 betas. The provisioner handles standard 404 / -32601 method-not-found responses gracefully.
Wiki
Repo docs
Report