Repository navigation
Headless Server
Run Halny Vibe Deck as an always-on server: no GUI, the UI is served over HTTPS and you use it from any browser or the PWA on your phone. Typical setup: a home server or VPS that is always reachable from your LAN/VPN.
Any Linux install works (AppImage extracted, the Linux zip unpacked, or the install script — see Installation). On a server without FUSE, extract the AppImage:
mkdir -p ~/apps && cd ~/apps
# download the AppImage from the releases page, then:
chmod +x halny-vibe-deck-<version>-x64.AppImage
./halny-vibe-deck-<version>-x64.AppImage --appimage-extract
mv squashfs-root halny-<version>Alternatively, download halny-vibe-deck-linux-x64.zip from the releases and unpack it — no FUSE needed either:
mkdir -p ~/apps/halny-<version>
unzip halny-vibe-deck-linux-x64.zip -d ~/apps/halny-<version>Both layouts end up with the halny-vibe-deck executable in the app folder. Run that binary directly — there is no AppRun in the zip layout, and calling the binary works for both.
The web server requires a login and password. The easiest way is CLI flags — Halny generates a random salt, hashes the password with scrypt and saves it to the app config; the plaintext is never stored. Username must be at least 3 characters, password at least 6.
xvfb-run -a ~/apps/halny-<version>/halny-vibe-deck --no-sandbox --headless --serve \
--port=8443 --user=yourname --password='your-secret'Stop it with Ctrl+C once you see the [serve] credentials saved for user: yourname line. Credentials can also be set in Settings → Serwer in the GUI — both paths write the same scrypt hash to the app config.
Tip: run this once to persist credentials, then start the service without the flags — your password never appears in
psoutput.
xvfb-run -a ~/apps/halny-<version>/halny-vibe-deck --no-sandbox --headless --serve --port=8443-
--headless— no window is created; the server runs without a desktop session -
--serve— starts the HTTPS server (you can also enable it in Settings so the flag is not needed) - HTTPS uses a self-signed certificate by default — accept the browser warning, or pass
--cert/--keywith your own certificate
You should see: [serve] HTTPS listening on https://0.0.0.0:8443. Point your browser at https://<server-ip>:8443 and log in.
Create ~/.config/systemd/user/halny-vibe-deck.service:
[Unit]
Description=Halny Vibe Deck - headless serve (HTTPS :8443)
After=network.target
[Service]
# v1.0.28+: the app integrates with systemd (sd_notify). Type=notify means
# the unit only reports "active" after the app confirms it is ready, and
# WatchdogSec restarts the service automatically if the process ever
# wedges (port open but nothing answered - the silent 521 behind a reverse
# proxy). NotifyAccess=all is required because the app runs as a child of
# xvfb-run, not as the unit's MainPID.
Type=notify
NotifyAccess=all
WatchdogSec=90
ExecStart=/usr/bin/xvfb-run -a /home/YOURUSER/apps/halny-VERSION/halny-vibe-deck --no-sandbox --headless --serve --port=8443
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.targetEnable it — and make it start at boot without an open login session via lingering:
systemctl --user daemon-reload
systemctl --user enable --now halny-vibe-deck
loginctl enable-linger $USERCheck status and logs:
systemctl --user status halny-vibe-deck
journalctl --user -u halny-vibe-deck -fVerify the watchdog is armed (v1.0.28+):
systemctl --user show halny-vibe-deck -p Type,NotifyAccess,WatchdogUSec,ActiveState
# expected: Type=notify NotifyAccess=all WatchdogUSec=1min 30s ActiveState=activeIf
journalctl --userever stops showing new entries on your system, read the same logs withsudo journalctl _UID=$(id -u) --since '...'.
systemctl --user stop halny-vibe-deck
xvfb-run -a ~/apps/halny-<version>/halny-vibe-deck --no-sandbox --headless --serve --port=8443 \
--user=yourname --password='new-secret' # wait for credentials saved, then Ctrl+C
systemctl --user start halny-vibe-deckCredentials are read from the config on each start, so no flags are needed in the unit file.
systemctl --user stop halny-vibe-deck
cd ~/apps && gh release download -R Dragonk/halny-vibe-deck -p 'halny-*-x64.AppImage' --clobber
chmod +x halny-<new>-x64.AppImage && ./halny-<new>-x64.AppImage --appimage-extract
mv squashfs-root halny-<new>
# update the halny-<new> path in the unit file, then:
systemctl --user daemon-reload && systemctl --user start halny-vibe-deckDesktop installs (install script) can update in place from Settings.
From v1.0.29 a systemd deployment can update in place too: open Settings → Updates in the web UI and click update — the app downloads the Linux zip, unpacks it over its own directory (safe on Linux: the kernel keeps the old inode mapped) and then asks systemd to restart the unit, which re-executes the freshly unpacked binary. Do not use the in-app update on a server with versions older than 1.0.29 — the relaunch step leaves the service down.
Keep the previous version folder as a rollback: point the unit's
ExecStart back at it, then daemon-reload and restart.
- Passwords stored as scrypt + random salt in the app config — never in plaintext
- Login is rate-limited (8 attempts / 10 min per IP)
- Session cookie is httpOnly, Secure, SameSite=Lax, with 30-day sliding expiry; the WebSocket requires the same cookie
- The server listens on all interfaces; combine it with your VPN/firewall rules. Do not expose it to the public internet with only the built-in auth.
| Symptom | Fix |
|---|---|
| Serve credentials not configured | Set login and password first (step 2) |
| Electron aborts with a sandbox/SUID error | Add --no-sandbox (or fix chrome-sandbox ownership + mode 4755) |
| Missing X server / ozone X11 error | Run under xvfb-run -a (or set DISPLAY) |
| AppImages require FUSE | Extract with --appimage-extract (step 1) or use the Linux zip |
AppRun: not found |
The zip layout has no AppRun — call the halny-vibe-deck binary directly instead (works for both layouts) |
| Port already in use | Change with --port, or stop the old instance |
| Reverse proxy reports 521; process alive but no response | Wedged main loop — with the v1.0.28 watchdog unit systemd restarts it within 90 s; look for [loop] lag lines in the journal (v1.0.29+) to find the cause |
| Web client shows „Connection to the app server was lost” while resuming a big session | The whole session history travels as one WebSocket frame; on slow links the flush outlasted the heartbeat and the server dropped the client mid-resume. Fixed in v1.0.32 (compressed frames + flush-aware heartbeat + parked-and-replayed calls during reconnect); watch [serve] ws ... journal lines to diagnose disconnects |
Service restarts every ~90 s although it serves fine (no [loop] lines) |
Chromium moved the browser process out of the unit cgroup into an app-*.scope, so the watchdog never saw the pings — fixed in v1.0.31 ([systemd] self-heal lines in the journal confirm it); update to >= 1.0.31 |
journalctl --user shows no new entries |
Read logs via sudo journalctl _UID=<uid> --since '...' instead |
| In-app update left the service down | Fixed in v1.0.29 (systemd-aware restart); on older versions update manually (section 6) |