dorkhub

magic-frame

Self-hosted home display — part photo frame, part family board, part Home Assistant dashboard. Drag-&-drop, live sync,…

jeremiaa
TypeScript34010 forksupdated 1 day ago
visit the demogit clone https://github.com/jeremiaa/magic-frame.gitjeremiaa/magic-frame
Magic Frame — local glassmorphism dashboard for tablets, monitors, and picture frames

English · Deutsch · 🌐 magicframe.dev

Runs entirely on your home network — no cloud account, no domain needed.

Drag & drop editor · Real live updates · Smart-home · Calendar · Weather · Picture-frame mode

License: Polyform NC Next.js 16 Docker PRs welcome Sponsor


What is it?

Magic Frame turns any browser-capable screen — tablets, kitchen monitors, old TVs, picture frames — into a self-hosted display for your home:

  • Family board — shopping list, todos, calendar, weather
  • Smart-home hub — live Home Assistant entities, scene buttons, camera pop-ups, notification tiles
  • Digital picture frame — wallpaper rotation from Immich or WebDAV, subtle clock on top
  • Status display / signage — power usage, timers, quick posts, rotating notices (non-commercial — see license)

One view per display, each with its own URL, layout and wallpaper. Everything syncs live over WebSocket — change a widget on your laptop and every display updates in under 100 ms, no refresh.

Where does it run?

Install it once on any box in your home network — that machine becomes "the server", your displays just open its IP in a browser. No cloud account, no domain, no DDNS required. Postgres ships inside the Docker stack.

Hardware
Raspberry Pi 4 / 5, Mini-PC (NUC, Beelink, …)
Synology / QNAP NAS ✅ Docker package in the NAS OS
Old laptop / desktop / Mac mini
VPS / cloud server ✅ optional — only if you want outside access

Rule of thumb: as long as you don't actively set up a domain, everything stays local on your LAN.


Quick Start

Two commands on a fresh Linux box. Skip step 1 if you already have Docker:

# 1. Install Docker (with Compose plugin) — official one-liner
curl -fsSL https://get.docker.com | sh

# 2. Install Magic Frame
curl -fsSL https://raw.githubusercontent.com/jeremiaa/magic-frame/main/deploy/install.sh | bash

macOS / Windows? Install Docker Desktop instead of step 1, then run step 2 in a terminal.

No curl? Install it (sudo apt install curl / sudo dnf install curl) — or use git instead: git clone https://github.com/jeremiaa/magic-frame.git && cd magic-frame && ./deploy/install.sh

The installer clones the repo, generates secrets, pulls the pre-built multi-arch images (ghcr.io, amd64 + arm64 — no 20-minute compile on a Pi) and starts the stack: app + Postgres + Caddy. Then open http://<your-ip>setup flow → create the first admin → done. Integrations (Google/Microsoft Calendar, OpenWeatherMap, Todoist, Home Assistant, Immich) are all added later through the UI.

Updating to a new version

The same command updates you — run it from the magic-frame folder any time a new version is out. It pulls the latest code, grabs the new images, and restarts. Your data, login, secrets and uploaded modules are never touched (none of that lives in git):

cd magic-frame && ./deploy/install.sh

No separate git pull needed — the installer does it for you. To build from source instead (forks, local changes): ./deploy/install.sh --build


Demo

Editor — drag widgets onto the grid, configure in the inspector

Magic Frame editor — adding widgets to a view on a 24-column grid

Dashboard — all your views and live system status at a glance

Magic Frame dashboard — Welcome screen with view thumbnails and system status strip

Watch the full ~1 min walkthrough (create view → drag widgets → configure inspector → save → live-sync to displays).


In the wild

Real-world setups across different hardware. Same project, different layouts, different rooms.

Big portrait monitor on the wall

Wall-mounted portrait monitor showing the time, two upcoming calendar entries, three live notifications and a 4-day weather forecast over a mountain wallpaper Close-up of notification tiles on the wall monitor at night
Info layout: clock, two upcoming calendar events, three live HA notifications, current temperature and 4-day weather forecast over a rotating Immich wallpaper. Quiet and glanceable for a hallway, office or bedroom wall. Notifications close-up: rule-based tiles that auto-show when something happens (washing machine done, "feed Milou", dryer done) and auto-hide once acknowledged. Wallpaper keeps running underneath.

Picture-frame tablet on a side table

Small picture-frame tablet on a side table with HA scene buttons, a clock and the current weather

Scene-button layout: a small tablet in a real photo-frame mount. Quick-access HA buttons, small clock, current temperature, rotating wallpaper underneath.

📸 More screenshots — dashboard, views, editor, modules

Dashboard — entry point with live status

Dashboard 3 stat cards (views · live-sync · integrations) + system-status strip (HTTPS, DDNS, HA, Todoist, modules, backups, security) + mini-previews of all views at a glance.

Views — all dashboards & displays

Views overview Each view is its own URL for one display — portrait for the tablet, landscape for the TV. Live previews show the real widget arrangement per view.

