A lightweight digital signage solution for Raspberry Pi. Node.js server with real-time WebSocket control and a React player optimized for smooth video playback.
- Full-screen kiosk mode with Chromium
- Real-time WebSocket control (instant video switching)
- Playlist support (videos + images)
- Eden collection sync (download and play from Eden API)
- Public URL via ngrok tunnel
- API key authentication for secure remote control
- HTTP API for curl/scripting
- Video with audio (HDMI output)
- Local video files and web URLs
- Auto-start on boot
- Minimal resource usage
Before setting up a Pi, you need:
-
ngrok account (free): https://dashboard.ngrok.com
- Get your authtoken from the dashboard
- Create a free static domain (e.g.,
pi01-xyz.ngrok-free.app)
-
Eden API key (optional): For syncing collections from Eden
1. Flash Raspberry Pi OS
Use Raspberry Pi Imager to flash Raspberry Pi OS 64-bit Lite (or Desktop).
In the imager settings:
- Enable SSH
- Set username to
pi - Configure WiFi (optional)
2. Run the setup script
SSH into the Pi and run:
curl -sL https://raw.githubusercontent.com/mars-college/chiba/main/setup-kiosk.sh | bash -s -- \
--ngrok-token YOUR_NGROK_AUTHTOKEN \
--ngrok-domain YOUR_DOMAIN.ngrok-free.app \
--eden-key YOUR_EDEN_API_KEYOr download and run manually:
wget https://raw.githubusercontent.com/mars-college/chiba/main/setup-kiosk.sh
chmod +x setup-kiosk.sh
./setup-kiosk.sh \
--ngrok-token YOUR_NGROK_AUTHTOKEN \
--ngrok-domain YOUR_DOMAIN.ngrok-free.app \
--eden-key YOUR_EDEN_API_KEY3. Reboot
The setup script will prompt to reboot. After reboot:
- Kiosk starts automatically on the connected display
- ngrok tunnel starts automatically
- Your Pi is accessible at
https://YOUR_DOMAIN.ngrok-free.app
For each Pi, create a unique ngrok domain in your dashboard, then run the setup with that domain:
| Pi | ngrok Domain | Setup Command |
|---|---|---|
| Pi 01 | pi01-abc.ngrok-free.app |
./setup-kiosk.sh --ngrok-token XXX --ngrok-domain pi01-abc.ngrok-free.app |
| Pi 02 | pi02-def.ngrok-free.app |
./setup-kiosk.sh --ngrok-token XXX --ngrok-domain pi02-def.ngrok-free.app |
| Pi 03 | pi03-ghi.ngrok-free.app |
./setup-kiosk.sh --ngrok-token XXX --ngrok-domain pi03-ghi.ngrok-free.app |
All Pis can share the same ngrok authtoken and Eden API key.
Run the server locally to test:
npm install
node server.jsOpen http://localhost:8080/player in your browser.
The server runs on port 8080 with HTTP + WebSocket on the same port.
Full API documentation: See API.md for complete endpoint reference, code examples, and client libraries.
All POST endpoints require an API key. The setup script generates one automatically - save it during setup!
Include the key in your requests:
curl -X POST https://your-domain.ngrok-free.app/file \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"file": "example.mp4"}'GET endpoints (/status, /files, /player, /media/*) are public and don't require authentication.
| Method | Endpoint | Body | Description |
|---|---|---|---|
| GET | /status |
- | Server status + connected clients |
| GET | /files |
- | List media files |
| GET | /player |
- | Serve the player app |
| GET | /media/<file> |
- | Serve a media file |
| POST | /file |
{"file": "video.mp4"} |
Play a single video (loop) |
| POST | /url |
{"url": "https://..."} |
Show website in iframe |
| POST | /off |
- | Black screen |
| POST | /sync |
{"collectionId": "..."} |
Download collection from Eden |
| POST | /sync_and_play |
{"collectionId": "...", "loop": true} |
Sync + play as playlist |
# Check status
curl https://your-domain.ngrok-free.app/status
# List files
curl https://your-domain.ngrok-free.app/files
# Play a single video (loops)
curl -X POST https://your-domain.ngrok-free.app/file \
-H "Content-Type: application/json" \
-d '{"file": "example.mp4"}'
# Sync Eden collection and play as playlist
curl -X POST https://your-domain.ngrok-free.app/sync_and_play \
-H "Content-Type: application/json" \
-d '{"collectionId": "68538ccaf883914b6b8e09a1", "loop": true}'
# Play playlist without looping (stops after last item)
curl -X POST https://your-domain.ngrok-free.app/sync_and_play \
-H "Content-Type: application/json" \
-d '{"collectionId": "68538ccaf883914b6b8e09a1", "loop": false}'
# Turn off display
curl -X POST https://your-domain.ngrok-free.app/off- Videos: Play to completion, then advance to next
- Images: Display for 10 seconds, then advance to next
- Loop mode: After last item, restart from beginning
- No loop: After last item, display goes black
Connect to wss://your-domain.ngrok-free.app for real-time state updates:
{"type": "state", "mode": "video", "file": "example.mp4", "url": null}
{"type": "state", "mode": "playlist", "file": "current.mp4", "playlist": [...], "loop": true}
{"type": "state", "mode": "off", "file": null, "url": null}chiba/
├── server.js # Node.js server (HTTP + WebSocket)
├── eden.js # Eden API integration
├── run_kiosk.sh # Full kiosk mode (server + Chromium)
├── setup-kiosk.sh # Pi setup script (with ngrok)
├── status.sh # Check kiosk status
├── .env # API keys (created by setup)
├── .env.example # Example env file
├── public/ # Player app (built)
│ ├── index.html
│ └── assets/
├── media/ # Video/image files
└── player/ # React source code
# Check status (from SSH)
cd ~/chiba && ./status.sh
# View ngrok tunnel status
sudo systemctl status ngrok
# View kiosk logs
journalctl -u ngrok -f
# Restart ngrok tunnel
sudo systemctl restart ngrok
# Stop kiosk (from TTY2: Ctrl+Alt+F2)
pkill -f run_kiosk
# Full restart
sudo reboot# Check ngrok status
sudo systemctl status ngrok
# Check ngrok logs
journalctl -u ngrok --no-pager -n 50
# Test ngrok manually
ngrok http 8080 --domain=your-domain.ngrok-free.app- Check if server is running:
curl http://localhost:8080/status - Check WebSocket clients: should show
wsClients: 1 - Verify Chromium is running:
pgrep chromium
# Check audio device
amixer
# Set HDMI audio
sudo raspi-config # System Options > Audio > HDMIcd player
npm install
npm run build # Outputs to public/Edit server.js to add MIME types:
const mimeTypes = {
'.mp4': 'video/mp4',
// add more...
};MIT