English | Deutsch
ShellyAdmin is a self-hosted web app for discovering, inventorying, checking, and managing Shelly Gen2+ devices on a trusted local network.
The Shelly cloud requires opting each device into a third-party service. Home Assistant's Shelly integration covers control, automation, and a per-entity view but not fleet-wide firmware management, compliance auditing, bulk provisioning, or an audit log of operator actions. ShellyAdmin sits next to those tools as the fleet ops console: scan a subnet, enroll devices into an inventory, push templated config to many at once, check/install firmware in bulk, and verify each device matches a compliance rule set — with every action audited.
It is designed as a single-container deployment with:
- staged device discovery before enrollment
- latest observed device state in SQLite
- manual firmware and provisioning workflows
- compliance checks against configured rules
- guided provisioning for normal use
- advanced provisioning mode for expert use
- audit logging in-app
More screenshots: Scan, Firmware, Provision, Compliance
Under active development. Current release is v0.5.3 (hardening: shellyctl rotate-key, device-response limits + JSON-RPC envelope validation, template-section validation, CI race detector + frontend coverage gate); the UI/API baseline is unchanged since v0.4.0. The project follows pre-1.0 semver: minor versions may carry breaking changes. Semver guarantees apply from v1.0.0.
Intended posture:
- experimental but usable for trusted-LAN administration
- optimized for a single trusted operator
- not intended for direct internet exposure
- not yet positioned as a multi-user or HA-ready platform
The target architecture is documented in docs/ARCHITECTURE.md.
- Easy to run in Docker
- Optimized for a single trusted operator on a LAN
- Supports Gen2+ Shelly devices (Gen1 is intentionally unsupported)
- Keeps risky actions manual and previewed
Fastest Docker run for a trusted LAN test setup:
docker run -d \
--name shellyadmin \
-p 8080:8080 \
-v shellyadmin-data:/data \
-e SHELLYADMIN_SECRET="$(openssl rand -hex 32)" \
-e SHELLYADMIN_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
-e COOKIE_SECURE=false \
ghcr.io/buliwyf42/shellyadmin:latestThen open http://localhost:8080 and create the admin account on the first-run setup screen. Forgot the password later? docker exec shellyadmin shellyctl reset-auth --force returns the instance to setup mode.
SHELLYADMIN_ENCRYPTION_KEY is required since v0.3.0 — the container won't start without it. Generate it once and reuse the same value on every recreate; swapping in a new key without rotation orphans all stored credentials. To rotate to a new key deliberately, use shellyctl rotate-key (v0.5.3) — see docs/SECURITY.md.
COOKIE_SECURE=false is only safe on plain HTTP over a trusted LAN. Set COOKIE_SECURE=true (and front the container with TLS) for any other deployment.
To skip the setup screen on a fresh instance, generate a hash and pass it as SHELLYADMIN_PASS_HASH:
HASH="$(docker run --rm ghcr.io/buliwyf42/shellyadmin:latest hash-password 'change-this-admin-password')"
docker run -d \
--name shellyadmin \
-p 8080:8080 \
-v shellyadmin-data:/data \
-e SHELLYADMIN_SECRET="$(openssl rand -hex 32)" \
-e SHELLYADMIN_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
-e SHELLYADMIN_PASS_HASH="$HASH" \
-e COOKIE_SECURE=false \
ghcr.io/buliwyf42/shellyadmin:latestThe hash is imported into the database once at boot, then ignored. To rotate the password after that, use Settings → Operator Account in the UI.
For a Compose-based deployment, see docker/docker-compose.yml:
docker compose -f docker/docker-compose.yml up -dSee docs/DEPLOYMENT.md for the full deployment guide, including hardening flags, MCP exposure, and pre-deploy DB snapshots.
- Scan with staging and explicit add-to-inventory
- Device inventory table with sortable columns and per-user column visibility
- Bulk actions with preview/apply for timezone, MQTT, location, SNTP, and reboot
- Auto-refresh in Devices view (30s, 1m, 5m)
- Separate scan timeout and refresh timeout in Settings
- Optional mDNS-assisted discovery in addition to subnet scanning
- Per-device row actions in Devices view:
- immediate refresh
- per-row reboot (⏻) with inline spinner
- delete/forget
- Reboot All toolbar button for bulk device reboots
- Per-device detail view with:
- raw config/status snapshots
- discovered capabilities
- safe single-device actions (refresh, firmware check/update, reboot)
- Locale-aware relative/absolute time presentation in both Devices and per-device detail
- Stale row signal when the latest refresh attempt fails
- Compliance status in Devices view with hover details
- Manual firmware check and update flow:
- per-device, per-channel availability cache (stable + beta read in a single check)
- sortable, select-all-aware Firmware page with a confirmation modal before bulk install
- dedicated install job with per-device version-match polling; both the timeout (default 300 s) and the poll cadence (default 5 s, bounded 1–60) are operator-configurable in Settings
- bulk auto-update controls (Off / Stable / Beta) implemented via
Schedule.*, the same mechanism the device's own web UI uses - shared Stable/Beta channel between the Firmware and Devices pages (persisted to localStorage)
- Guided provisioning form plus JSON mode with template management (load, save, delete, rename) in-context:
- full
Wifi.SetConfigsurface: primary STA, secondary STA (STA1), roaming (RSSI threshold, interval), static IPv4 per STA - Script section (per-id loop), UI.SetConfig, Ethernet IPv6/DNS
auto_updatesection (off / stable / beta) — synthesised onto the device as aSchedule.*job- Webhooks form (v0.2.4):
delete_alltoggle, delete-by-id, new-webhook entries (cid/event/name/enable/URLs) - Cover form (v0.2.5): id, name, maxtime open/close, swap_inputs, power_limit, and the FW 2.0.0-beta1
slatsub-object for venetian-blind tilt - Zigbee operations form (v0.2.6): write-mostly cards for
Zigbee.SendCommand/Zigbee.ReadAttr/Zigbee.WriteAttr, generates agen2_rpctemplate section restart_requiredbadge per device in results; "Reboot restart-required devices" button
- full
- Auth Groups page:
- groups contain their own auth credentials (
username,password/ha1, tags) - device-to-group assignment for future auth-required workflows
- groups contain their own auth credentials (
- Provisioning target validation (local/private/link-local IPs only)
- Compliance rule editor (including
{device_name}token matching) - Backup/export/import with dry-run and apply:
- settings
- templates
- auth groups
- device-group assignments
- Audit logs view (debug log mode removed)
- Documented API surface and OpenAPI JSON for the supported v1 routes
- Optional read-only MCP server for LLM-driven introspection
See docs/roadmap.md for the current roadmap. Headline items:
- Broader action discovery for device components where protocol support is reliable
shellyctlwrite commands (read-only CLI shipped in v0.3.6)- API stability guarantee from
v1.0.0
cmd/shellyctl Application entrypoint (server + CLI subcommands)
internal/api HTTP routing and handlers
internal/services Workflow orchestration
internal/core Shelly protocol logic (scanner, firmware, provisioner)
internal/mcp Read-only MCP server (HTTP + stdio transports)
internal/db SQLite persistence and migrations
internal/models Shared data models
web Svelte frontend
docker Container files
docs Architecture, deployment, and ADR documentation
Requirements:
- Go 1.25+ (the
go.modfloor; CI and the Docker build use the Go 1.26 toolchain) - Node 22+ (CI and the Docker build use Node 26)
In two terminals:
# Terminal 1 — backend on :8080
make dev-backend
# Terminal 2 — frontend dev server on :5173 (proxies /api and /health)
cd web
npm install
npm run devOpen http://localhost:5173 and log in with admin / dev-secret.
To run the Go binary with embedded frontend assets instead of the Vite dev server:
make frontend # build + sync the SPA into cmd/shellyctl/dist
make backend
./bin/shellyctlFor the full development reference (tests, lint, bundle-size budget, deployment workflow, release process), see docs/DEVELOPMENT.md.
ShellyAdmin runs as a single container. Tagged releases publish a multi-arch image to GHCR via .github/workflows/publish-image.yml:
ghcr.io/buliwyf42/shellyadmin:vX.Y.Z(immutable)ghcr.io/buliwyf42/shellyadmin:latest(mover)
The reference Compose file at docker/docker-compose.yml pulls :latest by default. To build locally instead of pulling:
docker compose -f docker/docker-compose.yml up -d --buildSee docs/DEPLOYMENT.md for the full deployment guide.
ShellyAdmin can expose a read-only Model Context Protocol server so LLM-driven agents (Claude Desktop, Claude Code, custom MCP clients) can introspect the fleet — list devices, check scan/firmware status, read compliance, inspect logs — without scraping the SPA. State-changing operations (refresh, scan, firmware update, provision, settings writes) are deliberately not exposed in v1; see docs/adr/0011-mcp-read-only-server.md.
The listener is off by default and can be enabled either by setting SHELLYADMIN_MCP_TOKEN (env var; takes precedence — useful for headless / CI / Compose-managed deploys) or by toggling MCP Server → Enable on the Settings page and entering a token there (since v0.1.20; encrypted at rest). When both are set, the env var wins and the Settings UI shows a "managed by environment variable" notice. Clients can authenticate either via the standard Authorization: Bearer <token> header or by putting the token as the first URL path segment (e.g. http://host:8081/<token>/) — convenient for clients like mcp-remote where a header arg is awkward.
| Env var | Default | Purpose |
|---|---|---|
SHELLYADMIN_MCP_TOKEN |
unset | Required to enable MCP. Supports _FILE indirection like other secrets. |
SHELLYADMIN_MCP_PORT |
8081 |
Port for the MCP listener. |
SHELLYADMIN_MCP_BIND |
0.0.0.0 |
Bind address. Set to 127.0.0.1 for loopback-only. |
Example Claude Desktop config (mcp.json) — header form:
{
"mcpServers": {
"shellyadmin": {
"url": "http://your-shellyadmin-host:8081/",
"headers": { "Authorization": "Bearer your-token-here" }
}
}
}Same client routed through mcp-remote (which doesn't natively expose a header field) using the URL-path form:
{
"mcpServers": {
"shellyadmin": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://your-shellyadmin-host:8081/your-token-here",
"--allow-http"
]
}
}
}When MCP is enabled, every tool call writes to the same audit log the SPA shows on the Logs page (prefixed with mcp , filterable by request id).
This project is intended for trusted LAN use, not direct internet exposure. See SECURITY.md for the reporting flow and supported-versions policy; docs/SECURITY.md carries the deeper threat model and deployment expectations.
Found a vulnerability? Open a private security advisory.
Documented in:
- docs/ARCHITECTURE.md — overall design
- docs/adr/README.md — architecture decision records
The project is still being shaped, so architecture changes should align with the documented design goals before implementation. See CONTRIBUTING.md for the development and PR workflow, and CODE_OF_CONDUCT.md for community expectations.
MIT © 2026 buliwyf42