What it looks like on the display

Live view with buttons + weather Live view minimal Live view with open button picker
Smart-home display: clock, 4 scene/device buttons (HA services), weather with 4-day forecast. Minimal / picture frame: just clock + weather, the wallpaper rotation is the main element. In action: tap on a button opens the matching pop-up — here a colour picker for a lamp incl. power toggle.

View editor — drag & drop

View editor 24-column grid, widget catalog on the left, inspector on the right. Auto-snapshot before every save, TV-sync to all connected displays.

Modules — upload your own widgets

Modules The core widgets ship with every install. Custom modules via JS-bundle upload — hot-loading, no container restart needed.

Features

Widgets (18 core)

Widget Description
Clock Time + date, optional mini weather, 12/24h
Weather Open-Meteo, DWD, OpenWeatherMap, or HA weather entity · Meteocons + 3D icon sets
Environment Air quality, pollen, PM and UV — Open-Meteo or your own HA sensors
Calendar iCal + Google + Microsoft 365 + HA calendars · list, agenda and month views · 12/24h toggle
Home Assistant Any HA entity + rule engine (colour/icon per state)
HA Notifications Rule-based push tiles, tap-to-toggle, auto-hide when quiet
Camera HA camera entities — snapshot refresh, fullscreen view
Sensor Multi-sensor value tiles — per-sensor icon/colour, history sparkline
Image Photo tile — Immich album or WebDAV slideshow
Buttons Tap tiles with HA service calls (incl. service data) / webhooks
Timer Live countdown, startable via REST API / iOS Shortcut
Messages Quick post (text + image) via REST API with TTL
Shopping 3 sources: local, HA (todo.*) or Todoist
Todos 3 sources: local, HA (todo.*) or Todoist
Media Player Now-playing card — artwork, progress, controls, several players stack
RSS Feed Headlines with thumbnail and teaser, QR code to carry on reading
QR Code Wi-Fi, links and text — rounded dots, gradients, icon in the middle
Status Device card with picture and live details — car charging, printer printing, toniebox playing

Editor & live view

  • Drag & drop builder on a 24-column grid — multiple views (portrait/landscape), one URL per display
  • Stack & overlay widgets with a drag-sortable layer list (z-order)
  • HA-triggered show/hide — bind any widget to an entity state: doorbell rings → camera pops up over the photos, auto-hides after n seconds. Home Assistant can also "press" dashboard buttons remotely
  • Light + dark editor theme · per-view auto-refresh (off / 1–24 h) · auto-snapshots before every save (last 20)
  • Live sync via WebSocket to every connected display · i18n German + English

Integrations

  • Home Assistant — live entity updates over one WebSocket (pushed, not polled)
  • Google Calendar and Microsoft 365 via OAuth (multiple accounts) — plus plain iCal feeds
  • Immich + WebDAV as wallpaper sources · Todoist · OpenWeatherMap (optional)
📅 Google Calendar on a home network — two ways
  • Simplest read-only path: Google Calendar's secret iCal address (Google Calendar → Settings → Integrate calendar) works as a plain iCal feed — no OAuth app, no domain needed. Trade-offs: Google refreshes that export on its own schedule (it can lag a few hours), and the link grants read access to anyone who has it.
  • OAuth on a LAN-only install: Google refuses local addresses as redirect URIs — but the domain never has to be reachable from the internet, it only has to resolve to your box inside your network. Set up a DDNS domain + HTTPS under Settings → Hosting & Network (DuckDNS/Cloudflare built in, DNS-challenge — no open ports), then open the editor via that https URL when connecting Google.
🔐 Hosting & security — all optional, off by default

For purely local LAN use you need none of these. Toggle-able in the UI:

  • Caddy reverse proxy with automatic HTTPS via Let's Encrypt
  • 10 DNS providers baked in for ACME DNS-01 (no open ports needed)
  • DDNS updater (Cloudflare, Hetzner, DynDNS-v2 generic)
  • 2FA (TOTP) with authenticator apps + recovery codes
  • In-app brute-force protection · scrypt password hashing · iron-session

Custom modules

Upload a JS bundle through the UI — hot-loaded on every display, no container restart. Build helper (node scripts/build-module.mjs), manifest with field schema, example module in examples/modules/hello/. See docs/custom-modules.md — community modules & PRs welcome.

📱 Companion app (iOS) — in development

Native Swift app being built alongside the web editor. Not yet available — TestFlight beta coming soon. Planned: lock-screen timer (App Intent), quick posts with TTL, shopping/todos sync with iOS Reminders, push notifications per frame, view switch/refresh from anywhere.

Until then (and afterwards too): all of this already works via the REST API with a shortcut token — perfect for iOS Shortcuts, Tasker, or curl. See docs/companion-api.md.


Architecture

One Docker stack with three services, all on the same host:

