feat(uhd): KEYDB.cfg-Unterstuetzung - MakeMKVs Schluessel-Kanal liefert nichts mehr
Ampel / ampel (push) Successful in 28s

4K-UHD scheiterte an "The volume key is unknown for this disc". Am 25.07.2026
im Worker-Container nachgemessen: Laufwerk und MakeMKV sind in Ordnung
(LibreDrive v06.3 / Meldung 1011, Disc wird gelesen, AACS-Dump geschrieben) -
MakeMKV versucht gar nicht erst, online einen Schluessel zu holen. Belege:
_private_data.tar enthaelt nur die Index-Datei und KEINE hkd_*.bin; auch mit
geloeschter update.conf (Meldung 5074 belegt den Web-Kontakt) und mit
app_UpdateEnable="1" kam keiner; hkdata.fairuse.org und hkdata.crabdance.com
loesen weltweit nicht mehr auf (NXDOMAIN gegen Fritz!Box, 8.8.8.8, 1.1.1.1).
Betroffen war Akira UHD (MKB v76, Pressung Dez. 2020) - also gerade KEINE
Neuerscheinung. Der bisherige Fehlertext ("Disc neuer als die
Schluessel-Datenbank, mit einem der naechsten Updates rippbar") war falsch.

Einziger heute funktionierender Weg ist eine vom Nutzer selbst mitgebrachte
KEYDB.cfg. Rippy liefert KEINE Schluessel mit, laedt keine herunter und
verteilt keine - es stellt nur den Platz bereit und zeigt an, was dort liegt.

- Datenverzeichnis persistent gemountet (MAKEMKV_DATA_HOST, Default
  /srv/rippy/makemkv): KEYDB.cfg und AACS-Dumps ueberleben jeden Rebuild.
  Vorher loeschte jeder "up -d --build" beides - inklusive des Dumps, auf den
  die Fehlermeldung selbst verwies.
- entrypoint.sh und tasks.py schreiben settings.conf ergaenzend statt
  zerstoerend. Der entrypoint bricht bei nicht beschreibbarem Verzeichnis
  nicht mehr ab - mit "restart: unless-stopped" waere das ein Crashloop
  gewesen, in dem auch reines DVD-Rippen tot ist.
- Neues Zwillings-Modul makemkv_daten.py (docker/api + docker/worker,
  byteweise identisch; test_zwillinge_sind_byteweise_identisch wacht darueber
  und wurde durch absichtliches Verstellen als wirksam nachgewiesen).
- API: GET/POST/DELETE /system/keydb, GET /system/aacs-dumps(/{dateiname}).
  JSON-Body statt Multipart - python-multipart ist bewusst nicht installiert
  und wuerde die API beim Import toeten. nginx client_max_body_size 64m,
  sonst scheitert der Upload mit 413, bevor die API ihn sieht.
- UI (Einstellungen -> System): Status, Hochladen per Datei-Dialog, Entfernen,
  Dump-Download, KEYDB-Plakette je Worker (nur wo das Verzeichnis wirklich
  gemountet ist - ein Remote-Transcode-Worker truege sonst eine Warnung,
  die ihn nichts angeht).
- parse_msg() + log_cb: MakeMKV-Meldungen landen im Rippy-Log (gedrosselt:
  Code 1003 raus, keine Wiederholungen, max. 40 je Rip). Nebenbei behoben:
  der alte Parser (split(",", 4)[3]) schnitt jede Meldung am ersten Komma ab.
- Fuenf Stellen richtiggestellt, die behaupteten, MakeMKV-Updates braechten
  die neueste Disc-Schluessel-Datenbank mit (UI, Anleitung, README,
  Worker-Dockerfile, makemkv_key.py).

NICHT bewiesen: ein erfolgreicher UHD-Rip - es lag keine KEYDB.cfg mit dem
Akira-Schluessel vor. Belegt sind der Befund und die neue Mechanik. So steht
es auch im SAVEPOINT und in der ROADMAP.

Quellen (AGENTS Regel D):
- Datenverzeichnis + Dateiname GROSS/case-sensitiv:
  https://forum.makemkv.com/forum/viewtopic.php?t=30636
- hkd_*.bin in _private_data.tar:
  https://forum.makemkv.com/forum/viewtopic.php?t=32675
- headless settings.conf / app_UpdateEnable:
  https://forum.makemkv.com/forum/viewtopic.php?t=20364
- KEYDB.cfg-Zeilenformat (libaacs):
  https://github.com/ShiftMediaProject/libaacs/blob/master/KEYDB.cfg
- MSG-/PRGV-Format: https://www.makemkv.com/developers/usage.txt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-25 01:26:08 +02:00
parent f74f4e54f6
commit 0935766f61
23 changed files with 1717 additions and 69 deletions
+262 -6
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { Save, Disc, Cpu, Database, Globe, AlertCircle, CheckCircle, HardDrive, Wrench, Send, Tv } from 'lucide-react'
import { useState, useEffect, useRef } from 'react'
import { Save, Disc, Cpu, Database, Globe, AlertCircle, CheckCircle, HardDrive, Wrench, Send, Tv, KeyRound, Upload, Trash2, Download } from 'lucide-react'
import { api } from '../lib/api'
import { useToast } from '../context/ToastContext'
import StorageMounts from '../components/StorageMounts'
@@ -62,7 +62,26 @@ type SettingsTab = 'ripping' | 'verarbeitung' | 'worker' | 'speicherziele' | 'ap
interface WorkerInfo {
name: string
encoders: string[]
info?: { makemkv?: string, handbrake?: string, makemkv_key?: string }
// keydb: "ja" | "nein" | "unbekannt" — sagt, ob DIESER Worker eine KEYDB.cfg
// in seinem MakeMKV-Datenverzeichnis sieht. Nur der Worker, der wirklich
// rippt, zaehlt, deshalb steht die Angabe pro Worker und nicht global.
info?: { makemkv?: string, handbrake?: string, makemkv_key?: string, keydb?: string }
}
// Antwort von GET/POST/DELETE /system/keydb (Feldnamen exakt wie die API sie liefert).
interface KeydbStatus {
vorhanden: boolean
pfad: string
groesse_bytes: number
eintraege: number
geaendert: string
}
// Ein AACS-Dump aus GET /system/aacs-dumps.
interface AacsDump {
name: string
groesse_bytes: number
geaendert: string
}
interface SystemInfo {
@@ -73,6 +92,24 @@ interface SystemInfo {
webhook_gesetzt: boolean
}
// Byte-Zahl menschenlesbar — eine echte KEYDB.cfg ist mehrere MB gross.
function bytesLesbar(bytes: number): string {
if (!bytes) return '0 B'
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`
return `${bytes} B`
}
// Die API liefert ISO-8601 in UTC (oder einen leeren String). Ohne Zonen-Kennung
// wuerde der Browser den Zeitstempel als Ortszeit lesen und die Uhrzeit um den
// Zonen-Versatz verschieben — deshalb notfalls ein Z anhaengen.
function zeitLesbar(iso: string): string {
if (!iso) return 'unbekannt'
const mitZone = /[Zz]$|[+-]\d{2}:?\d{2}$/.test(iso) ? iso : `${iso}Z`
const d = new Date(mitZone)
return isNaN(d.getTime()) ? 'unbekannt' : d.toLocaleString('de-DE')
}
export default function SettingsPage() {
const [settings, setSettings] = useState<SettingsState>(defaultSettings)
const [activeTab, setActiveTab] = useState<SettingsTab>('ripping')
@@ -86,6 +123,13 @@ export default function SettingsPage() {
const [quellenBusy, setQuellenBusy] = useState(false)
const [updates, setUpdates] = useState<Record<string, { installiert?: string, verfuegbar?: string, update?: boolean }> | null>(null)
const [updatesBusy, setUpdatesBusy] = useState(false)
// KEYDB.cfg und AACS-Dumps sind DATEIEN im MakeMKV-Datenverzeichnis, keine
// Einstellungen — deshalb bewusst NICHT in SettingsState: der globale
// Speichern-Knopf postet dieses Objekt komplett und wuerde sie mitschleifen.
const [keydb, setKeydb] = useState<KeydbStatus | null>(null)
const [keydbBusy, setKeydbBusy] = useState(false)
const [dumps, setDumps] = useState<AacsDump[]>([])
const keydbInput = useRef<HTMLInputElement>(null)
const { toast } = useToast()
@@ -106,6 +150,49 @@ export default function SettingsPage() {
}
}
// Status der KEYDB.cfg + Liste der AACS-Dumps frisch holen. Wird beim Laden
// der Seite und nach jeder Aktion aufgerufen — die API ist die Wahrheit,
// nicht das, was wir gerade hochgeschickt haben.
const keydbLaden = () => {
api.get('/system/keydb').then(r => setKeydb(r.data)).catch(() => setKeydb(null))
api.get('/system/aacs-dumps').then(r => setDumps(r.data?.dumps || [])).catch(() => setDumps([]))
// Die Worker-Plaketten oben kommen aus /capabilities und stammen aus dem
// letzten Herzschlag des Workers (minuetlich) — ohne dieses Nachladen
// widerspraeche die Kachel nach einem Upload sichtbar der Erfolgsmeldung.
// Ganz sofort ist sie trotzdem nicht: der Worker meldet erst beim
// naechsten Herzschlag neu. Genau so steht es auch im Erklaertext.
api.get('/capabilities').then(r => setWorkers(r.data.workers || [])).catch(() => setWorkers([]))
}
const keydbHochladen = async (datei: File) => {
setKeydbBusy(true)
try {
// Bewusst als JSON-Text und nicht als Multipart-Upload: der API fehlt
// python-multipart, ein File()-Endpunkt wuerde sie beim Import killen.
const inhalt = await datei.text()
await api.post('/system/keydb', { inhalt })
keydbLaden()
toast('success', 'KEYDB.cfg gespeichert — sie wirkt ab dem nächsten Rip.')
} catch (e: any) {
toast('error', e?.response?.data?.detail || 'Hochladen fehlgeschlagen — ist das wirklich eine KEYDB.cfg, und läuft die API?')
} finally {
setKeydbBusy(false)
}
}
const keydbEntfernen = async () => {
setKeydbBusy(true)
try {
await api.delete('/system/keydb')
keydbLaden()
toast('success', 'KEYDB.cfg entfernt — Rippy nutzt jetzt keine Schlüsseldatei mehr.')
} catch (e: any) {
toast('error', e?.response?.data?.detail || 'Entfernen fehlgeschlagen — läuft die API?')
} finally {
setKeydbBusy(false)
}
}
const quellenPruefen = async () => {
setQuellenBusy(true)
try {
@@ -131,6 +218,12 @@ export default function SettingsPage() {
api.get('/system/info')
.then(r => setSystemInfo(r.data))
.catch(() => setSystemInfo(null))
api.get('/system/keydb')
.then(r => setKeydb(r.data))
.catch(() => setKeydb(null))
api.get('/system/aacs-dumps')
.then(r => setDumps(r.data?.dumps || []))
.catch(() => setDumps([]))
}, [])
const webhookTesten = async () => {
@@ -633,13 +726,40 @@ export default function SettingsPage() {
HandBrake {w.info.handbrake}
</span>
)}
{/* Nur das Verzeichnis des rippenden Workers zaehlt — deshalb je Worker. */}
{w.info?.keydb === 'ja' && (
<span className="text-xs px-2.5 py-0.5 rounded-md font-medium bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border border-emerald-500/30">
KEYDB.cfg vorhanden
</span>
)}
{w.info?.keydb === 'nein' && (
<span className="text-xs px-2.5 py-0.5 rounded-md font-medium bg-amber-500/15 text-amber-600 dark:text-amber-400 border border-amber-500/30">
keine KEYDB.cfg
</span>
)}
{w.info?.keydb === 'unbekannt' && (
<span className="text-xs px-2.5 py-0.5 rounded-md font-medium bg-slate-200 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-300 dark:border-slate-700">
KEYDB.cfg unbekannt
</span>
)}
</div>
))
)}
{/*
Bis 25.07.2026 stand hier, MakeMKV-Updates braechten die neueste
Disc-Schluessel-Datenbank mit. Auf der Rippy-VM nachgemessen: falsch.
MakeMKV hat gar keine mitgelieferte Schluessel-Datenbank, und der
Online-Kanal liefert nichts mehr (die Server hkdata.fairuse.org und
hkdata.crabdance.com loesen weltweit nicht mehr auf). Updates bringen
Laufwerks-Firmware-Unterstuetzung und Fehlerbehebungen — Schluessel
fuer neue UHD-Pressungen kommen ausschliesslich aus der KEYDB.cfg.
*/}
<p className="text-xs mt-3 pt-3 border-t border-slate-200/60 dark:border-slate-800 text-slate-500 dark:text-slate-400">
<strong className="text-slate-700 dark:text-slate-300">MakeMKV</strong> lässt sich aktuell halten
(Update-Check unten; Version über <code>MAKEMKV_VERSION</code> + Rebuild) wichtig wegen der
Disc-Schlüssel-Datenbank.
(Update-Check unten; Version über <code>MAKEMKV_VERSION</code> + Rebuild) neue Versionen bringen
vor allem Laufwerks-Unterstützung und Fehlerbehebungen. <strong>Disc-Schlüssel für neue
4K-UHD-Pressungen kommen dagegen NICHT aus einem Update</strong>, sondern nur aus der KEYDB.cfg
im Block darunter.
{' '}<strong className="text-slate-700 dark:text-slate-300">HandBrake</strong> im eingebauten
Docker-Worker ist bewusst die stabile <strong>Debian-Version</strong> für die Kompression völlig
ausreichend und wird <strong>nicht separat aktualisiert</strong>. Native Worker (Windows) holen
@@ -655,6 +775,140 @@ export default function SettingsPage() {
placeholder="T-… (Forum-Thread „MakeMKV is free while in beta”)"
/>
{/*
MakeMKV-Schluessel (KEYDB.cfg) — Befund vom 25.07.2026 auf der Rippy-VM:
MakeMKV versucht bei einer unbekannten UHD-Pressung gar nicht mehr, online
einen Schluessel zu holen, und die dokumentierten Schluessel-Server sind tot.
Der einzige heute funktionierende Weg ist eine Datei KEYDB.cfg (GROSS
geschrieben, unter Linux case-sensitiv) im MakeMKV-Datenverzeichnis.
Rippy stellt nur den Platz dafuer bereit — mitgeliefert wird nichts.
*/}
<div className="p-4 rounded-xl border border-slate-200/80 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-950/80 space-y-3">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold text-slate-900 dark:text-slate-200 flex items-center gap-2">
<KeyRound size={16} className="text-amber-500" />
MakeMKV-Schlüssel (KEYDB.cfg)
</p>
<div className="flex items-center gap-2">
{/*
Versteckter Datei-Dialog: der Inhalt wird im Browser gelesen und als
JSON gepostet. Kein Multipart — der API fehlt python-multipart.
*/}
<input
type="file"
className="hidden"
ref={keydbInput}
accept=".cfg,text/plain"
onChange={(e) => {
const datei = e.target.files?.[0]
// Wert leeren, damit dieselbe Datei erneut gewählt werden kann.
e.target.value = ''
if (datei) keydbHochladen(datei)
}}
/>
<Button
variant="amber"
size="sm"
onClick={() => keydbInput.current?.click()}
disabled={keydbBusy}
>
<Upload size={14} />
{keydbBusy ? 'Arbeite…' : 'KEYDB.cfg hochladen'}
</Button>
{keydb?.vorhanden && (
<Button variant="danger" size="sm" onClick={keydbEntfernen} disabled={keydbBusy}>
<Trash2 size={14} />
Entfernen
</Button>
)}
</div>
</div>
{keydb?.vorhanden ? (
<div className="p-3 rounded-lg text-sm bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border border-emerald-500/30">
<p className="font-semibold">Eine KEYDB.cfg liegt bereit.</p>
<p className="text-xs mt-1">
{bytesLesbar(keydb.groesse_bytes)} · {keydb.eintraege} Zeilen mit Disc-Kennung ·
zuletzt geändert {zeitLesbar(keydb.geaendert)}
</p>
<p className="text-xs mt-1 font-mono opacity-80">{keydb.pfad}</p>
</div>
) : (
<div className="p-3 rounded-lg text-sm bg-amber-500/15 text-amber-600 dark:text-amber-400 border border-amber-500/30">
<p className="font-semibold">Es liegt keine KEYDB.cfg bereit.</p>
<p className="text-xs mt-1">
DVDs und normale Blu-rays laufen trotzdem. Nur bei 4K-UHD-Discs, deren Schlüssel MakeMKV
nicht kennt, bricht der Rip mit The volume key is unknown for this disc" ab.
</p>
</div>
)}
<p className="text-xs text-slate-500 dark:text-slate-400">
Die <strong className="text-slate-700 dark:text-slate-300">KEYDB.cfg</strong> ist eine reine
Textdatei mit Disc-Schlüsseln für 4K-UHD-Blu-rays. MakeMKV holte solche Schlüssel früher selbst
aus dem Netz — das funktioniert heute nicht mehr, die alten Schlüssel-Server sind abgeschaltet.
Ein MakeMKV-Update hilft dagegen nicht.
</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
<strong className="text-slate-700 dark:text-slate-300">Wichtig:</strong> Rippy liefert keine
Schlüssel mit, lädt keine herunter und verteilt keine. Rippy stellt nur den Platz für eine Datei
bereit, die du selbst mitbringst, und zeigt dir ehrlich an, was dort liegt. Die Zahl oben ist
genau das: die gezählten Zeilen mit einer Disc-Kennung in deiner Datei — keine von MakeMKV
bestätigte Anzahl brauchbarer Schlüssel.
</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
Eine hochgeladene Datei ersetzt die bisherige und wirkt
<strong className="text-slate-700 dark:text-slate-300"> ab dem nächsten Rip</strong> — laufende
Jobs bleiben unberührt, ein Neustart ist nicht nötig. Die Plakette am Worker weiter oben
folgt erst mit dessen nächstem Herzschlag (etwa eine Minute) — dieser Kasten hier ist sofort
aktuell.
</p>
</div>
{/*
AACS-Dumps: MakeMKV legt sie bei einer unbekannten UHD-Disc ab (Meldung 3332).
Seit das Datenverzeichnis persistent gemountet ist, ueberleben sie den
Container-Neustart — vorher waren sie nach jedem Rip weg.
*/}
<div className="p-4 rounded-xl border border-slate-200/80 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-950/80 space-y-3">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold text-slate-900 dark:text-slate-200">AACS-Dumps</p>
<Button variant="secondary" size="sm" onClick={keydbLaden}>
Liste aktualisieren
</Button>
</div>
{dumps.length === 0 ? (
<p className="text-xs text-slate-500 dark:text-slate-400">
Noch kein Dump vorhanden. MakeMKV legt hier automatisch einen ab, sobald eine 4K-UHD-Disc
an einem unbekannten Schlüssel scheitert.
</p>
) : (
<div className="space-y-1.5">
{dumps.map(d => (
<a
key={d.name}
href={`/api/system/aacs-dumps/${encodeURIComponent(d.name)}`}
download
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors text-amber-600 dark:text-amber-300 hover:bg-slate-100 dark:hover:bg-slate-800"
>
<Download size={14} className="flex-shrink-0" />
<span className="truncate min-w-0 font-mono">{d.name}</span>
<span className="ml-auto text-xs flex-shrink-0 text-slate-500 dark:text-slate-400 font-mono">
{bytesLesbar(d.groesse_bytes)} · {zeitLesbar(d.geaendert)}
</span>
</a>
))}
</div>
)}
<p className="text-xs text-slate-500 dark:text-slate-400">
Ein AACS-Dump ist das, was das Laufwerk von der Disc gelesen hat, bevor der Schlüssel fehlte.
Er enthält keinen Schlüssel und nützt dir allein nichts — aber du kannst ihn herunterladen und
im MakeMKV-Forum im Bereich <strong className="text-slate-700 dark:text-slate-300">Ultra HD
Blu-ray</strong> einreichen. Dort können Leute daraus den Schlüssel für deine Pressung ermitteln;
den trägst du dann in deine KEYDB.cfg ein.
</p>
</div>
<div className="p-4 rounded-xl border border-slate-200/80 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-950/80 space-y-3">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold text-slate-900 dark:text-slate-200">Update-Check</p>
@@ -672,7 +926,9 @@ export default function SettingsPage() {
<p className="text-xs mt-1">
⚠️ Update verfügbar — in der .env <code>MAKEMKV_VERSION={updates.makemkv.verfuegbar}</code> setzen,
dann auf der Rippy-Maschine <code>docker compose build worker &amp;&amp; docker compose up -d worker</code>.
Neue Versionen bringen auch die neueste Disc-Schlüssel-Datenbank mit.
{/* 25.07.2026 richtiggestellt: ein Update bringt KEINE Schluessel — die kommen nur aus der KEYDB.cfg. */}
Neue Versionen bringen Laufwerks-Unterstützung und Fehlerbehebungen — aber
<strong> keine Disc-Schlüssel</strong>: die kommen ausschließlich aus deiner KEYDB.cfg.
</p>
)
: <span className="text-xs ml-1 text-emerald-600 dark:text-emerald-400">✓ aktuell</span>}