From 1744d776e29aaf4a3b569efd8a1922e93afe496d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Tue, 5 May 2026 21:10:49 +0200 Subject: [PATCH] sunken-ship: mulbo-server systemd service + pull timer + ZT port 8091 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 5 of the mulbo Navidrome-pivot — companion HTTP service co- located with Navidrome that owns uploads + the dedup index + the real on-disk folder layout (which Navidrome's tag-virtual API can't expose). Wire spec lives in the mulbo repo at 20_mulbo/SERVER_API.md. Runs as `danny` so writes pass through to /home/danny/music/mulbo- uploads via the existing /srv/music ro bind-mount — no mount changes needed. Bound to [::]:8091 (8090 was taken by escape-hormuz on phantom-ship); firewall scopes it to the ZT mesh, same trick bbbot uses on 8080. Pulls the python-projects repo via SSH using sunken-ship's id_ed25519 (registered as a read-only deploy key on the repo). Auto-pull timer runs every 15 min, offset from fitness-bot-pull and dotfiles-rebuild. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 76 ++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index e305c03..663a242 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -95,7 +95,10 @@ networking.firewall = { allowedTCPPorts = [ 7000 7001 7100 4533 ]; allowedUDPPorts = [ 5353 6000 6001 7011 ]; - interfaces."zt+".allowedTCPPorts = [ 8080 ]; + # 8080: bbbot HTTP backend. 8091: mulbo-server companion service. + # Both ZT-only — see vps-relay.nix for reverse proxy if exposing + # publicly later. + interfaces."zt+".allowedTCPPorts = [ 8080 8091 ]; }; # Navidrome — self-hosted music streaming server (Subsonic API). @@ -200,6 +203,77 @@ timerConfig.RandomizedDelaySec = "2min"; }; + # Mulbo companion service (Phase 5: uploads + dedup index + folders). + # Wire spec: ~danny/python-projects/20_mulbo/SERVER_API.md. + # Bootstrap (one-time): git clone git@github.com:DannyDannyDanny/python-projects.git /home/danny/python-projects + # (uses sunken-ship's id_ed25519 as a read-only deploy key on the repo) + # ZT-only via the firewall rule above (port 8091). Runs as `danny` so + # writes go through to /home/danny/music/mulbo-uploads, which Navidrome + # reads via the existing /srv/music ro bind-mount with no mount changes. + systemd.tmpfiles.rules = [ + "d /home/danny/music/mulbo-uploads 0755 danny users -" + ]; + + systemd.services.mulbo-server = let + pythonEnv = pkgs.python312.withPackages (ps: with ps; [ + fastapi + uvicorn + python-multipart + ]); + in { + description = "Mulbo companion service (uploads, dedup, folders)"; + after = [ "network-online.target" "navidrome.service" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + MULBO_UPLOADS_DIR = "/home/danny/music/mulbo-uploads"; + MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db"; + MULBO_NAVIDROME_URL = "http://localhost:4533"; + MULBO_BIND_HOST = "::"; + MULBO_BIND_PORT = "8091"; + PYTHONUNBUFFERED = "1"; # immediate journal output + }; + serviceConfig = { + WorkingDirectory = "/home/danny/python-projects/20_mulbo"; + ExecStart = "${pythonEnv}/bin/python mulbo_server/app.py"; + Restart = "on-failure"; + RestartSec = 5; + User = "danny"; + StateDirectory = "mulbo-server"; # /var/lib/mulbo-server, owned by danny + }; + }; + + # Pull mulbo (python-projects repo) and restart service if repo changed. + # Repo lives at /home/danny/python-projects (must be cloned manually first + # — see bootstrap note above). DBs/state live in /var/lib/mulbo-server, + # not in the repo, so they survive pulls. + systemd.services.mulbo-pull = { + description = "Pull mulbo repo and restart mulbo-server if changed"; + path = with pkgs; [ git systemd ]; + environment = { + GIT_CONFIG_COUNT = "1"; + GIT_CONFIG_KEY_0 = "safe.directory"; + GIT_CONFIG_VALUE_0 = "/home/danny/python-projects"; + }; + script = '' + set -euo pipefail + cd /home/danny/python-projects + git fetch origin + if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then + exit 0 + fi + git pull origin main + systemctl restart mulbo-server + ''; + serviceConfig.Type = "oneshot"; + }; + + systemd.timers.mulbo-pull = { + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "*-*-* *:11/15:00"; # every 15 min, offset from fitness-bot-pull and dotfiles-rebuild + timerConfig.RandomizedDelaySec = "2min"; + }; + # Auto-rebuild service/timer + safe.directory provided by the # shared dotfiles-rebuild NixOS module (see nixos/modules/dotfiles-rebuild.nix). }