Layer What
Caddy Reverse proxy + automatic HTTPS (Let's Encrypt). Custom build with 10 DNS plugins for ACME DNS-01. For purely local use it runs as a plain HTTP proxy without TLS.
Next.js app /editor is the admin UI. /view/<id> is what displays render. /api/... is the REST surface for the companion app, shortcuts, and external tools. Socket.IO pushes live updates to every display.
Postgres 16 Dashboards, layouts, snapshots, users, OAuth tokens, custom modules, app settings.

Data flow on save: browser edits a widget → Next.js API → snapshot to Postgres → Socket.IO event → every display re-renders in under 100 ms. Persistent volumes (Postgres data, settings, wallpaper cache, Caddyfile, certs) survive every update.


Maintenance

cd magic-frame && ./deploy/install.sh                # update — data, login and modules survive
docker compose logs -f app                           # logs (app / caddy)
docker compose exec db pg_dump -U postgres magicdashboard | gzip > backup-$(date +%F).sql.gz   # DB backup
Update fails with "divergent branches" / "would clobber existing tag" (early v1.0.x clones)

If you cloned during launch week (before v1.0.2), upstream history was rewritten a few times. One-time recovery — your .env, database and modules are untouched (none of that lives in git):

cd magic-frame
git fetch --force --tags origin
git reset --hard origin/main
./deploy/install.sh

Troubleshooting

Browser refuses to load http://<server-ip> / says HTTPS failed

Brave, Chrome and Edge auto-upgrade http:// to https://. On a fresh local install there's no cert yet, so the upgrade fails before the request reaches Magic Frame.

  • Quickest workaround: type the full path — http://<server-ip>/login (the auto-upgrade often only fires on bare host URLs)
  • Brave: brave://settings/security"Always use secure connections" → off (or per-site exception) · Chrome/Edge: same setting under chrome://settings/security
  • Long-term fix: set up a domain → Caddy gets a real Let's-Encrypt cert automatically (Settings → Hosting & Network)
Bind for 0.0.0.0:80 failed: port is already allocated during install

Something else on the host is on port 80. Check ss -tlnp | grep :80 and docker ps --filter "publish=80" — common culprits are distro nginx/apache2 (systemctl stop nginx && systemctl disable nginx) or another container. After fixing: docker compose down && docker compose up -d.

Home Assistant unreachable although the name resolves inside the container

Split-horizon or self-hosted DNS (Technitium, Pi-hole, Unbound, …) can produce a confusing failure: dig and curl resolve the name inside the container just fine, but Magic Frame still reports ENOTFOUND.

The cause is usually the AAAA answer. If your resolver replies NXDOMAIN for the IPv6 query instead of NOERROR with an empty answer, musl libc — which the Alpine-based image uses for getaddrinfo(), and therefore for fetch/WebSockets — treats the whole dual-stack lookup as failed, even though the A record resolved. dig and curl don't go through that code path, which is why they keep working.

Check it with:

dig @<your-dns> ha.example.lan AAAA   # NXDOMAIN here is the problem — NOERROR is correct
dig @<your-dns> ha.example.lan A      # this one resolves fine
  • Quick fix: enter Home Assistant by IP address instead of hostname
  • Proper fix: make the resolver answer NOERROR (NODATA) for names that only have an A record

Thanks to @proffalken for tracking this down in #64.

Page still shows old behaviour after an update

Next.js and your browser both cache aggressively. Hard-refresh (Cmd+Shift+R / Ctrl+Shift+R) or use an incognito window after docker compose up -d --build.


Documentation

ROADMAP.md What's coming next + how releases work
LICENSE.md Polyform Noncommercial 1.0.0
.env.example All environment variables documented
docs/custom-modules.md Build + upload your own widget modules
docs/module-development.md Core-widget development (source-tree patch)
docs/companion-api.md REST API endpoints (companion app / your own scripts)
kubernetes/ Community Kubernetes manifests (thanks @RudiKlein)

Tech stack

Next.js 16 · React 19 · Postgres 16 + Prisma 7 · Caddy 2 (xcaddy custom build) · Tailwind CSS 4 · Socket.IO · react-grid-layout · iron-session · otplib · esbuild


Contributing

Issues with a clear reproduction are especially welcome — they directly drive releases (most of v1.1 came straight from community requests). PRs are happily reviewed; for larger changes please open an issue first so we can sort out what fits.


❤️ Support

Magic Frame is free for home use and built in my spare time. If it hangs on your wall and you want to say thanks:

GitHub Sponsors Buy Me a Coffee


License

Polyform Noncommercial 1.0.0 — open-source-style, allows free use, modification, distribution, and contribution. Commercial use (selling, SaaS offering, embedding in your own products) is not permitted without a separate license.

For commercial inquiries: magicframeapp@gmail.com

Vibe-coded with Claude.

more like this

agentlytics

Comprehensive analytics dashboard for AI coding agents — Cursor, Windsurf, Claude Code, VS Code Copilot, Zed, Antigravi…

JavaScript555
@f

frirss

A modern, self-hosted, customizable web frontend for FreshRSS.

TypeScript51

scriptio

Modern, elegant and cross-platform screenwriting software

TypeScript51

search

search projects, people, and tags