Commit Graph
556 Commits
Author SHA1 Message Date
Frank 39c8135a36 tautulli: restore ContainerName=tautulli (line churn ate it)
Without ContainerName= the v1 generator names the container
systemd-tautulli; the fleet references containers by their plain names
(podman inspect/exec, deployer health checks). Container was healthy but
renamed — caught by post-cutover verification, not by the seam; the seam
only asserts file keys, so pin the name here and keep plex/tautulli
structures parallel.
2026-09-01 22:58:44 -07:00
Frank c694053950 tautulli healthcheck via curl; restore PublishPort; plex gate to 600s
- tautulli.container: HealthCmd switches to the repo's proven curl pattern
  (jellyfin unit) — quadlet's systemd-style value parsing mangles embedded
  double quotes; PublishPort=8181:8181 restored (a mis-aimed line edit
  replaced it; the PublishPort seam test caught the regression)
- deploy_plex readiness gate: 36x5s -> 120x5s; the 2026-09 cutover showed
  the first container boot of a years-old library runs ~10 min while PMS
  pre-binds 32400 and empty-replies; 180s failed the gate twice with a
  healthy, still-booting service
2026-09-01 22:56:19 -07:00
Frank 58aa084d9c quadlet: drop the userns identity model — rootless cannot map uid 997
Evidence from the failed first cutover: podman top shows container root
maps to dank (1000) under UserNS=host rootless (subuid range 100000:65536
cannot express 997), so PUID=997 fell back to root and PMS crash-looped on
write access to its 997:984-owned data root. Fleet model instead: PUID=0/
PGID=0 (container root = dank, same as jellyfin/copyparty data roots) and
deploy_plex chowns /var/lib/plexmediaserver to 1000:1000 inside the
first-cutover block. Zero bytes moved; rollback regains one chown -R
997:984 step, documented in Plex.md. Seam test pins PUID=0/PGID=0 and the
absence of any userns key.
2026-09-01 22:46:00 -07:00
Frank 1939135e4c quadlet: UsernsMode= is compose syntax — use the v1 key UserNS=host
podman 5.7 podman-user-generator on dankserver hard-fails the whole unit on
the unknown key ('unsupported key UsernsMode in group Container'), so the
first cutover attempt masked the native unit and then found no plex.service
at all. Swap both units to UserNS=host (same --userns=host flag), update the
ownership-contract seam test to pin the exact key, and correct both docs.
Pin the seam: the v1 generator rejects unknown Container keys silently at
deploy distance — the test now pins UserNS=, not the misspelled alias.
2026-09-01 22:36:43 -07:00
Frank 592a0baa35 docs: plex and tautulli runtime pages reflect the quadlet migration
- runtime rows: podman quadlet under the systemd user generator, pinned
  image digests, in-place bind mounts, identity model, appnet aliases
- cutover/rollback section on Plex.md (mask-native-unit lever, decommission
  gate); drop the false 'updates are apt-only' and 'snap refresh' gotchas
