Commit graph

41 commits

Author SHA1 Message Date
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
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
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
DannyDannyDanny
4debab6f69 sunken-ship: mulbo-server creds via EnvironmentFile + MULBO_MUSIC_ROOT
Adds:
- MULBO_MUSIC_ROOT=/srv/music (for the /folders fs walk)
- EnvironmentFile=/home/danny/.secrets/mulbo-server-navidrome (creds
  for Subsonic API calls — file is mode 600, owned by danny, not in
  source control)

Required for the new /folders endpoint and the upcoming POST /tracks
which needs to call search3.view + startScan.view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:22:37 +02:00
DannyDannyDanny
1744d776e2 sunken-ship: mulbo-server systemd service + pull timer + ZT port 8091
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) <noreply@anthropic.com>
2026-05-05 21:11:05 +02:00
DannyDannyDanny
b8bc17f385 feat(servers): declare SSH authorizedKeys + root mac admin trust 🔑
Move the imperative SSH-key-related scars accumulated during the
clan/VPS rollout into nix config so future installs and rebuilds
reproduce the same state:

- danny@sunken-ship + danny@phantom-ship: trust the mac admin key
  (id_ed25519_<host> on Daniel-Macbook-Air) and the host's own
  self-loopback key (used by clan ssh-ng:// nix-copy-closure back
  to the same host during `clan machines update`).
- root@sunken-ship + root@phantom-ship: trust the mac admin key so
  `clan machines update` can run its SOPS-key upload step that
  SSHes to root@<host> to write /var/lib/sops-nix/key.txt.

Existing key files (~/.ssh/id_ed25519 on each host) stay where they
are; the keypair was generated once during initial bootstrap and the
public side is now declared above. Reinstalls would regenerate and
need the pubkey re-pinned here.
2026-04-25 13:30:40 +02:00
DannyDannyDanny
644420481e fix(sunken-ship): bbbot 8080 only allowed on ZT interface 🔐 2026-04-25 13:26:37 +02:00
DannyDannyDanny
3b5288a48c feat(sunken-ship): bbbot bind dual-stack so VPS Caddy reaches it via ZT IPv6 🪢 2026-04-25 13:17:27 +02:00
DannyDannyDanny
bce34985eb feat(sunken-ship): open firewall :8080 for bbbot via vps-relay 🔓 2026-04-25 13:15:59 +02:00
DannyDannyDanny
914a825587 feat(sunken-ship): trust danny for nix remote builds 🏗️ 2026-04-24 13:47:38 +02:00
DannyDannyDanny
47fc658523 feat(clan): add vps-relay + strip bbbot cloudflared 🚢
Stage 4.5: declare a Hetzner-hosted reverse-proxy VPS as a clan machine.

- nixos/hosts/vps-relay.nix: Debian→NixOS cx23 in hel1. Caddy at public
  80/443 reverse-proxies navidrome.dannydannydanny.me and
  bbbot.dannydannydanny.me over ZT to sunken-ship.
- nixos/disko-cloud.nix: simple GPT + ext4 root, no LUKS — cloud provider
  has physical disk anyway and there's no operator at boot.
- flake-modules/clan.nix: register vps-relay as an inventory machine,
  zerotier peer, internet networking target at its clan-generated ZT
  IPv6, and add vps-relay.clan to clanHostsModule /etc/hosts.
- sunken-ship fitness-bot: drop pkgs.cloudflared from PATH + set
  WEBAPP_URL=https://bbbot.dannydannydanny.me. Paired with the bbbot
  upstream patch (start.py honors env WEBAPP_URL and skips cloudflared
  when set) — once the 15-min fitness-bot-pull timer pulls that change,
  bbbot will stop churning trycloudflare.com URLs.

Vars (zerotier identity/ip + sops machine key) generated on sunken-ship
because clan's hermetic sandbox on macOS fails to run the zerotier
identity generator (same workaround as for data-mesher earlier).

VPS install flow: Hetzner-created Debian box, then `clan machines
install vps-relay --target-host root@<public-ipv4>` reinstalls to
NixOS; subsequent updates go over ZT.
2026-04-24 13:43:21 +02:00
DannyDannyDanny
0cd4947282 feat(sunken-ship): retire Cloudflare Tunnel for navidrome ☁️💥
Stage 4d of the clan migration. Navidrome is now reachable only over
the ZeroTier mesh (port 4533 on sunken-ship's ZT IPv6 address, or via
the sunken-ship-zt SSH alias). Dropped:

- systemd.services.cloudflare-tunnel
- clan.core.vars.generators.cloudflare-tunnel
- cloudflared from environment.systemPackages
- vars/per-machine/sunken-ship/cloudflare-tunnel/

