A custom smart display system built with Next.js. Designed to run on a Raspberry Pi in Chromium kiosk mode, replacing Dakboard with a fully web-based, drag-and-drop configurable display.
- Drag-and-drop editor — visually arrange modules on a 1080x1920 portrait canvas
- Multi-screen rotation — configure multiple screens that cycle automatically
- 33 built-in modules — clock, calendar, weather (8 views), countdown, dad jokes, text, image, quote, todo, sticky note, greeting, news, stock ticker, crypto, word of the day, this day in history, moon phase, sunrise/sunset, photo slideshow, QR code, year progress, traffic/commute, sports scores, air quality, todoist, rain map, multi-month calendar, garbage day, sports standings, affirmations, date (5 views), meal planner (5 views), and web embed (iframe)
- 5 weather providers — OpenWeatherMap, WeatherAPI, Pirate Weather, NOAA (free, no API key), and Open-Meteo (free, no API key) with a shared interface
- Profile system — define screen groups with schedule-based auto-activation (day of week, time windows)
- Remote display control — wake, sleep, brightness, screen navigation, and alerts via simple HTTP endpoints
- Per-module scheduling — show or hide individual modules by day of week and time window
- Google Calendar integration — display upcoming events from one or more calendars
- Background images — upload custom backgrounds or rotate via Unsplash or NASA Astronomy Picture of the Day
- Per-module styling — opacity, blur, colors, fonts, border radius, padding
- Password-protected editor — optional password auth for the configuration editor
- System management — upgrade, rollback, backup/restore, and power control from the UI
- Raspberry Pi kiosk scripts — one-command setup for a dedicated display
- Configurable screen transitions — 8 effects (fade, slide, slide-up, zoom, flip, blur, crossfade, none) with adjustable duration
- Auto-hide cursor — cursor hides after a configurable idle period on the display
- Pre-release update channel — opt into the dev channel for early updates
Home Screens is built for Raspberry Pi OS Lite 64-bit (Trixie). Desktop variants also work, but Lite is recommended — it has a smaller footprint and the install script provides everything needed for the kiosk display.
Clone the repo and run the install script:
git clone https://github.com/agent462/home-screens.git
# Raspberry Pi OS Lite (default)
~/home-screens/scripts/install.sh
# Raspberry Pi OS with Desktop
~/home-screens/scripts/install.sh --desktop
# Custom port (default is 3000)
~/home-screens/scripts/install.sh --port 8080The script handles everything:
- Downloads the latest pre-built release to
/opt/home-screens/ - Installs Node.js 22, Chromium, and system dependencies
- Creates the systemd service and configures the kiosk
- Configures autologin and display orientation
After install, reboot to start the kiosk:
sudo rebootTo run without the systemd services:
bash scripts/start-display.shsudo systemctl start home-screens # start the server
sudo systemctl stop home-screens # stop server + kiosk
sudo systemctl status home-screens # check status
journalctl -u home-screens -f # view logs# Install dependencies
npm install
# Run development server
npm run devThen visit:
http://localhost:3000/editor— configure your screenshttp://localhost:3000/display— fullscreen display view
All API keys and credentials are configured through the editor UI under Settings → Integrations. No .env.local file is required for normal use.
Google Calendar uses the OAuth 2.0 Device Flow, which means you can authorize from any device on your network — no redirect URI or public domain required. This is ideal for a kiosk display.
- Go to Google Cloud Console → APIs & Services → Credentials
- Click Create Credentials → OAuth Client ID
- Application type: TVs and Limited Input devices
- Name it anything (e.g. "Home Screen Display")
- In the editor, go to Settings → Integrations and enter the Client ID and Client Secret
- Enable the Google Calendar API at APIs & Services → Library → search "Google Calendar API" → Enable
- In the editor, go to Settings → Google Calendar → Sign in with Google
- You'll see a code and a link to
google.com/device— open that link on your phone or computer, enter the code, and grant access
If you're using the pre-built Raspberry Pi image, SSH is enabled with the following defaults:
| Hostname | home-screens.local |
| Username | hs |
| Password | screens |
ssh hs@home-screens.localChange the default password after your first login:
passwdIf you installed via install.sh on your own Raspberry Pi OS, SSH uses whatever user account you ran the installer from.
Your configuration and credentials live in the data/ directory (/opt/home-screens/current/data/ on the Pi). The editor has a built-in backup system at Settings > System > Backups that automatically retains up to 6 config snapshots, but it only covers config.json and does not back up sensitive secrets. To fully protect your setup, back up the key files manually:
# On the Raspberry Pi — copy to your home directory
cp /opt/home-screens/current/data/secrets.json ~/secrets-backup.json
cp /opt/home-screens/current/data/config.json ~/config-backup.json
cp /opt/home-screens/current/data/auth.json ~/auth-backup.json
cp /opt/home-screens/current/data/google-tokens.json ~/google-tokens-backup.json| File | Contents |
|---|---|
data/config.json |
Screen layouts, module settings, display configuration |
data/secrets.json |
API keys (weather, Unsplash, Todoist, TomTom, etc.) |
data/auth.json |
Editor password hash and session secret |
data/google-tokens.json |
Google Calendar OAuth tokens |
To restore from a backup, copy the files back and restart:
cp ~/secrets-backup.json /opt/home-screens/current/data/secrets.json
cp ~/config-backup.json /opt/home-screens/current/data/config.json
sudo systemctl restart home-screensTip: These files are preserved across upgrades, but it's still good practice to back them up before major updates. You can also pull them off the Pi entirely with
scp:scp hs@home-screens.local:/opt/home-screens/current/data/secrets.json ./
The server runs on port 3000 by default. You can set a custom port during install or change it any time afterward.
During install:
~/home-screens/scripts/install.sh --port 8080Checking the current port:
cat /opt/home-screens/current/data/port.conf # shows custom port, or "No such file" = default 3000Changing the port after install:
# Set a new port (replace 8080 with your desired port)
echo 8080 > /opt/home-screens/current/data/port.conf
# Regenerate the systemd service and kiosk launcher
bash /opt/home-screens/current/scripts/upgrade.sh setup-system
# Restart to apply
sudo rebootResetting to the default port (3000):
rm /opt/home-screens/current/data/port.conf
bash /opt/home-screens/current/scripts/upgrade.sh setup-system
sudo rebootThe port is resolved in order of priority: PORT environment variable > data/port.conf > default (3000). The data/port.conf file is preserved across upgrades.
If the display looks wrong (stretched, cropped, or rotated the wrong way), there are two ways to fix it:
From the editor (recommended):
- Open
http://<pi-ip>:<port>/editorfrom another device (default port is 3000) - Go to Settings > Display
- Adjust the width, height, and transform (rotation) settings
- Save — the display updates on the next refresh cycle
From SSH (if the editor is unusable):
Edit the kiosk config directly:
nano /opt/home-screens/current/data/kiosk.confThe file looks like this:
DISPLAY_MODE="1920x1080"
DISPLAY_TRANSFORM="90"
| Setting | Values |
|---|---|
DISPLAY_MODE |
Your display's native resolution (e.g., 1920x1080, 2560x1440) |
DISPLAY_TRANSFORM |
90 = portrait clockwise, 270 = portrait counter-clockwise, 180 = inverted. Remove the line entirely for landscape. |
After editing, reboot for changes to take effect:
sudo rebootYou can also use the interactive rotation script:
bash /opt/home-screens/current/scripts/rotate-display.shWhen your Google Cloud project is in Testing publishing status (the default), OAuth refresh tokens expire after 7 days. This means your Google Calendar module will stop showing events weekly, and you'll need to re-authorize through the device flow in Settings > Google Calendar > Sign in with Google.
This is a Google-imposed limitation on apps in test mode. Moving to production publishing status is out of scope for this documentation — see Google's publishing guide for details.
If you set a password on the editor and can't remember it, SSH into the Pi and delete the auth file:
rm /opt/home-screens/current/data/auth.json
sudo systemctl restart home-screensThe editor will be unprotected again. Set a new password in Settings > General.
Raspberry Pis run from SD cards, which have limited write cycles. Home Screens is configured to minimize disk writes out of the box:
- Journal — stored in RAM (volatile), capped at 16 MB, never written to disk
- Swap — uses zram (compressed RAM) instead of disk-based swap
- Temp directories —
/tmpand/var/tmpare mounted as tmpfs (RAM-backed) - Config writes — only occur when you save changes in the editor, not on a timer
No additional tuning is needed. These optimizations are applied automatically by the install script.
- Getting Started — installation and setup
- Editor Guide — how to use the visual editor
- Modules Reference — all 33 modules and their options
- API Reference — all API endpoints
- Configuration — config file schema and examples
- Raspberry Pi Deployment — kiosk setup and troubleshooting
- Development Guide — architecture, adding modules, and contributing
graph TB
subgraph Clients
Editor["Editor<br/>(browser)"]
Display["Display<br/>(kiosk / browser)"]
end
subgraph "Next.js Server"
API["API Routes"]
ConfigAPI["/api/config"]
SecretsAPI["/api/secrets"]
end
subgraph "Local Storage"
Config["data/config.json"]
Secrets["data/secrets.json"]
end
subgraph "External Services"
Weather["Weather Providers<br/>(OWM, WeatherAPI,<br/>Pirate Weather, NOAA, Open-Meteo)"]
ESPN["ESPN<br/>(scores, standings)"]
Google["Google<br/>(Calendar, Routes)"]
Other["RSS, CoinGecko,<br/>Yahoo Finance, etc."]
end
Editor -- "Zustand store<br/>PUT /api/config" --> ConfigAPI
ConfigAPI -- "read / write" --> Config
Display -- "GET /api/config" --> ConfigAPI
API -- "read keys" --> Secrets
SecretsAPI -- "read / write" --> Secrets
API --> Weather
API --> ESPN
API --> Google
API --> Other
- Next.js 16 / React 19 (App Router)
- Tailwind CSS v4
- @dnd-kit (drag-and-drop)
- Zustand (editor state)
- Framer Motion (screen transitions)
- Vitest (testing)
src/
app/
(display)/display/ # Fullscreen kiosk view
(editor)/editor/ # Configuration editor
api/ # Config, calendar, weather, jokes, backgrounds, and more
components/
modules/ # Clock, Calendar, Weather, Countdown, Quote, News, etc.
display/ # Screen rotator, screen renderer
editor/ # Canvas, module palette, property panel, settings, backgrounds
lib/ # Config I/O, weather providers, Google Calendar
stores/ # Zustand editor store
types/ # TypeScript config types
data/
config.json # Screen configuration (file-based, no database)
scripts/
install.sh # Full Raspberry Pi install script
start-display.sh # Manual start script
All API routes are server-side proxies that keep credentials off the client. The request flow:
graph LR
Browser["Browser<br/>(Editor or Display)"] -- "fetch" --> Route["Next.js<br/>API Route"]
Secrets["data/secrets.json"] -- "API keys" --> Route
Route -- "authenticated request" --> Service["External Service<br/>(weather, ESPN, etc.)"]
Service -- "response" --> Route
Route -- "JSON" --> Browser
| Route | Methods | Description |
|---|---|---|
/api/config |
GET, PUT | Read/write screen configuration |
/api/calendar |
GET | Google Calendar event proxy |
/api/calendars |
GET | List available Google Calendars |
/api/weather |
GET | Weather data (5 providers) |
/api/geocode |
GET | Location geocoding for weather |
/api/jokes |
GET | Dad jokes proxy |
/api/quote |
GET | ZenQuotes daily quote proxy |
/api/news |
GET | RSS feed parser |
/api/stocks |
GET | Yahoo Finance stock prices |
/api/crypto |
GET | CoinGecko crypto prices |
/api/history |
GET | This day in history |
/api/backgrounds |
GET, POST | List/upload background images |
/api/unsplash |
GET | Unsplash background photos |
/api/nasa |
GET | NASA Astronomy Picture of the Day |
/api/traffic |
GET | Traffic/commute times (Google Routes or TomTom) |
/api/sports |
GET | Live sports scores (ESPN) |
/api/air-quality |
GET | Air quality index and UV (OpenWeatherMap) |
/api/todoist |
GET | Todoist tasks proxy |
/api/rain-map |
GET | RainViewer precipitation map tiles |
/api/standings |
GET | ESPN league standings |
/api/time |
GET | Server time |
/api/image-proxy |
GET | Proxy external images |
/api/secrets |
GET, PUT | Manage API keys and credentials |
/api/display/* |
GET, POST | Remote display control (wake, sleep, brightness, navigation, alerts) |
/api/auth/* |
GET, POST | Authentication (password + Google OAuth) |
/api/system/* |
GET, POST | System management (version, upgrade, rollback, backups, power) |
- Create a component in
src/components/modules/ - Add the type to
ModuleTypeinsrc/types/config.ts - Define its config interface in
src/types/config.ts - Add a default size in
src/lib/constants.ts - Register it in
src/lib/module-registry.ts - Add a dynamic import in
src/lib/module-components.ts - Add an editor config section in
src/components/editor/PropertyPanel.tsx - (Optional) Create an API route in
src/app/api/if external data is needed
flowchart LR
Component["React Component<br/>src/components/modules/"] --> Types["Type + Config<br/>src/types/config.ts"]
Types --> Defaults["Default Size<br/>src/lib/constants.ts"]
Defaults --> Registry["Module Registry<br/>src/lib/module-registry.ts"]
Registry --> DynImport["Dynamic Import<br/>src/lib/module-components.ts"]
DynImport --> PropPanel["Editor Config Section<br/>PropertyPanel.tsx"]
PropPanel --> Render["Module rendered<br/>on canvas / display"]
See the Development Guide for a detailed walkthrough.