- update path is now deploy.sh dankserver --service plex|tautulli
2026-09-01 22:31:38 -07:00
Frank eebf2f6c7d dankserver: add plex and tautulli quadlet units
- plex: lscr.io/linuxserver/plex:1.43.3.10896-cb3ebc72d-ls322 (verified ==
  ghcr latest digest; plan's ls9 pin superseded by the re-verify clause),
  /var/lib/plexmediaserver bind-mounted in place as /config, /mnt/entropy
  mounted ro at the same path, PUID=997/PGID=984 + UsernsMode=host so the
  container's file identity is byte-compatible with the native unit
- tautulli: lscr.io/linuxserver/tautulli:v2.18.1-ls242 (verified == ghcr
  latest digest), fresh /home/dank/Containers/tautulli config root,
  PUID=1000/PGID=1000, python3 /home healthcheck
- both on dankserver_appnet with aliases; 32400/8181 published as before
2026-09-01 22:31:26 -07:00
Frank 93f16f68be deployer: route plex and tautulli as dankserver quadlet services
- deploy_plex(): install unit, stop+mask native plexmediaserver.service on
  first cutover (mask, never disable), gate on /identity for 180s
- deploy_tautulli(): install unit, stop+disable snap on first cutover,
  seed config.ini with PMS_Web_Host=plex only if absent
- allow-lists (deploy.sh valid_service, deploy-remote.sh service_is_allowed),
  planner routing (SERVICE_ORDER, DANKSERVER_SERVICES, quadlet_services),
  test-changed cutover case, quadlet seam enumeration + new
  userns/PUID=997/PGID=984 ownership-contract test
2026-09-01 22:31:14 -07:00
Frank 28f8dc2c0d homepage: point Plex link to /web/index.html 2026-09-01 20:45:49 -07:00
Frank 54239294b7 authentik: require flows + application on proxy providers; harden SSO e2e
The loki/grafana/seerr proxy providers were script-provisioned with no
flows and no Application: null flows made the embedded outpost's Rust
deserializer reject the whole config (all guarded vhosts down after any
outpost reload), and the missing apps bounced post-login to authentik's
user UI instead of the app. Backfilled both live; documented the two
provisioning rules and updated the provider inventory. SSO e2e now
asserts the app landing (pathname leaves /if/, no IdP markers in body)
instead of only the hostname.
2026-09-01 18:55:44 -07:00
Frank ffae9b5e21 qBittorrent pinned to 4.6.7; all five download clients live-verified
- qB 5.x's 204 login breaks Readarr's retired client at runtime; 4.6.7 answers
  legacy 200+Ok. Pin recorded in the unit and documented.
- Prowlarr download client created via schema template; all five clients test
  200 through the podman gateway.
- PBKDF2 gotcha: qB 5.x conf rewrite left a hash 4.6.7 rejects; fresh rewrite
  of the same line restored login.
- E2E: torrent through the Panama tunnel at ~17 MB/s, completed into Finished
  Torrents, deleted with data.
2026-09-01 18:10:32 -07:00
Frank a6da2221d7 frankbook-pro: pin qbittorrent to 4.6.7 (Readarr's retired client needs qB 4.x auth) 2026-09-01 18:00:59 -07:00
Frank ef1b077a2c homepage: move ComfyUI tile from user grid to admin panel
ComfyUI is internal-only since the 2026-08-31 vhost decommission; the
status tile belongs with the other admin-category services, not the
user grid.
2026-09-01 17:48:52 -07:00
Frank ea0bba3023 docs(authentik): outpost claim-cache gotcha (proxysession) + Containers data paths 2026-09-01 17:43:15 -07:00
Frank b945dd08cc docs: E2E Suite page + seams-table rows (quadlet, browser e2e) 2026-09-01 17:15:47 -07:00
Frank 00b7eb75d4 docs: Anubis v1.27.0 — domain-wide cookie, persistent key, new gotchas
- Facts table: image pin, COOKIE_DOMAIN, persistent ED25519 key via host
  env file; restarts no longer invalidate solved challenges.
- Gotcha 8: X-Real-Ip mandatory at v1.27+ (500 without; chain sets it).
- Gotcha 9: DENY renders error-styled page at HTTP 200 — probe bodies,
  not statuses.
- Verify live: probe www (apex 308s), version-marker grep, body-based
  deny check. All commands executed live before commit.
2026-09-01 17:13:51 -07:00
Frank 4e1c8a873f e2e: harden akLogin against Lit stage re-render; settle per hop in crawl/sso 2026-09-01 17:09:40 -07:00
Frank 912c0edeff e2e: budget user/admin title asserts to 60s for cold Anubis PoW 2026-09-01 17:09:17 -07:00
Frank 9c593476af anubis: pin v1.27.0, fleet-wide cookie domain, persistent signing key
- Image bump v1.13.0 -> v1.27.0 (deploy_anubis pull+tag updated; JSON
  policy schema unchanged at this release).
- COOKIE_DOMAIN=franksdank.cloud: the challenge cookie now covers all
  guarded *.franksdank.cloud vhosts — solve the PoW once, not per-host.
- ED25519_PRIVATE_KEY_HEX via host env file (0600) with gitea-style
  [Service] EnvironmentFile expansion: restarts no longer invalidate
  everyone's solved challenges.
- Scratch-verified v1.27.0 before pinning: policy loads, challenge/DENY/
  ALLOW all behave, X-Real-Ip now mandatory (chain always sets it).
2026-09-01 17:09:09 -07:00
Frank 7457a9c607 deploy.sh: accept dankclaw:homepage as a valid explicit service (drift vs deploy-plan.py) 2026-09-01 16:43:06 -07:00
Frank dfd9c8d1c0 e2e: Playwright suite for homepage + guard chain; RBAC gate on /admin; fix stale service links 2026-09-01 16:42:33 -07:00
Frank e513007aa4 deploy-remote: seed qB password under WebUI\\Password_PBKDF2 (qB 4.2+ ignores the legacy key) 2026-09-01 16:42:30 -07:00
Frank 94d9d249a1 docs: FrankBook-Pro active-tunnel reality (PA-12, 6881 retirement, qB wiring + save-path convention) 2026-09-01 16:32:16 -07:00
Frank d2ef56442d deploy-remote: strip systemd bullet from frankbook unit resolution 2026-09-01 16:18:23 -07:00
Frank f1171b99cc docs: log entry reflects full port scan of Proton endpoint 2026-09-01 15:59:29 -07:00
Frank 3094ffa746 docs: identify WG endpoint as ProtonVPN Seattle; Mac gate is a second dashboard WG config 2026-09-01 15:58:22 -07:00
Frank 2d06be876f docs: correct Jellyfin admin-password claim (archive OCR corruption); prepare Mac WG peer 2026-09-01 15:53:13 -07:00
Frank 6a67a9067f docs: Jellyfin LDAP-Auth integration (outpost + plugin) and authentik LDAP rules 2026-09-01 15:43:04 -07:00
Frank f4ef2b81ed authentik-outpost-ldap: pin image by amd64 digest
podman 5.7.0 refuses ghcr.io/goauthentik/ldap:2026.8.0's nested manifest
list ('Unexpectedly received a manifest list instead of a manifest for a
single image'); pin the linux/amd64 single-platform digest instead.
2026-09-01 15:27:31 -07:00
Frank 691da478ac deploy-remote: allow authentik-outpost-ldap in service_is_allowed 2026-09-01 15:26:26 -07:00
Frank 4ad85fae70 authentik: add LDAP outpost service for Jellyfin LDAP-Auth
- New authentik-outpost-ldap quadlet: ghcr.io/goauthentik/ldap:2026.8.0,
  publishes host 389/636 -> container 3389/6636 for cross-host binds from
  dankserver's Jellyfin LDAP-Auth plugin; outpost token in host .env.
- deploy-plan.py: register authentik-outpost-ldap in SERVICE_ORDER and
  DANKCLAW_SERVICES; map dankclaw/authentik/quadlet/ unit path.
- deploy-remote.sh: deploy_authentik_outpost_ldap handler + dispatch case.
- deploy.sh: valid-service entry.
2026-09-01 15:25:56 -07:00
Frank 792eb19feb docs: Seerr service page, index entry, and 2026-09-01 work log 2026-09-01 14:47:54 -07:00
Frank f4c9b4f381 seerr: quadlet on dankclaw + guard-chain vhost + deployer wiring
Unit: ghcr.io/seerr-team/seerr:v3.4.1, loopback-only 5055 publish
(homepage probe; users go through Anubis->Authentik->caddy:8017->seerr),
Containers data root, no env secrets. Guard chain: seerr.franksdank.cloud
vhost behind anubis:8923 with post-auth handler on 8017; addn-hosts record;
homepage widget; deploy-remote/deploy-plan/deploy.sh routing; seam test.
2026-09-01 14:40:10 -07:00
Frank b6be6d3da6 docs: FrankBook-Pro page + 2026-09-01 log
Document the Mac *arr/qBittorrent stack (VM placement, gluetun credential
gate, socat LaunchAgents, PBKDF2 qB seed) and log the day: dankserver
recovery + full quadlet cutover of the six media services, the frankbook
skip-propagation/PBKDF2 handler fix, the 7-service frankbook deploy, and
the jellyfin GPU-parity finding.
2026-09-01 14:32:57 -07:00
Frank 1a257c06fe frankbook: propagate skip-state before restart, PBKDF2 qB seed
The frankbook handler ran port verification even for services the
credential gate skipped, so a parked qbittorrent failed the deploy and
aborted the five arrs. Gate now lives Mac-side (test -s gluetun/env
inside the VM) before restart+verify.

The qB first-boot seed wrote a plaintext WebUI\Password, which qB >= 4.2
ignores (PBKDF2 @ByteArray only), and omitted HostHeaderValidation=false,
which would 401 the LAN forwarder path. Seed now generates the PBKDF2
hash in the VM (python3 hashlib) and sets ServerDomains=* +
HostHeaderValidation=false. Arr categories (lidarr/radarr/tv-sonarr)
pre-created with save_path=/Volumes/COMPLETE/Finished Torrents so any
grab lands in the review dir regardless of wiring details.
2026-09-01 14:22:30 -07:00
Frank fe34f04454 docs: record FreeToken compressed-tensors FP8 loader failures; Huihui restored
Ornith per-channel aborts in expert-source resolution (nonstandard
tensor naming); HauhauCS block-fp8 128x128 aborts in _dequant_fp8_weight
(shape mismatch). modelopt block-fp8 is the only FP8 path. Log entry +
runbook quant matrix updated; dead downloads staged to host trash.
2026-09-01 14:11:39 -07:00
Frank 949f68e2b5 frankbook-pro: *arr stack + qbittorrent quadlets inside podman machine
Seven units installed INSIDE the Mac VM (/home/core/.config/containers/
systemd) by a new deploy_frankbook_stack handler: gluetun (ProtonVPN
WireGuard, env-file credentials, publishes qbit's 8080/6881), qbittorrent
(Network=gluetun.container, verbatim /Volumes data roots, first-boot
config seed with fresh machine-local WebUI password and the Finished
Torrents category), and prowlarr/sonarr/radarr/lidarr/readarr (grab-only,
config migrated from dankserver at provisioning, readarr pinned to
amd64-1.0.980). deploy.sh routes the new target through the ssh-config
alias with REMOTE_SHELL=/usr/local/bin/bash (macOS bash 3.2 lacks
associative arrays); the planner learns FRANKBOOK_SERVICES and the
gluetun->qbittorrent netns pairing; new seam test pins the contract.
2026-09-01 14:10:23 -07:00
Frank 4d5bea42e0 frank-default-small: revert to Huihui FP8 — FreeToken v0.1.2 cannot load compressed-tensors FP8
Two live-verified loader failures on Dankserver (2026-09-01), each a
clean exit at load (no swap-thrash, 37 GB fits RAM):

1. ornith-ai/Ornith-1.5-35B-A3B-FP8 (compressed-tensors PER-CHANNEL fp8):
   ValueError: Missing MoE expert source layers: {'gate_up': [0, 1, ...]}
   — nonstandard expert tensor naming, provider finds no expert sources.

2. lamianlbe/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-fp8
   (compressed-tensors BLOCK-fp8 128x128, standard per-expert naming):
   RuntimeError: The size of tensor a (2048) must match the size of
   tensor b (...) in _dequant_fp8_weight — the dequant path multiplies
   weight * weight_scale directly and cannot expand compressed-tensors
   block scales.

Empirical matrix for FreeToken v0.1.2 on qwen3_5_moe (RTX 3090):
  modelopt block-fp8 (quant_method 'fp8' + weight_scale_inv)  -> LOADS
  compressed-tensors per-channel FP8                          -> fails
  compressed-tensors block-fp8 128x128                        -> fails

batsclamp/Huihui-...-abliterated-FP8 is the proven uncensored checkpoint;
restoring it until a modelopt/NVFP4 requant of a wanted model exists or
FreeToken learns compressed-tensors.
2026-09-01 14:02:27 -07:00
Frank 272ef6f0b0 docs: record quadlet standardization of dankserver media + dankclaw data roots
Phase 1/2 of the podman standardization plan: service pages updated to the
quadlet reality (units, Containers data roots, env-file secret convention,
the v1-generator findings each caught live), Storage Policy documents the
two rootless ownership regimes and the merge-move migration, Deployment
Flow's service-manager table no longer claims dankserver media is bare
inventory-only. Bundle change log entry added.
2026-09-01 13:57:33 -07:00
Frank 34ab6b4d0c frank-default-small: retarget to lamianlbe/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-fp8
Ornith-1.5-35B-A3B-FP8 failed to load on FreeToken v0.1.2: its
compressed-tensors checkpoint uses a nonstandard expert tensor layout
(FreeToken: 'Missing MoE expert source layers: gate_up').  Retarget to
the HauhauCS Aggressive FP8 finetune of the SAME Qwen3.6-35B-A3B arch
Huihui used, which carries compressed-tensors BLOCK-fp8 (128x128) with
the standard per-expert naming (mlp.experts.N.gate_proj/up_proj/down_proj
+ .weight_scale) that FreeToken's MoE loader reads.

- MODEL_DIR -> /home/dank/models/hauhaucs-qwen36-35b-a3b-aggressive-fp8
- use the checkpoint's own chat_template.jinja (drop the Sharp override;
  forcing a template the finetune wasn't trained on risks format drift)
- pool plan unchanged (same ~35 GB FP8 mass / qwen3_5_moe arch)
2026-09-01 13:56:22 -07:00
Frank a14a70a404 dankserver: copyparty PublishPort=3923 + seam pinning host-served ports
The cutover unit joined dankserver_appnet but dropped the compose-era
0.0.0.0:3923 mapping (live inspect: bridge, 3923 published), so
copyparty listened inside the netns while the host refused. New seam
asserts every host-facing port matches the pre-cutover PortBindings:
romm 8085->8080, jellyfin 8096, calibre-web 8083, copyparty 3923.
2026-09-01 13:53:10 -07:00
Frank 06de2b4cbf dankserver: jellyfin GPU device via AddDevice=, seam guard for Device=
The podman 5.x v1 quadlet generator rejected the whole jellyfin unit on
the unsupported Device= key (generator log: unsupported key 'Device' in
group 'Container'), so no jellyfin.service was generated and the cutover
restart failed with 'Unit not found' after the data root had already
migrated cleanly. gluetun proves AddDevice= is the house key. New seam
test denies Device= across all six dankserver units and pins the
AddDevice=/dev/dri replacement.
2026-09-01 13:49:45 -07:00
Frank 26b0d4c357 deploy-remote: privileged rename + deep merge in migrate_data_root
rename() of a directory requires write on the directory itself; mariadb/
redis data roots are owned by the containers' mapped subuids (100998),
so the deploy user cannot move them. Use run_privileged mv (numeric
ownership preserved = rootless mapping contract) and skip the chown pass
for romm via the new keep mode — chowning mapped subuid data to the
deploy uid breaks the in-container mysql/redis users. Collisions with
rescued anonymous-volume stubs now merge one level deep instead of
stranding real data under /opt. Reverse the podman rm order so the
dependent romm container is removed before its backends.
2026-09-01 13:42:31 -07:00
Frank a18bb62d30 deploy-remote: ensure quadlet unit dir exists for all handlers
dankserver's ~/.config/containers/systemd did not exist (no quadlet units
had ever been installed there); the dankserver media handlers cp'd into it
without mkdir -p, failing before touching any container.
2026-09-01 13:40:17 -07:00
Frank af6cd72d02 frank-default-small: swap to ornith-ai/Ornith-1.5-35B-A3B-FP8 + Qwen-Sharp chat template
- MODEL_DIR -> /home/dank/models/ornith-1.5-35b-a3b-fp8 (37 GB,
  compressed-tensors per-channel FP8, same qwen3_5_moe arch/mass as
  Huihui so the pool plan carries unchanged)
- CHAT_TEMPLATE_FILE -> scripts/chat-template.jinja (Qwen-Sharp, shared
  with frank-default-large); the checkpoint's stock template
  mis-serializes reasoning per its own model card
- env/serve/unit headers updated; loader-risk + BF16-does-not-fit note
2026-09-01 13:40:12 -07:00
Frank cdfaf48191 deploy-remote: migrate_data_root merges into pre-seeded destinations
mv of a data root onto a non-empty directory fails (Directory not empty).
The romm/copyparty cutover creates Containers/<svc>/ before migrating:
the seed env file lands there first, and the romm anonymous-volume rescue
extracts into it before the /opt move. Merge the old root's entries in
without overwriting existing state (mv -n), warn and leave residue if a
collision kept the new side, and keep the plain rename when the
destination is absent or empty.
2026-09-01 13:38:31 -07:00
Frank 9e904d23a3 deploy-remote: daemon-reload after copying hermes units, before restarts
The first deploy after a unit-file edit restarted with the stale generated
unit (generator had not seen the new Volume= paths), crash-looping hermes on
statfs ENOENT for the just-renamed data root. Reload between cp and restart.
2026-09-01 05:16:27 -07:00
Frank e570d92bb9 dankclaw: standardize gitea/authentik/hermes data roots under /home/dank/Containers
Repoint Volume=/EnvironmentFile= in the 8 affected quadlet units to the
caddy-established Containers/<svc> layout: gitea (data, postgres, env),
authentik (data, postgres), hermes (data, webui checkout). Data-root mv on the
target happens at deploy time before each group's unit restart; same-fs rename,
instantly reversible. Running containers keep their inode-bound binds until the
unit restart flips them to the new path.
2026-09-01 04:57:41 -07:00
Frank b206f477ad dankserver: quadlet units + deploy handlers for romm trio, jellyfin, calibre-web, copyparty
Cutover of the six surviving dankserver services from compose-era bare
containers to boot-persistent Quadlet units (WantedBy=default.target; podman
5.x v1 generator honors it at user-manager start).

Units are faithful to live inspect ground truth (image pins = running tags,
not compose inventory):
- romm trio shares one data root: /home/dank/Containers/romm mounted whole at
  /romm; mariadb datadir and redis data dir live inside it. Aliases mariadb/
  redis preserved on dankserver_appnet via Network=...:alias=. Secrets stay in
  the host-local env file (EnvironmentFile + ${VAR} expansion in the generated
  ExecStart, gitea precedent). copyparty passes its admin account via
  Exec=-a ${COPAY_ACCOUNT}:${COPAY_PASSWORD} (Exec= not Command=: the v1
  generator hard-fails on Command=); entropy stays rw only there.
- jellyfin keeps every creation-time env as Environment= lines (zero drift);
  Device=/dev/dri + GroupAdd=keep-groups preserve hardware transcoding.
- calibre-web pins the exact running image tag (id 986277563e76 =
  0.6.27-ls397), pasta network preserved.

Deployer: first-cutover handlers retire the old container, rescue unshadowed
anonymous-volume content (podman volume export + tar --skip-old-files), mv
data roots on the same fs, then start units in dependency order with health
gates. Re-deploys restart only the selected unit. deploy-plan.py maps compose
keys mariadb/redis to the new unit names; allowlists updated. New seam test
tests/dankserver_quadlet.bats pins boot-persistence + v1-generator contract;
test-changed.sh maps the new dirs to it. Full suite green (209 tests).
2026-09-01 04:14:08 -07:00
Frank 11638442e7 Revert frank-default-small to Huihui FP8: Thomson-1.0-Small is BF16 ~66GB, exceeds Dankserver 62GB RAM (thrash/OOM); needs a quantized checkpoint 2026-09-01 02:53:20 -07:00
Frank 415f64964a frank-default-small: swap Huihui FP8 for thomsonreuters/Thomson-1.0-Small 2026-09-01 02:11:35 -07:00