Turn a device — a Chromebook, a phone, a laptop — into a dedicated music-player appliance with separate admin and user accounts, kiosk-locked so that users can only play music or configure their own profile.
Status: pre-release demo. The user and admin interfaces are functional; the Chromebook image builder and OTA update flow are implemented. A first tagged release will follow a period of real-device testing.
- A kiosk-locked music appliance with separate admin and user accounts.
- Admin configures the device, services, users, quotas, and network. Admin cannot play music.
- Users can only play music or configure their own account. No shell, no browsing, no messaging, no other apps.
- Multi-user aware: only one user plays audio at a time; another user logging in takes over and pauses the previous session.
- No telemetry. See Privacy.
| Service | Notes |
|---|---|
| MPD | Remote-control or stream-to-device modes; see MPD service |
| Subsonic | Navidrome, Funkwhale, Airsonic-Advanced; library cached locally |
| Internet radio | radio-browser.info directory (~30k stations) + SomaFM |
| Podcasts | Arbitrary RSS/Atom feeds |
| Spotify | Web Playback SDK (Premium required; Widevine DRM — see Privacy) |
| Local files | Files stored on the device or mounted from LAN (samba) |
Local files via removable drives, WebDAV, NFS, SFTP, and rclone-mounted remotes; Deezer, Apple Music, Amazon Music, SoundCloud, YouTube Music, Tidal, Qobuz, Bandcamp, and Podcast Index integration.
| Target | Status | README |
|---|---|---|
| Demo — any Debian/Ubuntu desktop | Functional; runs without installation | targets/demo/README.md |
| Chromebook — HP x360 12b-ca0010nf | Image builder + OTA update implemented | targets/chromebook/README.md |
Raspberry Pi, smartphones, and generic desktop installs are planned for future versions.
The demo target runs on any modern Debian/Ubuntu desktop without installation. It cannot replace the production Chromebook image, but it is the primary development and UI-testing vehicle.
git clone https://github.com/your-org/zik.git
cd zik
./install-build-deps.sh --yes demo
make setup
make dev-demoOpen http://localhost:5173 in a browser. Ctrl-C stops all processes.
See targets/demo/README.md for the full dev-loop, uninstall instructions, and how to run tests.
See targets/chromebook/README.md for the full procedure: ChromeOS recovery backup, firmware flash, image build, and SSD install. The short version:
# On the build host
cd targets/chromebook
sudo ./build-image.sh --version 1.0
sudo ./configure-image.sh --image work/zik-chromebook-1.0.img.zst --config config.yaml
# Flash to a USB key, boot the Chromebook from it, optionally write to the SSD.Zik can connect to any running Music Player Daemon instance on the local network or on the device itself. Two modes are supported and can be combined on the same server.
This is the classic MPD setup. The MPD server is connected to a sound system (speakers, amplifier, DAC…) and plays audio through its own audio output. Zik acts as a remote control: it browses the library and sends play/pause/stop/seek/volume commands via the MPD protocol. No stream URL is needed.
MPD server configuration (mpd.conf):
# Any standard audio output will do — for example PipeWire:
audio_output {
type "pipewire"
name "PipeWire output"
}
To prevent clients from controlling playback (e.g. if you want the server
to be browseable but not playable by remote clients), restrict the password
to the read command group only:
password "readonly_password@read"
password "full_password@read,add,control,admin"
Connecting from the zik appliance:
Fill in Host, Port (default 6600), and optionally Password. Leave Stream URL empty. When you click a track, the MPD server begins playing through its own audio output.
The MPD server is configured with an httpd output plugin, which broadcasts
the current audio as an HTTP stream. Zik fetches that stream and plays it
locally through the browser's audio engine. This lets you control an MPD
library while hearing the music on the zik device itself.
MPD server configuration (mpd.conf):
audio_output {
type "httpd"
name "HTTP stream"
encoder "lame" # or "opus", "flac", "vorbis"
port "8000"
bitrate "192"
format "44100:16:2"
always_on "yes" # keep the port open even when not playing
}
You can have both an httpd output and a local audio output active at the
same time; MPD will feed all active outputs simultaneously.
Connecting from the zik appliance:
Fill in Host, Port, Password (if any), and set Stream URL to the
address of the httpd output — for example http://192.168.1.10:8000/.
When you click a track, MPD starts playing and Zik opens that URL in its
audio engine.
Known limitation: HTTP audio streams are not seekable in the traditional sense. When you use the seek bar, Zik sends the seek command to MPD (which changes its playback position) and then reconnects the stream, so audio resumes from the new position after a brief gap.
Zik connects to any Subsonic-compatible server (Navidrome, Funkwhale, Airsonic-Advanced). Auth uses the token+salt method (token = MD5(password+salt) per Subsonic API ≥ 1.13).
The library is fetched once and cached to disk; subsequent sessions load the cache instantly. Two refresh modes are available on the source strip:
- Quick refresh (↺) — fetches the 500 most recently added albums and merges new tracks into the cache. Fast, but cannot detect deletions or metadata changes.
- Full refresh (⟳) — re-fetches the entire library via paginated
search3. Detects deletions and metadata changes; takes longer for large libraries.
Zik collects no telemetry of any kind. The following services may receive network requests from the device; all are triggered by user actions or configured feeds, not by background analytics.
| Service | When contacted | Data sent |
|---|---|---|
| radio-browser.info | Searching for stations; playing a station (click registration) | Search query; station UUID |
| SomaFM | Loading the internet radio page | None beyond your IP address |
| Podcast RSS feeds | Subscribing to or refreshing a feed | Your IP address (to the feed server) |
| Spotify | Using the Spotify service | Full Spotify session (account data, playback); Widevine DRM requires a licence request to Google |
Services not yet implemented that may contact external servers in future versions: Last.fm (optional scrobbling), LRCLIB (lyrics), Podcast Index (podcast search).
Zik does not install browser extensions, modify the system hosts file, or inject tracking scripts. Streams and metadata are fetched by the backend and forwarded to the frontend; vendor APIs are never called directly from the browser, except for the Spotify Web Playback SDK which runs in the browser by design.
The library fetcher pages through search3 results (500 tracks per request) and
stops at a safety cap of 100 000 tracks (_MAX_SONGS in
common/backend/zik_backend/services/subsonic/client.py).
Libraries beyond that size will be silently truncated. Tracks added after the
initial fetch appear via the quick-refresh path (checks the 500 most recently
added albums via getAlbumList2?type=newest). If your library exceeds 100k
tracks, raise the constant or implement a smarter pagination strategy.
Subsonic authentication uses a token+salt pair (token = MD5(password+salt)) that
is embedded as query parameters in each track's stream URL
(/rest/stream?u=…&t=…&s=…). These URLs are built in the browser from auth
data returned by the backend, and are only ever passed as <audio src> — the
browser does not expose them to JavaScript after assignment. They are held in
memory as part of the play queue (QueueItem.audioUrl) for the lifetime of the
session and are never written to disk or localStorage. An attacker with access
to the browser process memory or DevTools on the device could extract them. On
a locked-down kiosk this is an acceptable risk; on a shared or multi-user
desktop it should be noted.
On the Chromebook target, the GRUB boot menu is password-protected with a PBKDF2-hashed admin password. The maintenance shell (recovery partition) is gated behind the same password.
GPL v3. See LICENSE.