Manual follow-ups still needed on sunken-ship:
- rm /home/danny/.secrets/cloudflare-tunnel-token  (old unmanaged token)
- delete the tunnel itself in the Cloudflare Zero Trust dashboard
- unlink the DNS record music.dannydannydanny.me if it was separate
2026-04-20 10:36:15 +02:00
DannyDannyDanny
7d3fd2d8cf feat(sunken-ship): migrate cloudflare-tunnel-token to clan vars 🔐
Declare a clan.core.vars.generators.cloudflare-tunnel generator that
prompts for the tunnel token on first run and stores it SOPS-encrypted
under vars/per-machine/sunken-ship/cloudflare-tunnel/tunnel-token.
systemd.services.cloudflare-tunnel ExecStart now reads the decrypted
secret at runtime from \${config.clan.core.vars...path} (lives at
/run/secrets/vars/...) instead of the unmanaged
/home/danny/.secrets/cloudflare-tunnel-token file.

Stage 4c of the clan migration. The tunnel itself is slated for
retirement in 4d — ZeroTier-only access after that. Cloudflare token
was rotated during this migration; old value no longer valid.
2026-04-19 21:07:02 +02:00
DannyDannyDanny
88c51399d0 refactor(nix): move flake to repo root 🚚
clan-cli silently ignores the `?dir=` URL parameter when resolving a
flake source, so with the flake at nixos/flake.nix `clan machines
update` fails with "flake.nix does not exist". Move the flake tree up
so the repo root contains flake.nix, flake.lock, flake-modules/, lib/,
modules/, sops/, and vars/. Host-specific NixOS modules stay in
nixos/{hosts,home,fish.nix,neovim.nix,…}; flake-module paths updated
accordingly.

- dotfiles-rebuild flakeRef is now "${dotfilesDir}#<host>" (was
  "${dotfilesDir}/nixos#<host>").
- CLAUDE.md build commands + clan section updated. nixupdate fish alias
  updated. sunken-ship hostsfile comment updated.
- Existing /etc/dotfiles checkouts on the servers will pick up the new
  layout on the next `dotfiles-rebuild` timer tick; the rebuild service
  was pre-updated via rsync so its flakeRef matches before the pull.

Also includes 4b follow-through: zerotier identities are now live on
both servers (sunken-ship=d553a2de33 controller, phantom-ship=6c048abbdc
peer) and IPv6 ping across the ZT mesh works.
2026-04-19 15:19:59 +02:00
DannyDannyDanny
975b2a3ee9 refactor(nix): auto-load flake-modules + extract shared dotfiles-rebuild 🌳
- Add import-tree input; flake.nix now auto-loads every file under
  ./flake-modules so new hosts/features drop in without editing flake.nix.
- Extract the duplicated dotfiles-rebuild service, timer, and
  safe.directory wiring into nixos/modules/dotfiles-rebuild.nix, exposed
  via flake.nixosModules.dotfiles-rebuild.
- sunken-ship and phantom-ship now pull it in from their flake-modules;
  hostname-specific flakeRef is derived from config.networking.hostName.
2026-04-18 18:00:54 +02:00
DannyDannyDanny
5e7b76bdcf fix(servers): declare safe.directory in /etc/gitconfig 🔒
The dotfiles-rebuild service runs as root, but /etc/dotfiles is owned
by `danny`. The GIT_CONFIG_* env vars in the service unit only affect
the git CLI — nix/libgit2 reads safe.directory from /etc/gitconfig.
After a recent nixpkgs bump libgit2 now enforces this strictly, so the
service was failing to evaluate the flake.

Enable programs.git and set programs.git.config.safe.directory =
[ dotfilesDir ] on both sunken-ship and phantom-ship so the trust is
persistent and Nix-managed.
2026-04-18 17:29:11 +02:00
DannyDannyDanny
a36b90e656 fix(sunken-ship): set fsType=none on /srv/music bind mount
nixos-rebuild was failing with "fsType accessed but has no value
defined" on newer nixpkgs. Bind mounts need fsType=none explicitly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:35:27 +02:00
DannyDannyDanny
5db45664ab feat(sunken-ship): auto-pull fitness bot from GitHub every 15 min
New fitness-bot-pull service + timer, modeled on dotfiles-rebuild.
Checks origin/main for new commits, pulls + restarts the service if
the HEAD moved. Offset by 7 min from dotfiles-rebuild to avoid
overlap.

