Commit graph

747 commits

Author SHA1 Message Date
ef383cb2f0 sunken-ship: bump navidrome 0.61.2 -> 0.62.0 for inPlaylist visibility fix
In navidrome 0.61.x, the inPlaylist/notInPlaylist smart-playlist
criteria SQL builder did not know the smart playlist owner. It only
allowed referencing PUBLIC playlists, regardless of ownership. Per
the docs, an inaccessible reference makes the rule match no tracks,
so notInPlaylist against a private playlist silently degrades to
NOT IN () (always true) - zero filtering.

Symptom: smart playlist `Unrated (de-duped)` returned 9217 tracks
including all members of `[mulbo] dupe-losers` (private, same owner).
GRIVINA "Я хочу" showed 3 copies (1 unique + 2 dupe-losers). Verified
by DB poke: same owner_id, public=0 on both playlists.

Upstream fix: navidrome/navidrome#5411 (deluan) - "Relax playlist
visibility in inPlaylist/notInPlaylist rules". Passes the smart
playlist owner identity into the criteria SQL builder so same-owner
private references work. Shipped in v0.62.0 (2026-06-08).

nixpkgs PR for this bump: NixOS/nixpkgs#529720 (tebriel), opened
2026-06-09, not yet merged. nixos-unstable still on 0.61.2. This
adds a local nixos/pkgs/navidrome/ verbatim from nixpkgs master with
just the 3 hash lines bumped, and wires services.navidrome.package
to it. REMOVE both once nixpkgs-unstable carries 0.62.x.

