The board's card width changed at every group boundary because `columns:` was being tuned per group under the 2026-08-18 "columns = member count" rule. That rule is retired: it sets `lg:grid-cols-N` for one group, so it fixes that group's CARD WIDTH, not its density. Measured on the live board, Notes rendered a single 1464px card, News and Media 728px, Eval & Retrieval 286px, everything else 360px. All 20 groups are now `columns: 4` and every card renders at 360. `.service-name` reserved a 78px status gutter with `padding-right` and relied on `overflow: hidden` to hold it, but overflow clips at the PADDING box — the gutter was spill room the title printed straight through. Six cards on the AI tab rendered their name underneath their own status pill, measured by testing the title text node's box against the status cluster's. The intended ellipsis never fired either: it is painted by whichever block's own line overflows, and that is the anonymous box around the bare title text node, which does not carry `overflow`. The gutter holds by wrapping now, and the description opts back out of it with a negative margin since the pill only ever covers the first line. Scriberr's `homepage.group=AI Systems` named a group absent from `layout:`, so it had no `tab:` and rendered on all four tabs — the same defect as UltraSeedbox in 2026-08-18, arriving this time from a container label. Relabelled to `AI - Audio Tools`, where the other ASR seats already live. The `homepage.group=AI Systems` sample in the repo-root CLAUDE.md was the source of the name and now carries the constraint, plus the fact that a labelled container is discovered from any of the five configured engines and must not also be listed in services.yaml. Also: descriptions clamped to three lines so a five-line outlier stops dragging its row 50px taller than its neighbours; icon ramp overridden off slate-400 -> slate-700, which was sinking the bottom half of every glyph into the card fill; bookmark groups and Jellyfin's trailing stream rows brought into the card and eyebrow vocabulary; group gap 10px -> 22px now that width no longer separates them. The icon override sits on `html[class]`, not `:root`: Homepage sets the same variables on `.theme-slate`, which is on the <html> element, and a class beats `:root` on the same element. Verified with Playwright against the live board — per-group card width, card height spread, and a geometric title-vs-status collision check, before and after. Before/after captures: http://10.100.10.50:8090/b/homepage-relayout/
109 lines
5.7 KiB
YAML
109 lines
5.7 KiB
YAML
# Scriberr — self-hosted audio/video transcription with speaker diarization.
|
|
# Upstream: https://github.com/rishikanthc/Scriberr (Go + SvelteKit, SQLite).
|
|
#
|
|
# Transcription runs locally via WhisperX (Whisper + pyannote diarization);
|
|
# NVIDIA Parakeet / Canary models are also selectable in the UI. Optional
|
|
# summarisation / transcript chat talks to any OpenAI-compatible endpoint —
|
|
# point it at the LiteLLM gateway rather than a paid API (see README).
|
|
#
|
|
# ── IMAGE: BUILT LOCALLY, ON PURPOSE ──────────────────────────────────────
|
|
# ana-ml2's RTX PRO 6000 Blackwell cards are **sm_120**. Upstream publishes
|
|
# `scriberr-cuda` (built for sm_61…sm_89 — no sm_120 kernels) and documents a
|
|
# `scriberr-cuda-blackwell` image that **has never actually been published**
|
|
# (GHCR returns no tags for it, checked 2026-08-23). The sm_120 path upstream
|
|
# ships is `Dockerfile.cuda.12.9` (CUDA 12.9.1 + cu128 torch), built locally.
|
|
# Do NOT "simplify" this to the published `scriberr-cuda` image — it will
|
|
# fail on these cards or silently fall back to CPU.
|
|
# Rebuild: see README "Rebuilding" — checkout lives at
|
|
# /tank/scriberr/src/Scriberr on ana-ml2.
|
|
#
|
|
# ── GPU PINNING ───────────────────────────────────────────────────────────
|
|
# Pinned to **GPU1** via explicit device_ids, per the house convention and
|
|
# because GPU0 is fully committed to the `gen` seat. GPU1 shares space with
|
|
# the `sec` seat, so this stack is a guest there — keep an eye on VRAM.
|
|
# NOTE: do NOT add `NVIDIA_VISIBLE_DEVICES=all` (as upstream's compose does).
|
|
# It overrides the device_ids reservation and exposes both cards.
|
|
#
|
|
# All tunables live in .env — edit that, not this file.
|
|
|
|
services:
|
|
scriberr:
|
|
image: ${SCRIBERR_IMAGE:-scriberr:local-blackwell}
|
|
container_name: scriberr
|
|
restart: unless-stopped
|
|
ports:
|
|
- "${SCRIBERR_BIND:-0.0.0.0}:${SCRIBERR_PORT}:8080"
|
|
volumes:
|
|
# Bind mounts rather than named volumes: /var/lib/docker on ana-ml2
|
|
# lives on zroot with limited headroom, while /tank has terabytes.
|
|
# Model weights (Whisper, pyannote, NeMo) land in whisperx-env and are
|
|
# multi-GB — they must not go anywhere near the root pool.
|
|
- ${SCRIBERR_DATA_DIR}:/app/data
|
|
- ${SCRIBERR_ENV_DIR}:/app/whisperx-env
|
|
environment:
|
|
# ⚠ 10001, NOT the fleet-usual 1000 — this is load-bearing.
|
|
# Dockerfile.cuda.12.9 creates `appuser` at uid 10001 (Ubuntu 24.04's
|
|
# base image already owns uid 1000 as `ubuntu`, so upstream moved it) and
|
|
# chowns /app to 10001. The entrypoint's PUID remapping only chowns
|
|
# /app/data + /app/whisperx-env, not /app itself, so running as 1000
|
|
# leaves the app unable to open its SQLite DB and it crash-loops with
|
|
# `unable to open database file: out of memory (14)` — which is
|
|
# SQLITE_CANTOPEN wearing a misleading message, not a real OOM.
|
|
# The host bind-mount dirs are therefore chowned to 10001:10001 too.
|
|
# Verified 2026-08-23: PUID=1000 crash-loops, PUID=10001 starts clean.
|
|
- PUID=${SCRIBERR_PUID:-10001}
|
|
- PGID=${SCRIBERR_PGID:-10001}
|
|
- APP_ENV=production
|
|
# Served over plain HTTP on the LAN. Left at the production default of
|
|
# `true`, the session cookie is marked Secure and the browser silently
|
|
# drops it — you log in, get bounced back to the login page, and the
|
|
# logs show nothing wrong. This must stay false while access is HTTP.
|
|
- SECURE_COOKIES=${SCRIBERR_SECURE_COOKIES:-false}
|
|
# Upstream defaults to localhost origins only, which fails CORS when
|
|
# reached by host IP. Keep this in sync with how the app is reached.
|
|
- ALLOWED_ORIGINS=${SCRIBERR_ALLOWED_ORIGINS}
|
|
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
|
# Scriberr builds each model backend's Python env with `uv` at runtime.
|
|
# uv's default link mode reflink/hardlinks out of its cache, which fails
|
|
# on this overlayfs+ZFS combination with a misleading
|
|
# "Failed to clone ... Resource temporarily unavailable (os error 11)"
|
|
# and takes out the Parakeet + Sortformer backends (WhisperX survives).
|
|
# `copy` trades a little disk and time for it actually working.
|
|
- UV_LINK_MODE=${SCRIBERR_UV_LINK_MODE:-copy}
|
|
deploy:
|
|
resources:
|
|
reservations:
|
|
devices:
|
|
- driver: nvidia
|
|
device_ids: ["${SCRIBERR_GPU_ID:-1}"]
|
|
capabilities: [gpu]
|
|
healthcheck:
|
|
# 127.0.0.1 rather than localhost — the IPv6-first resolution trap has
|
|
# bitten news-digest and chatterbox in this fleet before.
|
|
# start_period is generous: first boot builds a Python env and pulls
|
|
# several GB of model weights before the port answers.
|
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/ >/dev/null || exit 1"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 600s
|
|
networks:
|
|
- tnet
|
|
labels:
|
|
# `AI - Audio Tools`, not `AI Systems`. The group name has to match a key
|
|
# in the dashboard's settings.yaml `layout:` block — a group that appears
|
|
# nowhere in that block gets no `tab:` assignment, and Homepage renders an
|
|
# untabbed group on EVERY tab. `AI Systems` did exactly that from
|
|
# 2026-08-23 until it was caught; transcription belongs with the other ASR
|
|
# seats anyway (Parakeet, Speaches).
|
|
- homepage.group=AI - Audio Tools
|
|
- homepage.name=Scriberr
|
|
- homepage.icon=mdi-microphone-message
|
|
- homepage.description=Audio/video transcription + diarization (ana-ml2, GPU1)
|
|
- homepage.href=http://10.250.50.54:${SCRIBERR_PORT}
|
|
|
|
networks:
|
|
tnet:
|
|
name: traefik-net
|
|
external: true
|