Daemon (mosaicd)
Headless Linux variant for servers and NAS — same engine, same HTTPS+WS interface, no GUI.
mosaicd is the headless Linux build of Mosaic. It runs the same anacrolix-backed engine, the same SQLite persistence, and the same HTTPS+WebSocket interface as the desktop app, but with no Wails shell, no system tray, and no desktop notifications. The web UI is reached through any browser pointed at the daemon’s bind address.
Conceptually it is the equivalent of qbittorrent-nox: a long-running BitTorrent service controlled exclusively over its remote interface. Use it on seedboxes, NAS appliances, and any host where a GUI process would be a poor fit.
Install
mosaicd ships in three forms; pick the one your host prefers.
Debian / Ubuntu — apt repository (recommended)
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://repo.x86-64.com/exec.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/exec.gpg
echo "deb [signed-by=/etc/apt/keyrings/exec.gpg] https://repo.x86-64.com/ stable main" | sudo tee /etc/apt/sources.list.d/exec.list > /dev/null
sudo apt update
sudo apt install mosaicd
This is the preferred path going forward: future mosaicd releases land in the repo automatically, and apt upgrade keeps you current with no manual download step. The signing key is pinned per-source via signed-by=, so the apt trust scope is exactly this one repository.
The repository signing key fingerprint is 318B7812 99D5 C05B 2A0C 8F0F 88C3 F86B E415 C388 (exec apt repository <repo@x86-64.com>). Run gpg --show-keys /etc/apt/keyrings/exec.gpg after installing it to verify.
Debian / Ubuntu — direct .deb
curl -LO https://github.com/exec/mosaic/releases/latest/download/Mosaic-vX.Y.Z-linux-amd64-mosaicd.deb
sudo apt install ./Mosaic-vX.Y.Z-linux-amd64-mosaicd.deb
The package creates a dedicated mosaic system user, installs the binary at /usr/bin/mosaicd, the SPA bundle at /usr/share/mosaicd/dist/, and a systemd unit at /lib/systemd/system/mosaicd.service. It auto-enables and starts the service on fresh install (mirroring qbittorrent-nox’s behavior); on package upgrade the unit is restarted only if it was already running, so a deliberately-stopped daemon stays stopped.
Fedora / RHEL (.rpm)
curl -LO https://github.com/exec/mosaic/releases/latest/download/Mosaic-vX.Y.Z-linux-amd64-mosaicd.rpm
sudo dnf install ./Mosaic-vX.Y.Z-linux-amd64-mosaicd.rpm
Same layout as the .deb.
Raw tarball (Docker, Alpine, non-systemd)
curl -LO https://github.com/exec/mosaic/releases/latest/download/mosaicd-vX.Y.Z-linux-amd64.tar.gz
tar -xzf mosaicd-vX.Y.Z-linux-amd64.tar.gz
cd mosaicd-vX.Y.Z-linux-amd64
./mosaicd --assets-dir ./dist --data-dir ./data --port 8080
The tarball contains the binary, the SPA bundle (dist/), an example mosaicd.service, and a brief README.txt. No system user is created — adapt to your environment.
Verify the SHA-256 checksum against the release’s SHA256SUMS manifest before running.
First run
The .deb / .rpm postinstall enables and starts the unit automatically. Verify with:
sudo systemctl status mosaicd
Temporary password (qbittorrent-nox style)
Until you set your own password, mosaicd generates a fresh random 256-bit password on every restart and prints it to journald. Find the most recent one:
sudo journalctl -u mosaicd -e | grep -A4 'temporary web-interface password' | tail -8
You’ll see a banner like:
================ mosaicd: temporary web-interface password ================
URL: http://127.0.0.1:8080/
Username: admin
Password: <random base64-url string>
This password is REGENERATED on every restart.
Log in and change it via Settings → Users to make it persist.
===========================================================================
Visit the URL, log in as admin, then immediately go to Settings → Users and set your own password. The first time you save a password through the UI, mosaicd flips the admin account’s password_set flag and stops rotating — your password persists across restarts from that point on. Active sessions for that account are revoked when you change the password, so you’ll need to log back in once.
If you ever forget the password and need to re-enter the recovery flow, stop the daemon, clear the admin account’s flag in the SQLite users table (UPDATE users SET password_set = 0 WHERE id = 1;), and restart — the next boot will print a fresh temporary password again. (A mosaicd reset-password subcommand for this is on the roadmap.)
TLS
A self-signed TLS certificate is generated at /var/lib/mosaic/web-tls/{cert,key}.pem the first time the listener binds to all interfaces. The cert covers localhost, 127.0.0.1, and ::1 only — your browser will warn about the self-signed CA, which is expected. For real production use, put a reverse proxy with a real certificate in front (see below).
Users & permissions
mosaicd is multi-seat. Unlike the desktop app — which has a single login for its optional web server — the daemon hosts multiple application-level accounts, managed from Settings → Users. All accounts share one OS process, one engine, and one save tree; this is an in-process pseudo-permissions model, not OS-level isolation.
The account seeded on first boot (admin, id 1) is the administrator. Admins manage other accounts; the temporary-password recovery flow above always targets this account.
Roles & permissions
Each account has a role — admin or user — which presets a set of toggleable permission flags an admin can override per account:
| Permission | What it grants |
|---|---|
| Add torrents | Add magnets and .torrent files |
| Share torrents | Share owned torrents with other users |
| Manage RSS | Create and edit RSS feeds and filters |
| Manage categories/tags | Edit the shared category and tag sets |
| Change global settings | Bandwidth, connection, schedule and blocklist |
Admins implicitly hold every permission and can manage users. Engine-wide settings, RSS, categories and tags are shared; only torrents are per-user.
Torrent ownership & sharing
A torrent (one infohash) is a single engine entity, but access to it is per-user:
- Whoever adds a torrent owns it. Each user sees only their own torrents plus anything shared with them; admins see everything.
- An owner can share a torrent with another account at one of two levels: viewer (read-only — status, files, peers) or editor (may pause/resume/recheck and change priorities, but not delete or re-share).
- “Removing” a torrent you don’t own just detaches it from your list; the torrent keeps running for everyone else. Only the last remaining owner (or an admin) tears it down from the engine.
API keys are per-account: each user generates and rotates their own from Settings → Users → Your account, and a bearer key authenticates as — and is scoped to — that user.
Configuration
mosaicd exposes a small set of CLI flags. Everything else is a persisted setting that you change from the Settings panel in the web UI; flags exist for first-run bootstrapping and for sysadmin overrides.
| Flag | Default | Notes |
|---|---|---|
--config |
<XDG_CONFIG_HOME>/Mosaic/mosaic.yaml |
Path to the YAML config file. Same format as the GUI’s config. |
--data-dir |
<XDG_DATA_HOME>/Mosaic |
Override the data directory (DB, engine state, web TLS material, credentials). |
--assets-dir |
/usr/share/mosaicd/dist |
Directory containing the SPA bundle (index.html + assets/). |
--port |
(stored) | Override the persisted web-interface port for this run only. |
--bind-all |
(stored) | Force-bind to 0.0.0.0 (HTTPS) for this run only. |
--version |
- | Print version and exit. |
The systemd unit installed by the package passes --data-dir /var/lib/mosaic --assets-dir /usr/share/mosaicd/dist. Edit /etc/systemd/system/mosaicd.service.d/override.conf (sudo systemctl edit mosaicd) to change them; never edit the unit file in /lib/systemd/system/ directly — package upgrades will overwrite it.
The on-disk config file format is shared with the GUI build. See the configuration sections of Installation for the schema.
systemd
# Enable on boot + start now
sudo systemctl enable --now mosaicd
# Check status
sudo systemctl status mosaicd
# Tail logs (structured JSON via zerolog)
sudo journalctl -u mosaicd -f
# Stop / restart
sudo systemctl stop mosaicd
sudo systemctl restart mosaicd
# Disable on boot
sudo systemctl disable mosaicd
The unit ships with sane systemd hardening: NoNewPrivileges, ProtectSystem=strict, ProtectHome=true, PrivateTmp, PrivateDevices, ProtectKernelTunables, ProtectKernelModules, ProtectControlGroups, plus StateDirectory=mosaic and LogsDirectory=mosaic so systemd auto-creates /var/lib/mosaic and /var/log/mosaic with the right ownership. The daemon runs as the mosaic:mosaic user/group, never as root.
Behind a reverse proxy
Production deployments should put a real TLS certificate in front of mosaicd. The recommended pattern: bind mosaicd to loopback only (Settings → Web Interface → “Bind to all” off, which is also the default), then proxy through nginx with a Let’s Encrypt cert.
server {
listen 443 ssl http2;
server_name mosaic.example.com;
ssl_certificate /etc/letsencrypt/live/mosaic.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mosaic.example.com/privkey.pem;
# Mosaic's WebSocket lives at /api/ws — needs Upgrade headers to flow.
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket upgrade (used by /api/ws for live torrent state)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
}
If your reverse proxy is on a different host than mosaicd, you have two options:
- Enable “Bind to all” in
mosaicd’s settings (or pass--bind-all) so the proxy can reach it.mosaicdwill speak HTTPS on0.0.0.0with a self-signed cert; the proxy canproxy_pass https://<mosaicd-host>:8080and useproxy_ssl_verify offsince the upstream cert isn’t trusted by a public CA. This is the only option that protects the link between the proxy andmosaicd. - Place the proxy and
mosaicdon the same host (the more common deployment) and bindmosaicdto loopback. Localhost traffic doesn’t leave the kernel.
Differences from the GUI
| Feature | Desktop (mosaic) |
Daemon (mosaicd) |
|---|---|---|
| BitTorrent engine | yes | yes (identical) |
| HTTPS+WS interface + SPA | yes | yes (identical) |
| REST + WebSocket API | yes | yes (identical) |
| User accounts | single login | multi-user (Settings → Users) |
| System tray | yes | no |
| Desktop notifications | yes | no |
| File associations | yes | no |
| Magnet handler | yes | no |
| Auto-update | yes | no — manage via apt / dnf |
The REST and WebSocket transport — endpoints under /api/*, the /api/ws upgrade, the cookie/bearer auth mechanics — is identical between the two builds, so anything you script against the GUI’s web interface works unchanged against the daemon. The one behavioural difference is the account model: the desktop app’s optional web server has a single login, while mosaicd is multi-user (see Users & permissions above). On the daemon, every request is scoped to the authenticated account, and the extra /api/users, /api/me and /api/torrents/{id}/shares routes are live.
Upgrading
The system package manager is the supported upgrade path:
# Debian / Ubuntu
sudo apt update && sudo apt upgrade mosaicd
# Fedora / RHEL
sudo dnf upgrade mosaicd
Both package managers run the systemd unit’s Restart=on-failure policy and re-read the unit file on daemon-reload, so the daemon picks up the new binary on the next service restart. For zero-downtime upgrades, run sudo systemctl restart mosaicd after the package install completes.
The auto-updater visible in the GUI’s Settings panel is not wired into mosaicd. System services should be upgraded by the system, not by the service itself — running an upgrade from inside a process that systemd is supervising is fragile, and bypasses your package manager’s signature verification.