v1.4.0
The documentation
magicframe.dev/docs is live: 28 pages covering every install path, every widget option, Home Assistant, Immich, calendars, hosting and troubleshooting. The same pages live in this repo under wiki/, and they are checked against the code in CI: a build fails when a page names a widget, route or setting that does not exist, or when a screenshot shows something that no longer exists.
For AI assistants: /llms.txt is the index, /llms-full.txt is the whole manual in one request. Every docs page is also served as plain markdown (same address with .md).
The README is a landing page now, with current screenshots in light and dark.
Home Assistant: a display can now only use what is on your views
/api/ha/action had no login check and passed any service for any entity straight to Home Assistant with your token's full rights. The missing login is deliberate - a wall tablet cannot sign in and its buttons have to work - but it meant anyone who could reach the server could unlock your front door, whether or not a lock appeared on any screen.
A caller without a session now has to pass two checks:
- every entity, device or area in the outgoing request must appear in a saved widget or view config, and
- the service must be one the widgets emit, or a
domain.serviceyou saved on a Button widget.
A signed-in browser skips both checks entirely. /api/ha-lists/… got the same treatment.
This round can break things. The check is derived from your saved configs, and there are setups it does not see:
- A custom module that hard-codes an entity in its bundle instead of declaring it as a manifest field, or calls a service outside the built-in set. The display shows the refusal.
- An automation or script calling the route directly.
/api/ha/actionand/api/ha-lists/…have no token authentication.?key=does not help here. If you drive Magic Frame from arest_command, check it after updating. - Reading a Home Assistant list through
/api/ha-lists/…now needs that list to be on a view.
Turning the limit off
Set MAGIC_FRAME_HA_ACTION_UNRESTRICTED=1 and restart. This restores the old behaviour completely.
- Docker Compose: in the
.envnext todocker-compose.yml - Home Assistant add-on: the
ha_action_unrestrictedoption - Kubernetes: the same key in the ConfigMap (Helm:
appConfig.haActionUnrestricted)
The family lists finally work from the wall
Ticking an item on the built-in Shopping or Todos widget never worked from a display without a login: the tick appeared and silently reverted at the next poll. These lists are household data - reading them was always open - so a display may now tick, add and clear them, and dismiss messages and timers. Creating a timer or sending a message still needs an account or the companion token, as before.
Displays also say when something failed now. Widgets used to fire a request and ignore the answer; a refused or failed action shows its reason on the screen for a few seconds instead of doing nothing.
The login lockout counts per address now
Every failed login used to be filed under one placeholder address, so five wrong guesses by anyone locked /login for the whole household for 30 minutes. Caddy now forwards the real client address, and existing installs pick this up automatically with the new caddy image. If you run your own reverse proxy in front, set TRUSTED_PROXY_HOPS=2 in .env (see Hosting).
The editor is closed when SESSION_SECRET is missing or short
/login and /editor answer HTTP 503 with a plain-text explanation instead of letting everything through. It used to let every request pass, which quietly left the editor open. /view/… is untouched: a broken .env costs you the editor, not the screen on the wall.
Docker and add-on installs generate a 64-character secret and are unaffected. Kubernetes users who never replaced the placeholder will see the 503 - generate one with openssl rand -hex 32 and put it in the Secret, not the ConfigMap.
Twelve admin routes now require an Admin account
Roles existed from the first release but almost nothing checked them. A viewer could change security settings, restore a backup over the whole install, and upload a custom module. Single-user installs are admin by default and lose nothing.
The database port is no longer published
docker-compose.yml no longer maps 5432 to the host. It carried a superuser login with postgres/postgres reachable from the whole network - password hashes, TOTP secrets, the Home Assistant token - and a published Docker port sits in front of the host firewall, so it was open even with ufw closed.
The documented backup path (docker compose exec db pg_dump) is unaffected. If you back up or query the database from outside the container, it will stop working, and nothing will tell you - a pg_dump cron will simply start failing. Check yours. To keep the port, put it in a docker-compose.override.yml next to the compose file; Compose merges it automatically and updates never touch it:
services:
db:
ports:
- "127.0.0.1:5432:5432"
The installer
git is checked up front, curl warns instead of failing (it is only needed for the final readiness check). And hand-edited files in the repo folder are now reset before the update: previously a hand-edited docker-compose.yml made the pull abort, so you kept your edit and got no update. Now you get the update and lose the edit - a timestamped copy is saved to .magic-frame-local-changes/ and the path is printed. If you changed docker-compose.yml (a port, extra_hosts, DNS), move it to docker-compose.override.yml before you update.
Smaller fixes
- The Timer widget honours up to 6 concurrent timers; the slider offered 6 but the code capped at 4
- Timer, Messages, Shopping and Todos can be added from the phone editor; they were missing from its picker
- The photo info bar's text shadow slider actually applies on displays
- A notification with a fixed duration survives a reload; its age no longer comes from
last_changed, so the "feed the cat" tile stays up on day three - A broken calendar feed among several is named on the widget instead of silently dropped
- Long press with a Home Assistant service call works; it never did, the inspector and the widget disagreed on the config key
- Comma-separated
entity_idlists anddevice_id/area_idtargets pasted from Home Assistant's developer tools work on displays when they come from a saved Button config
Rollback
If anything pinches: MAGIC_FRAME_HA_ACTION_UNRESTRICTED=1 restores the old Home Assistant behaviour completely. Full rollback: git checkout v1.3.4 && docker compose up -d - the v1.3.4 images stay published.