After deploy: smart playlist songCount 9217 -> 7101, GRIVINA dupes
3 -> 1. Confirmed via direct API fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 18:21:34 +02:00
DannyDannyDanny
0eab0d47ae nixos: add bananasimulator-beta service + vhost
Cheat instance for the bananasim project. Mirrors the production
service on phantom-ship but:
  - own DB at /home/danny/.local/share/bananasimulator-beta/
  - own working dir /home/danny/bananasimulator-beta/
  - port 8084 (added to zt+ firewall allowlist + new vps-relay vhost
    at bananasimulator-beta.dannydannydanny.me)
  - BS_RIPE_MIN_PER_STAGE=0.2 so a banana cycles in ~3 min (testable)
  - BS_BETA_MODE=1 so the server exposes /api/cheat/* + sets beta:true
    in /api/me, which makes the frontend render the 🧪 cheat menu

Same code base; deploy with the same tar-over-ssh ritual into the
sibling dir. apps.json gets a private 'bananasim (beta)' entry that
only my user sees.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 23:25:34 +02:00
DannyDannyDanny
f8a873bd06 nixos: add tdpixi service (port 8093) + vps-relay vhost
Idle Tower Defence Mini App by @plasmagoat forked from
github.com/plasmagoat/TDPixi. Pure static FastAPI serve,
no DB. Proxied at tdpixi.dannydannydanny.me.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:27:32 +02:00
DannyDannyDanny
e2cf93e7d6 feat(foreign-port): add WiFi-only laptop as clan machine
Mirrors the distant-shore pattern: clan-managed (no standalone
flake-module), wired into zerotier/data-mesher/dm-pull-deploy with the
generated vars. WiFi via NetworkManager (PSK from /etc/secrets/nm.env);
locally-signed boot chain (shim chain-loads sbsign-signed systemd-boot
+ kernel, refreshed every nixos-rebuild). targetHost is the LAN IP for
the first push, switch to ZT IPv6 once on the mesh. buildHost =
sunken-ship to avoid self-SSH on the closure copy.
2026-06-07 21:44:14 +02:00
DannyDannyDanny
610454f0d2 fix(distant-shore): drop duplicate standalone flake-module (clan-managed now) 🩹 2026-06-07 20:27:34 +02:00
DannyDannyDanny
0cdb4b8697 fix(distant-shore): build on sunken-ship (avoids self-SSH on closure copy) 🔧 2026-06-07 20:25:09 +02:00
DannyDannyDanny
df18b1cfaf feat(distant-shore): generate clan vars (zerotier/data-mesher/dm-pull-deploy) + ZT host entry 🔐 2026-06-07 20:25:09 +02:00
DannyDannyDanny
bbe05c971d feat(distant-shore): add X13 Gen 2 as clan machine w/ shim+MOK secure boot
ThinkPad X13 Gen 2, BIOS-locked + Secure Boot enforced. Boots NixOS via
Microsoft-signed shim chain-loading MOK-signed systemd-boot + kernel
(re-signed each rebuild). WiFi via NetworkManager. Migrated from the
standalone install module into clan (zerotier/data-mesher/dm-pull-deploy).
2026-06-07 20:25:09 +02:00
09f191d10b feat: add studio.dannydannydanny.me vhost 🎨
Kyranna's private art-learning archive ("Studio"), served by the same
notes service on phantom :8092 (routed by Host header, STUDIO_HOST).
Mirrors the map/kf vhosts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:25:16 +02:00
DannyDannyDanny
05896f6d3b phantom-ship/shipyard: rename poppler_utils → poppler-utils
nixpkgs renamed it; the old attr is now an error alias.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 13:17:33 +02:00
DannyDannyDanny
cc8cc05a08 phantom-ship/shipyard: add media-processing tools for feedback
Feedback now accepts photos, voice notes, video, documents etc. Phase
A captures + stores raw files (Pillow for EXIF strip); Phase B derives
OCR text, speech transcripts, poster frames, PDF text — all via
subprocess so each tool degrades gracefully if absent. Wire the
following into the shipyard service:

  - python3Packages.pillow → EXIF strip on captured photos
  - ffmpeg                 → poster frames + audio→16kHz WAV for whisper
  - tesseract (eng + rus)  → OCR (vyscul writes in Russian)
  - whisper-cpp            → speech-to-text for voice / audio / video
  - poppler_utils          → pdftotext for document attachments

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 13:16:57 +02:00
680c20483c feat: add map.dannydannydanny.me vhost 🗺️
Curated-architecture world map by Kyranna, served by the same notes
service on phantom :8092 (routed by Host header, MAP_HOST). Mirrors the
kf vhost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 13:05:55 +02:00
DannyDannyDanny
592e989b03 fix(home): resurrect process list + track zed settings in dotfiles 🏠
tmux-resurrect only restores programs in its allow-list; nvim/claude/ssh
were missing so restored panes came back as bare fish prompts. Adds the
three programs with argv-aware restart patterns.

Also wires ~/.config/zed/settings.json as an xdg.configFile symlink so
Zed config survives machine rebuilds alongside the rest of dotfiles.
2026-06-05 17:19:38 +02:00
DannyDannyDanny
9283643e07 feat(fish): add gco — smart checkout that cds into worktrees 🌿
If the target branch is already checked out in another worktree,
`gco <branch>` cds there instead of erroring with "already used by
worktree at". Falls through to plain `git checkout` otherwise.
2026-06-05 17:18:57 +02:00
DannyDannyDanny
e43a5eb880 sunken-ship: add ffmpeg to mulbo-server PATH
quality.py's spectral-rolloff probe shells out to ffmpeg to extract
a 30s PCM clip. Without ffmpeg on PATH, subprocess fails silently
and get_or_compute_rolloff returns 0.0 — picker degrades to bitrate
ranking (which is what we were trying to fix). Add ffmpeg via
systemd unit `path = with pkgs; [ ffmpeg ];`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 12:40:45 +02:00
DannyDannyDanny
dc7ef47681 sunken-ship: add numpy to mulbo-server env
For FFT-based spectral-rolloff analysis (quality.py) used by the
chromaprint-dupe winner picker. Effective bitrate alone can't tell
a real lossless file from a re-encoded-128kbps-MP3-saved-as-WAV;
spectral rolloff catches the upsampled fakes (rolloff < 17kHz =
came from lossy source).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 12:35:22 +02:00
DannyDannyDanny
09d25a1899 sunken-ship: add mutagen to mulbo-server env
The /enrich/revert endpoint shipped in 20_mulbo commit 5d4e9466 calls
enrich.write_tags, which imports mutagen. The main mulbo-server's
pythonEnv only had fastapi/uvicorn/python-multipart — first revert
attempt 500'd with "ModuleNotFoundError: No module named 'mutagen'".
(The enrich oneshot has its own env with mutagen; that's why batch
enrichment worked.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:17:05 +02:00
DannyDannyDanny
ba51b6bcf7 tmux: add resurrect + continuum so force-quits don't nuke sessions
Twice in the last few sessions a Love2D force-quit cascaded into
killing the tmux server and losing every window. Resurrect snapshots
windows / panes / cwd / pane contents (with capture-pane-contents on)
to ~/.local/share/tmux/resurrect/last. Continuum auto-saves every 15
min and auto-restores on tmux server start — so the next force-quit
just costs up to 15 min of recent activity, not the whole workspace.

Manual save: prefix + Ctrl-s. Manual restore: prefix + Ctrl-r.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:48:00 +02:00
DannyDannyDanny
b2df891b20 sunken-ship: PurgeMissing = always (valid value; 'missing' was rejected by navidrome 0.61.2)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 10:35:59 +02:00
DannyDannyDanny
8fcb43f279 sunken-ship: navidrome Scanner.PurgeMissing = missing
Stops `missing=1` rows accumulating in media_file. After Phase 7
dedupe, Navidrome's watcher minted ~4k track IDs for files briefly
present in /home/danny/music/.mulbo-quarantine; after rm -rf'ing
the quarantine, those rows stayed flagged-missing forever — and
Substreamer's cached queue then hit 500s on every play attempt
("Internal Server Error: open /srv/music/.mulbo-quarantine/...: no
such file or directory").

Cleaned the 4135 quarantine rows manually via SQL; this config
prevents recurrence. Trade-off: missing rows used to preserve
play-history across "file disappeared, came back" cycles. We prefer
client-cache hygiene.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 10:35:07 +02:00
Danny
1204584ae4 fitness-bot: ExecStartPost runs set-bot-presence.py
Re-publishes the bot's menu button + description on every restart
so @BBBot's chat experience stays in sync with $WEBAPP_URL. Errors
are non-fatal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 12:01:56 +02:00
Danny
cda9c4cf0f sunken-ship: drop python-telegram-bot from fitness-bot pythonEnvs
bot.py was deleted upstream — neither prod nor shipyard launches a
polling bot anymore. server.py only needs python-dotenv + aiohttp.
Also refresh the prod section's comment + service description to
reflect the Mini-App-only architecture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:51:20 +02:00
DannyDannyDanny
3dcbdd408a chore: unpin clan-community now that dm-pull-deploy fix merged 🔧
PR clan/clan-community#25 (machine.name hyphen sanitization) merged
upstream, so swap clan-community.url from the fork branch back to
clan/clan-community/archive/main.tar.gz and update flake.lock to
upstream rev 81e4c9c. Eval confirms byte-identical host closures.

Also finishes the dotfiles-rebuild retirement: phantom-ship.nix still
referenced the now-deleted modules/dotfiles-rebuild.nix in comments.
2026-05-22 21:15:20 +02:00
DannyDannyDanny
b11add8525 Revert "Merge add-catppuccin-forgejo: Catppuccin theme on Forgejo"
This reverts commit 1b0eb5835d, reversing
changes made to 5d4f2048a6.
2026-05-20 20:13:44 +02:00
DannyDannyDanny
9793d5ef7c Revert "phantom-ship/forgejo: switch to catppuccin-mauve-auto (light in light mode)"
This reverts commit cbf0defa34.
2026-05-20 20:13:44 +02:00
DannyDannyDanny
cbf0defa34 phantom-ship/forgejo: switch to catppuccin-mauve-auto (light in light mode)
The catppuccin nix module only generates the static flavor+accent
combinations and sets DEFAULT_THEME to e.g. catppuccin-mocha-mauve.
The auto-switching CSS files (catppuccin-<accent>-auto) ship in the
gitea-theme assets but aren't wired into THEMES.

Override DEFAULT_THEME to catppuccin-mauve-auto so the browser's
prefers-color-scheme decides — latte (light) in light mode, mocha
(dark) in dark mode. Append all auto variants + the four mauve
flavor variants to THEMES so users can still pick from the
appearance settings.
2026-05-20 19:31:22 +02:00
DannyDannyDanny
2e9441f367 Retire dotfiles-rebuild, switch to dm-pull-deploy push timer
- Drop modules/dotfiles-rebuild.nix and its imports in clan.nix;
  sunken-ship + phantom-ship no longer ship the legacy 15-min
  rebuild-from-git timer.
- Add dm-pull-deploy-push systemd timer on sunken-ship: every 15min
  runs dm-send-deploy to announce origin/main rev via data-mesher
  gossip (sunken is the dm-pull-deploy push node).
- Fix mulbo-pull service path: add openssh so 'git fetch' over an
  SSH remote stops failing with 'cannot run ssh'.
- vps-relay authorized_keys: rename Mac key comment to mac-admin,
  add sunken-ship's actual ed25519 key for ZT mesh debugging.
- home.nix: add cinny-desktop (Matrix client).
- neovim: enable cursorline.
2026-05-20 19:31:22 +02:00
DannyDannyDanny
1b0eb5835d Merge add-catppuccin-forgejo: Catppuccin theme on Forgejo 2026-05-20 18:46:57 +02:00
DannyDannyDanny
0c11628f73 phantom-ship: Catppuccin theme for Forgejo (mocha + mauve)
Adds catppuccin flake input and wires its NixOS module into phantom-ship's
imports via clan.nix. Enables catppuccin.forgejo with mocha flavor + mauve
accent on the running Forgejo instance.

Module ref: https://nix.catppuccin.com/options/main/nixos/catppuccin.forgejo/
2026-05-20 18:44:51 +02:00
Hara
5d4f2048a6 hara: heartbeat timer reduced to once daily at 06:07 2026-05-20 15:37:39 +02:00
DannyDannyDanny
0f34d2508d feat: add kf.dannydannydanny.me portfolio vhost
Routes the new subdomain to the existing notes service on
phantom-ship :8092 (Host-header routed). Serves Kyranna Fardi's
architecture portfolio.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:55:58 +02:00
DannyDannyDanny
4fab9a28a2 chore: update flake.lock ⬆️ 2026-05-12 13:57:36 +02:00
DannyDannyDanny
fc9894c32f feat: install zed-editor 2026-05-12 10:13:11 +02:00
DannyDannyDanny
e8158e6c0f monitoring: fix prometheus → alertmanager loopback (IPv4 vs IPv6)
Alertmanager binds [::1]:9093 but Prometheus was dialing
127.0.0.1:9093 — connection refused, so alerts fired internally
but never reached Alertmanager. Switch the target to [::1]:9093
to match the bind.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:47:37 +02:00
DannyDannyDanny
dc7895e3b2 monitoring: bracket IPv6 listenAddress for node_exporter
The NixOS module concatenates listenAddress and port as `${a}:${p}`,
so "::" became ":::9100" and node_exporter rejected it ("too many
colons in address"). Use "[::]" so the result is "[::]:9100".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:17:28 +02:00
DannyDannyDanny
3b6f4545b4 monitoring: prometheus + alertmanager + grafana on sunken-ship
node_exporter on all three hosts (port 9100, ZT-only). Prometheus
server scrapes via the clan ZT IPv6s. Alertmanager routes alerts to
@HarakatBot (chat 66070351); critical repeats every 1h, others 4h.
Starter rule: HostDown when up==0 for 5m. Grafana on :3000 over ZT,
provisioned with the local Prometheus as default datasource.

Manual secrets on sunken-ship: /etc/alertmanager/telegram-token and
/etc/grafana/secret-key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:12:08 +02:00
DannyDannyDanny
40cc62f65b sunken-ship: chromaprint on PATH for mulbo-server-enrich
AcoustID needs fpcalc -plain output (re-fingerprinted on-demand
since tracks_index stores -raw for dedup). chromaprint added
alongside the existing yt-dlp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:02:42 +02:00
Danny
83dd92d738 shipyard staging gets a stable URL: b3.dannydannydanny.me
Drop the cloudflared Quick Tunnel (URL changed on every restart →
unworkable for shipyard's apps.json). Move to the same pattern
every other tenant uses:

- vps-relay Caddy: new virtualHost b3.dannydannydanny.me →
  reverse_proxy to sunken-ship's ZT IPv6 :8081.
- sunken-ship: open port 8081 on the zt+ firewall interface
  (was 8080 + 8091, now 8080 + 8081 + 8091).
- fitness-bot-shipyard service: set WEBAPP_URL=https://b3...
  so start.py skips its own tunnel attempt; drop pkgs.cloudflared
  from path now that nothing in the unit needs it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:00:39 +02:00
Danny
067bab125b sunken-ship: shipyard staging uses shipyard_poc_bot token
shipyard_poc_bot is the shared "POC slot" Telegram bot that hosts
whatever experiment is currently being staged; B3Bot staging is
just the current tenant. Repoint EnvironmentFile and
ConditionPathExists at /home/danny/.secrets/shipyard_poc_bot.env.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:12:09 +02:00
DannyDannyDanny
851ee8ea1d sunken-ship: mulbo-server-enrich oneshot (Phase 7.5)
Companion oneshot for mulbo-server. python312 env adds mutagen
(tag writeback); pkgs.yt-dlp on PATH for SoundCloud lookups.
Same User/SupplementaryGroups/EnvironmentFile/StateDirectory as
mulbo-server-backfill. TimeoutSec=8h covers a full library pass.

Trigger:           sudo systemctl start mulbo-server-enrich
Follow:            journalctl -fu mulbo-server-enrich

Add MULBO_ACOUSTID_KEY to /home/danny/.secrets/mulbo-server-navidrome
to enable the AcoustID source; the yt-dlp + filename sources need
no keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:01:05 +02:00
Danny
fb99ef3cff sunken-ship: add fitness-bot-shipyard staging instance
Mirrors the prod fitness-bot setup but watches origin/staging,
runs in /home/danny/tg_fitness_bot_shipyard, listens on port 8081,
and loads its bot token from
/home/danny/.secrets/bigbiggerbiggestbot-shipyard.env via
EnvironmentFile (separate from prod's secrets file).

ConditionPathExists keeps the service from start-looping until the
secrets file is written. No WEBAPP_URL set, so start.py boots an
ephemeral cloudflared Quick Tunnel; the bot updates its Telegram
menu button to that URL on every start (same as prod was originally).

Pull-timer fires every 15 min on the :13/28/43/58 offset to spread
load against the existing fitness-bot-pull (:07/15) and
mulbo-server-pull (:11/15) timers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:48:26 +02:00
DannyDannyDanny
c5cabe7531 sunken-ship: MULBO_MUSIC_WRITE_ROOT for mulbo-server dedup
/srv/music is RO bind-mount; deletes/quarantines have to go through
the underlying /home/danny/music. New env var separates the read-side
(MUSIC_ROOT, used for hashing) from the write-side (MUSIC_WRITE_ROOT,
used for unlink + move-to-quarantine).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:43:46 +02:00
814993e66b phantom-ship: revert bon to 3B model (7B too slow on CPU)
A/B-tested 7B vs 3B on a real NETTO receipt. 7B took 3.6 min/receipt
vs ~30s for 3B. Accuracy gain was minimal — 7B still picked a line
item ('ARLA SEOMELK 1.') as merchant when the OCR header was missing,
just a different one than 3B picked ('REJESALAT'). The merchant
problem isn't a model-size problem; it's an OCR problem (Tesseract
missed the NETTO logo entirely on this receipt).

Keeping both models in loadModels so we can flip back via env var
without a fresh pull.
2026-05-08 20:39:31 +02:00
ccf9eb2859 phantom-ship: bon switches to qwen2.5:7b-instruct for extraction
3B was making column-parsing mistakes on real receipts (conflating
qty/price, nominating line items as merchant). 7B Q4_K_M is ~3x slower
on phantom-ship CPU (~5min vs ~1.5min per receipt) but materially
better at structured extraction. Background task — speed isn't critical.
Keep 3B in loadModels as a fallback knob (BON_OLLAMA_MODEL env).
2026-05-08 15:28:52 +02:00
DannyDannyDanny
eee28d3e9a phantom-ship + vps-relay: declare notes service + vhosts (port 8092)
notes serves both notes.dannydannydanny.me (blog) and
dannydannydanny.me (apex landing) from the same FastAPI process,
switching on Host header. Source rsync'd from ~/python-projects/26_notes/
to /home/danny/notes/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 07:23:17 +02:00
327bdc11fe phantom-ship: services.ollama + qwen2.5:3b-instruct for bon extraction 2026-05-08 07:23:08 +02:00
647d748d30 phantom-ship: add tesseract to bon service for OCR 2026-05-08 06:57:06 +02:00
DannyDannyDanny
4525e73f1a sunken-ship: mulbo-server-backfill systemd oneshot
Companion oneshot for mulbo-server: populates the dedup index
(tracks_index) from Navidrome's existing 15k tracks. Without it,
GET /tracks/by-hash misses for every existing offshore track and
the upload path duplicates content.

Inherits same User/SupplementaryGroups as the running service.
chromaprint added to PATH for fpcalc. TimeoutSec=8h covers full
274 GB hashing run with headroom.

Triggered manually — not auto-scheduled:
  sudo systemctl start mulbo-server-backfill
  journalctl -fu mulbo-server-backfill

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:30:10 +02:00
082529dac9 phantom-ship + vps-relay: declare bon service + vhost (port 8091)
bon — receipt scanner Mini App. Snap a receipt with the device camera,
upload, list. MVP only captures + stores; OCR/categorization later.

phantom-ship.nix
  - systemd.services.bon on port 8091, binds :: for ZT
  - 8091 added to zt+ allowedTCPPorts
  - tmpfiles for /home/danny/.local/share/bon/{,images}
  - python env adds python-multipart (form upload) + pillow (image
    validate + downscale to 2400px JPEG)

vps-relay.nix
  - Caddy vhost bon.dannydannydanny.me → ZT [::]:8091
2026-05-07 22:12:03 +02:00
DannyDannyDanny
73d4225f9b sunken-ship: grant mulbo-server read on navidrome.db
mulbo-server's /folders endpoint reads navidrome.db directly because
the Subsonic API's path field is tag-virtual (not real fs paths).

Three pieces:
- services.navidrome UMask = 0027 (force) so future DB writes are
  group-readable; default was 0077.
- tmpfiles z-rules to chmod 0640 the existing navidrome.db, -wal, -shm
  (created under the old umask).
- mulbo-server gets SupplementaryGroups=[navidrome] so the unit's
  process can read those files.

Trade-off: couples mulbo-server to Navidrome's schema (specifically
media_file.id + media_file.path). Acceptable given Navidrome 0.61.1
has been stable on these columns; we'll catch breakage at the /health
navidrome_db_readable probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:06:51 +02:00