Services - Ziad0dev/dots GitHub Wiki
Only Jellyfin and mDNS are admitted on the LAN. Other services use loopback or the VPN namespace; Radarr and Sonarr bind wildcard sockets but the host firewall admits them only from the VPN namespace bridge.
| Service | Address | Reachable from |
|---|---|---|
| Jellyfin |
:8096 http, :8920 https |
LAN (enp5s0) and tailnet (tailscale0) |
| qBittorrent web UI |
:8081 in namespace wg
|
tailnet via port mapping; host at http://192.168.15.1:8081
|
| Prowlarr |
:9696 in namespace wg
|
tailnet via port mapping; host at the namespace address |
| FlareSolverr |
127.0.0.1:8191 in namespace wg
|
Prowlarr only |
| Radarr / Sonarr |
*:7878 / *:8989
|
host; firewall admits the VPN namespace bridge only |
| Audiobookshelf | 127.0.0.1:8000 |
host |
| calibre-web | 127.0.0.1:8083 |
host |
| llama.cpp units | 127.0.0.1:8080 |
host |
| llama-fim | 127.0.0.1:8012 |
host |
| Ollama | 127.0.0.1:11434 |
host |
| MPD |
127.0.0.1:6600 (socket-activated) |
host |
| hello-page |
127.0.0.1:8137 → tailscale serve on 443 |
tailnet (HTTPS) |
| Avahi | :5353/udp |
LAN |
/mnt/media is a 10 TB exFAT drive (modules/media.nix) mounted with the user as owner and gid 3000 (media), dmask=0002 / fmask=0113 so the whole media group can write. It's nofail + x-systemd.automount; every unit that touches it has RequiresMountsFor = /mnt/media, so services wait for the drive instead of writing into an empty mountpoint.
| Service | Notes |
|---|---|
| Jellyfin | NVENC. Decode: h264, hevc, hevc10, vp8, vp9, av1, mpeg2, vc1. Encode: HEVC on, AV1 off. Tone mapping on, transcode throttling on, CRF 21 (h264) / 26 (h265). DeviceAllow adds nvidia0, nvidia-uvm(-tools), nvidia-modeset. forceEncodingConfig = true — encoding settings changed in the web UI are overwritten on restart; change them in Nix |
| Radarr / Sonarr | group media, lib/hardening.nix with ReadWritePaths = /mnt/media
|
| Audiobookshelf | loopback only, group media, hardened |
| calibre-web | loopback only, library at /mnt/media/books, uploads disabled. GOOGLE_BOOKS_API_KEY (optional) comes from secretspec |
| MPD | user service (home/profiles/linux-desktop.nix), music at /mnt/media/music. Two outputs: PipeWire (default) and a bit-perfect ALSA output on hw:CARD=G30 (disabled; switch with mpc enable only 2, back with mpc enable only 1). Clients: rmpc, mpdris2 for media keys |
modules/vpn.nix, using VPN-Confinement.
tailnet 100.64.0.0/10
│ port mappings 8081, 9696
host ─── bridge ────┤
▼
┌─ netns "wg" ── WireGuard (Mullvad) ── internet
│ qbittorrent :8081 torrent port 51413
│ prowlarr :9696
│ flaresolverr 127.0.0.1:8191
└─
- The namespace uses
/etc/wireguard/mullvad.conf(not in the repo). The current SOPS file declares only the Restic password; add the Mullvad secret as described in Secrets, or provision the WireGuard file manually. Until that file exists, the WireGuard namespace services are skipped.wg-dnsruns beforewg.serviceand rewrites itsDNS =line to Mullvad's resolver100.64.0.7so lookups inside the namespace stay in the tunnel. -
accessibleFrom = [ tailnet ]routes replies to tailnet clients back out of the bridge instead of into the tunnel. - A firewall rule drops forwarded traffic to 8081/9696 unless it came in on
tailscale0— the mappings are tailnet-only, not LAN. -
qBittorrent runs as its own system user in group
media, saves to/mnt/media/.incoming, no UPnP, pauses at ratio 2 or after 24 h seeding,MemoryHigh = 2G. Web UI auth is skipped for the namespace subnet (192.168.15.0/24); the host reaches it athttp://192.168.15.1:8081, which is what the bar'sdots-qbtwidget polls. -
Prowlarr is hardened with
lib/hardening.nix. - FlareSolverr gets a browser-friendly variant of the hardening (Chromium needs namespaces and devices) and waits for DNS inside the tunnel before starting.
Check confinement from inside the namespace:
sudo ip netns exec wg curl -s https://am.i.mullvad.net/connectedMullvad for the host itself is separate (modules/mullvad.nix, the GUI and dots-vpn toggle).
modules/llm.nix + modules/ollama.nix. llama.cpp models live in /var/lib/dots/models; Ollama stores its models in /var/lib/ollama.
| Unit | Model | Port | For |
|---|---|---|---|
llama-cpp |
Qwen3.5-9B Q6_K | 8080 | general / coding / maths |
llama-uncensored |
Huihui Qwen3.5-9B abliterated Q6_K | 8080 | uncensored general |
llama-hermes |
Hermes 4 14B IQ4_XS | 8080 | steerable general, 8k context |
llama-sec |
WhiteRabbitNeo V3 7B Q5_K_M | 8080 | security |
llama-agent |
Qwen2.5-Coder-14B-Instruct Q4_K_M | 8080 | tool calling |
llama-gemma |
Gemma 4 12B Q4_K_M | 8080 | general, vision, audio |
llama-coder |
Gemma 4 26B-A4B MoE Q4_K_M | 8080 | coding / agentic — 10 expert layers on CPU, q8_0 KV cache |
llama-fim |
Qwen2.5-Coder-3B Q8_0 | 8012 | llama.vim fill-in-the-middle |
ollama |
/var/lib/ollama |
11434 | trying models quickly |
How they behave:
-
CUDA build of llama.cpp, kernels for the RTX 3060 (sm_86) only. No binary cache carries it, so it builds locally (~14 min) — measured +32% prompt processing over the Vulkan build. All layers on GPU (
-ngl 99), 16k context unless noted, flash attention,--jinjachat templates. -
One at a time. Every llama unit
Conflicts=every other llama unit, so starting one stops the last. Before starting,free-gpuasks Ollama to unload whatever it holds. Ollama keeps at most one model loaded and drops it after 5 minutes idle. -
Never at boot.
wantedByis forced empty on all of them, Ollama included. - No sudo. A polkit rule lets your active local session start and stop these units.
-
Sandboxed. The llama units run as
DynamicUserwithProtectHome,ProtectSystem=strict, no capabilities andMemoryDenyWriteExecute. GGUF files must be world-readable. - llama.cpp's model directory is created for your user. Download or copy GGUF files there; the services do not download them. Ollama creates and manages its own model files.
systemctl start llama-coder # swap models
dots-llm next # cycle, same as the bar widget
dots-llm off
gpu-check # which llama unit is up + ollama ps
gpu-free # stop all llama units, unload Ollama modelsmodules/backup.nix — restic, $HOME, the media apps' state under /var/lib (minus artwork, metadata and logs) and the Secure Boot keys → /mnt/backup/restic-new, daily (persistent, up to 1 h random delay).
- Excludes caches, Steam and other game libraries, replay recordings, flatpak and container storage, Downloads, Trash, and build junk (
node_modules,target,zig-cache,zig-out,.venv,.direnv,__pycache__). - Retention: 7 daily, 4 weekly, 6 monthly. Each run checks a random 5 % of pack data.
- Skips cleanly when the backup drive isn't mounted or writable; runs at nice 19, idle I/O.
- Password at
/etc/restic/password; the unit refuses to start if the file is empty.
sudo systemctl start restic-backups-home
journalctl -u restic-backups-home -e
sudo restic -r /mnt/backup/restic-new -p /etc/restic/password snapshots| Mount | What | Mounted as |
|---|---|---|
/ |
LUKS, ext4 | |
/mnt/media |
exFAT 10 TB | user:media, group-writable, automount, visible in file managers |
/mnt/backup |
Independent exFAT backup drive, configured by its own UUID | root:root 0077, automount (10 min idle), hidden from file managers |
/mnt/pool1 |
ext4 2 TB spare drive | owned by the user, automount (10 min idle), visible in file managers |
exFAT has no permissions, so ownership comes from mount options. udiskie ignores both external exFAT drives by UUID — they belong to systemd — but still automounts real removable media. dots-mounts (the bar's storage alert) reports whether /mnt/media, /mnt/backup and /mnt/pool1 are healthy. If the backup drive is replaced, update its UUID in modules/storage.nix.
Games and wallpaper folders are created in your home directory. Replay captures go to ~/Videos/Replays. llama.cpp's model files belong in /var/lib/dots/models; VM images and installer ISOs use /var/lib/libvirt/images and /var/lib/libvirt/isos. Restore or download those files separately after reinstalling. Ollama manages models under /var/lib/ollama when you start it.
home/flatpak.nix declares remotes, apps and overrides with nix-flatpak; its flatpak-managed-install user unit applies them (user installation) after each switch. Being a home-manager unit, only your user runs it — the SDDM greeter's user manager never sees it.
- Remotes: Flathub and NVIDIA's GeForce NOW repo.
- Apps: GeForce NOW, Foliate, Flatseal, Bottles, F3D, MeshLab, Blender, Simple Scan, Kdenlive, TeXstudio — each pinned to a commit, so a switch installs exactly that build. To bump one,
flatpak remote-info --user --log <origin> <appId>, then paste the commit. Runtimes are whatever the pinned app requires. -
uninstallUnmanaged: it uninstalls any user app that isn't listed — add apps there, not withflatpak install. -
modules/flatpak.nixkeeps onlyflatpak-nvidia-gl, which installsGL.nvidia-<driver>/GL32.nvidia-<driver>for the loaded driver (from/sys/module/nvidia/version; the name carries the version, so it can't be declared). Old extensions are swept by the weeklyflatpak-prune. - GeForce NOW gets
SDL_VIDEODRIVER=x11. The desktop profile exportsSDL_VIDEODRIVER=waylandglobally, flatpak inherits it, and SDL2's Wayland backend can't capture the mouse — aim breaks in-game without the override.
modules/hello-page.nix runs ~/the-page/app.py (system python, as your user, heavily sandboxed, read-write only to that directory) on 127.0.0.1:8137. hello-page-serve waits for tailscaled, then publishes it with tailscale serve --bg --https 443. Environment overrides go in /var/lib/secrets/the-page.env.
-
Docker runs rootless;
DOCKER_HOSTpoints at the user socket. Not started at boot. - Podman is available alongside.
-
libvirt:
qemu:///system, images under/var/lib/libvirt/imagesand installer ISOs under/var/lib/libvirt/isos; virt-manager is preconfigured (autoconnect to system, SPICE, host-passthrough CPU, qcow2). Your user isn't in thelibvirtdgroup, so connecting toqemu:///systemgoes through a polkit password prompt. Restore VM images and ISOs manually after reinstalling.
Source: docs/services.md