Code now lives at github.com/DannyDannyDanny/bigbiggerbiggestbot
(cloned to /home/danny/tg_fitness_bot). workouts.db is gitignored
so it's preserved across pulls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:33:15 +02:00
DannyDannyDanny
be6dde6f0a feat(sunken-ship): add cloudflare tunnel for external access 🌐
Exposes navidrome via music.dannydannydanny.me.
Bypasses CGNAT — no port forwarding needed.
Token stored outside repo at ~/.secrets/cloudflare-tunnel-token.
2026-04-06 21:19:38 +02:00
DannyDannyDanny
76f63f0ae3 fix(sunken-ship): move navidrome music folder to /srv/music — ProtectHome bypass 🎵 2026-04-06 15:36:07 +02:00
DannyDannyDanny
c31ca7d473 feat(sunken-ship): add navidrome user to users group for music dir access 🎵 2026-04-06 14:46:24 +02:00
DannyDannyDanny
4bccb6e6a8 fix(sunken-ship): add audioconvert to uxplay pipeline — fixes ALAC format error 🎵
feat(home): add uhk-agent for UHK keyboard configuration 🎹
2026-04-06 11:55:06 +02:00
DannyDannyDanny
4d6b64dee9 fix: add nixos-rebuild to dotfiles-rebuild PATH on sunken-ship 🔧 2026-04-04 11:26:09 +02:00
DannyDannyDanny
49165590a6 feat: add fitness bot systemd service to sunken-ship
Code deployed separately via rsync (private repo, not referenced here).
Expects code at /home/danny/tg_fitness_bot/ and token at
~/.secrets/bigbiggerbiggestbot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:15:51 +02:00
DannyDannyDanny
33e2e327b5 fix(nixos): increase UxPlay ALSA buffer to reduce audio chop 🐛
WiFi jitter causes underruns with default buffer. Set buffer-time
to 200ms for smoother playback.
2026-03-31 10:01:22 +02:00
DannyDannyDanny
384b84fec2 fix(nixos): enable nix-ld on sunken-ship for Claude Code 🐛
The Claude Code remote CLI is a dynamically linked binary that
fails on NixOS without a standard ld-linux stub. nix-ld provides it.
2026-03-30 23:52:03 +02:00
DannyDannyDanny
657e250f75 fix(nixos): quote UxPlay alsasink GStreamer pipeline arg 🔧 2026-03-30 16:17:14 +02:00
DannyDannyDanny
cea6913cf3 fix(nixos): route UxPlay audio directly to ALSA on sunken-ship 🔊
Drop PipeWire (WirePlumber fails to detect ALSA cards without a
graphical session). Use GStreamer alsasink with plughw:USB,0 to
output directly to the Scarlett Solo.
2026-03-30 16:16:18 +02:00
DannyDannyDanny
84715596f5 feat(nixos): add PipeWire and fix UxPlay audio on sunken-ship 🔊
Enable PipeWire with ALSA/PulseAudio compat so GStreamer can output
audio. Move UxPlay to a user service with linger so it can reach
PipeWire. Add danny to audio group, add alsa-utils for debugging.
2026-03-30 15:49:35 +02:00
DannyDannyDanny
ee2fa1e5f1 feat(nixos): add UxPlay systemd service on sunken-ship 🔊
Audio-only AirPlay receiver that starts at boot, advertises as
"sunken-ship", and auto-restarts on failure.
2026-03-30 15:44:00 +02:00
DannyDannyDanny
e2b820aac0 feat(nixos): add UxPlay AirPlay receiver to sunken-ship 📡
Enable Avahi for mDNS discovery and open firewall ports for
AirPlay mirroring (TCP 7000-7100, UDP 5353/6000-6001/7011).
2026-03-30 15:38:27 +02:00
DannyDannyDanny
d8e5cbe26a fix(nixos): add safe.directory for dotfiles-rebuild service 🔧
Git refuses to operate on /etc/dotfiles owned by danny when the
service runs as root. Pass safe.directory via environment variables.
2026-03-30 14:20:31 +02:00
DannyDannyDanny
463249961e fix(nixos): replace removed light option and harden char-count script
Restore flake checks by removing deprecated `programs.light` from sunken-ship and switching to brightnessctl guidance. Also clean up flake formatting and make the Raycast char-count script safer for empty input.

Made-with: Cursor
2026-03-24 12:58:40 +01:00
DannyDannyDanny
a114d2fc0c feat(sunken-ship): enable wireless networking support 2026-03-08 12:47:32 +01:00
DannyDannyDanny
424ba82399 feat(sunken-ship): pwless ssh access 🔒 2026-03-08 12:05:25 +01:00
DannyDannyDanny
1338fb1b68 Rename nixos-server to sunken-ship
- Rename hosts/nixos-server.nix -> sunken-ship.nix, nixos-server-hardware.nix -> sunken-ship-hardware.nix
- Flake: nixos-server -> sunken-ship, update module path
- Set networking.hostName = sunken-ship in server configs
- Update AGENTS.md, nixos/readme.md, docs/ssh-and-secrets.md, TODO.md

Made-with: Cursor
2026-03-01 12:44:28 +01:00
Renamed from nixos/hosts/nixos-server.nix (Browse further)