diff --git a/backend/package-lock.json b/backend/package-lock.json index cf7d084..37a3c9f 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-backend", - "version": "0.16.0", + "version": "0.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-backend", - "version": "0.16.0", + "version": "0.16.1", "dependencies": { "archiver": "^7.0.1", "cors": "^2.8.5", diff --git a/backend/package.json b/backend/package.json index af61a52..2733bf1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.16.0", + "version": "0.16.1", "private": true, "type": "commonjs", "scripts": { diff --git a/backend/src/db/database.js b/backend/src/db/database.js index ca910b5..e79958d 100644 --- a/backend/src/db/database.js +++ b/backend/src/db/database.js @@ -831,6 +831,12 @@ async function removeDeprecatedSettings(db) { // Aktualisiert settings_schema-Metadaten (required, description, validation_json) // für bestehende Einträge, ohne user-konfigurierte Werte in settings_values anzutasten. const SETTINGS_SCHEMA_METADATA_UPDATES = [ + { + key: 'makemkv_registration_key', + required: 0, + description: 'Optionaler Registrierungsschlüssel. Wird beim Speichern nach ~/.MakeMKV/settings.conf synchronisiert. Darunter kann der aktuelle Betakey per API übernommen werden.', + validation_json: '{}' + }, { key: 'handbrake_preset_bluray', required: 0, diff --git a/backend/src/plugins/ConverterPlugin.js b/backend/src/plugins/ConverterPlugin.js index c5fe9ec..97cc32b 100644 --- a/backend/src/plugins/ConverterPlugin.js +++ b/backend/src/plugins/ConverterPlugin.js @@ -4,6 +4,7 @@ const fs = require('fs'); const path = require('path'); const { SourcePlugin } = require('./PluginBase'); const { spawnTrackedProcess } = require('../services/processRunner'); +const { syncRegistrationKeyToConfig } = require('../services/makemkvKeyService'); const { parseHandBrakeProgress, parseMakeMkvProgress } = require('../utils/progressParsers'); const { ensureDir, sanitizeFileName, renderTemplate } = require('../utils/files'); @@ -236,15 +237,7 @@ class ConverterPlugin extends SourcePlugin { const settings = await ctx.settings.getSettingsMap(); const makemkvCmd = String(settings?.makemkv_command || 'makemkvcon').trim() || 'makemkvcon'; - const registrationKey = String(settings?.makemkv_registration_key || '').trim() || null; - - if (registrationKey) { - try { - await this._runCaptured(makemkvCmd, ['reg', registrationKey], { jobId: job?.id }); - } catch (_err) { - // Registrierung ist optional - } - } + await syncRegistrationKeyToConfig(settings?.makemkv_registration_key || ''); const args = ['--noscan', '--decrypt', '-r', 'backup', `iso:${inputPath}`, rawJobDir]; diff --git a/backend/src/routes/settingsRoutes.js b/backend/src/routes/settingsRoutes.js index 72f560c..4804637 100644 --- a/backend/src/routes/settingsRoutes.js +++ b/backend/src/routes/settingsRoutes.js @@ -11,6 +11,7 @@ const userPresetService = require('../services/userPresetService'); const activationBytesService = require('../services/activationBytesService'); const diskDetectionService = require('../services/diskDetectionService'); const coverArtRecoveryService = require('../services/coverArtRecoveryService'); +const { fetchCurrentBetaKey } = require('../services/makemkvKeyService'); const logger = require('../services/logger').child('SETTINGS_ROUTE'); const { getDb } = require('../db/database'); @@ -52,6 +53,55 @@ router.get( }) ); +router.get( + '/makemkv/beta-key', + asyncHandler(async (req, res) => { + logger.debug('get:settings:makemkv:beta-key', { reqId: req.reqId }); + const result = await fetchCurrentBetaKey(); + res.json({ + betaKey: result.key, + sourceUrl: result.sourceUrl + }); + }) +); + +router.post( + '/makemkv/beta-key/apply', + asyncHandler(async (req, res) => { + logger.info('post:settings:makemkv:beta-key:apply', { reqId: req.reqId }); + const force = req.body?.force === true; + const currentSettings = await settingsService.getSettingsMap({ forceRefresh: true }); + const existingKey = String(currentSettings?.makemkv_registration_key || '').trim(); + const betaKeyResult = await fetchCurrentBetaKey(); + + if (!force && existingKey && existingKey !== betaKeyResult.key) { + const syncResult = await settingsService.syncMakeMKVRegistrationKeyFromSettings({ + settingsMap: currentSettings + }); + res.json({ + applied: false, + preservedExistingKey: true, + reason: 'existing_key', + betaKey: betaKeyResult.key, + sourceUrl: betaKeyResult.sourceUrl, + settingsFilePath: syncResult?.path || null + }); + return; + } + + const updated = await settingsService.setSettingValue('makemkv_registration_key', betaKeyResult.key); + wsService.broadcast('SETTINGS_UPDATED', updated); + + res.json({ + applied: true, + preservedExistingKey: false, + setting: updated, + betaKey: betaKeyResult.key, + sourceUrl: betaKeyResult.sourceUrl + }); + }) +); + router.get( '/scripts', asyncHandler(async (req, res) => { diff --git a/backend/src/services/makemkvKeyService.js b/backend/src/services/makemkvKeyService.js new file mode 100644 index 0000000..b0e143a --- /dev/null +++ b/backend/src/services/makemkvKeyService.js @@ -0,0 +1,133 @@ +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const logger = require('./logger').child('MAKEMKV_KEY'); + +const MAKEMKV_BETA_KEY_API_URL = 'https://cable.ayra.ch/makemkv/api.php?raw'; +const MAKEMKV_BETA_KEY_API_USER_AGENT = process.env.MAKEMKV_BETA_KEY_API_USER_AGENT + || 'Ripster/1.0 (MakeMKV beta key sync)'; +const MAKEMKV_BETA_KEY_API_FROM = process.env.MAKEMKV_BETA_KEY_API_FROM + || 'ripster@localhost'; + +function normalizeRegistrationKey(rawValue) { + return String(rawValue || '').trim(); +} + +function getMakeMKVConfigDir(homeDir = os.homedir()) { + return path.join(String(homeDir || '').trim() || os.homedir(), '.MakeMKV'); +} + +function getMakeMKVSettingsFilePath(homeDir = os.homedir()) { + return path.join(getMakeMKVConfigDir(homeDir), 'settings.conf'); +} + +function escapeKeyForSettingsFile(value) { + return String(value || '') + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"'); +} + +function buildUpdatedSettingsContent(currentContent, registrationKey) { + const existingLines = String(currentContent || '') + .split(/\r?\n/) + .filter((line) => !/^\s*app_Key\s*=/.test(line)); + const normalizedKey = normalizeRegistrationKey(registrationKey); + + while (existingLines.length > 0 && existingLines[existingLines.length - 1] === '') { + existingLines.pop(); + } + + if (normalizedKey) { + existingLines.push(`app_Key = "${escapeKeyForSettingsFile(normalizedKey)}"`); + } + + return existingLines.length > 0 ? `${existingLines.join('\n')}\n` : ''; +} + +async function syncRegistrationKeyToConfig(rawValue, options = {}) { + const normalizedKey = normalizeRegistrationKey(rawValue); + const homeDir = String(options?.homeDir || '').trim() || os.homedir(); + const configDir = getMakeMKVConfigDir(homeDir); + const settingsFilePath = getMakeMKVSettingsFilePath(homeDir); + const fileExists = fs.existsSync(settingsFilePath); + const currentContent = fileExists + ? await fs.promises.readFile(settingsFilePath, 'utf8') + : ''; + const nextContent = buildUpdatedSettingsContent(currentContent, normalizedKey); + + if (!normalizedKey && !fileExists) { + return { + changed: false, + path: settingsFilePath, + hasKey: false + }; + } + + await fs.promises.mkdir(configDir, { recursive: true }); + + if (nextContent) { + await fs.promises.writeFile(settingsFilePath, nextContent, 'utf8'); + await fs.promises.chmod(settingsFilePath, 0o600).catch(() => {}); + logger.info('settings-conf:key-synced', { + path: settingsFilePath, + hasKey: true + }); + } else { + await fs.promises.writeFile(settingsFilePath, '', 'utf8'); + await fs.promises.chmod(settingsFilePath, 0o600).catch(() => {}); + logger.info('settings-conf:key-cleared', { + path: settingsFilePath, + hasKey: false + }); + } + + return { + changed: currentContent !== nextContent, + path: settingsFilePath, + hasKey: Boolean(normalizedKey) + }; +} + +async function fetchCurrentBetaKey() { + const response = await fetch(MAKEMKV_BETA_KEY_API_URL, { + headers: { + 'User-Agent': MAKEMKV_BETA_KEY_API_USER_AGENT, + 'From': MAKEMKV_BETA_KEY_API_FROM, + 'Accept': 'text/plain, text/html;q=0.9, */*;q=0.8', + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache' + } + }); + + if (!response.ok) { + const error = new Error(`Betakey konnte nicht geladen werden (HTTP ${response.status}).`); + error.statusCode = 502; + throw error; + } + + const rawBody = await response.text(); + const betaKey = String(rawBody || '') + .split(/\r?\n/) + .map((line) => line.trim()) + .find(Boolean) || ''; + + if (!/^[A-Za-z0-9][A-Za-z0-9-]{15,}$/.test(betaKey)) { + const error = new Error('Betakey-Antwort ist ungültig.'); + error.statusCode = 502; + throw error; + } + + return { + key: betaKey, + sourceUrl: MAKEMKV_BETA_KEY_API_URL + }; +} + +module.exports = { + MAKEMKV_BETA_KEY_API_URL, + fetchCurrentBetaKey, + getMakeMKVConfigDir, + getMakeMKVSettingsFilePath, + normalizeRegistrationKey, + syncRegistrationKeyToConfig +}; diff --git a/backend/src/services/pipelineService.js b/backend/src/services/pipelineService.js index 2f6a30d..6bb573a 100644 --- a/backend/src/services/pipelineService.js +++ b/backend/src/services/pipelineService.js @@ -16207,27 +16207,23 @@ class PipelineService extends EventEmitter { } async ensureMakeMKVRegistration(jobId, stage) { - const registrationConfig = await settingsService.buildMakeMKVRegisterConfig(); - if (!registrationConfig) { - return { applied: false, reason: 'not_configured' }; + const syncResult = await settingsService.syncMakeMKVRegistrationKeyFromSettings(); + if (!syncResult?.applied) { + return { applied: false, reason: 'not_configured', path: syncResult?.path || null }; } await historyService.appendLog( jobId, 'SYSTEM', - 'Setze MakeMKV-Registrierungsschlüssel aus den Settings (makemkvcon reg).' + 'Synchronisiere MakeMKV-Key aus den Settings nach ~/.MakeMKV/settings.conf.' ); - await this.runCommand({ - jobId, - stage, - source: 'MAKEMKV_REG', - cmd: registrationConfig.cmd, - args: registrationConfig.args, - argsForLog: registrationConfig.argsForLog - }); - - return { applied: true }; + return { + applied: true, + path: syncResult?.path || null, + changed: Boolean(syncResult?.changed), + stage + }; } isReviewRefreshSettingKey(key) { diff --git a/backend/src/services/settingsService.js b/backend/src/services/settingsService.js index fbe6167..4199aea 100644 --- a/backend/src/services/settingsService.js +++ b/backend/src/services/settingsService.js @@ -14,6 +14,10 @@ const { } = require('../utils/validators'); const { splitArgs } = require('../utils/commandLine'); const { setLogRootDir } = require('./logPathService'); +const { + syncRegistrationKeyToConfig, + normalizeRegistrationKey +} = require('./makemkvKeyService'); const { defaultRawDir: DEFAULT_RAW_DIR, @@ -75,6 +79,7 @@ const SERVER_CONSOLE_LOG_SETTING_KEYS = new Set([ SERVER_CONSOLE_LOG_WARN_ENABLED_KEY, SERVER_CONSOLE_LOG_ERROR_ENABLED_KEY ]); +const MAKEMKV_REGISTRATION_KEY_SETTING_KEY = 'makemkv_registration_key'; const MEDIA_PROFILES = ['bluray', 'dvd', 'cd', 'audiobook']; const PROFILED_SETTINGS = { raw_dir: { @@ -1056,25 +1061,36 @@ class SettingsService { } const serializedValue = serializeValueByType(schema.type, result.normalized); + const normalizedKey = String(key || '').trim().toLowerCase(); - await db.run( - ` - INSERT INTO settings_values (key, value, updated_at) - VALUES (?, ?, CURRENT_TIMESTAMP) - ON CONFLICT(key) DO UPDATE SET - value = excluded.value, - updated_at = CURRENT_TIMESTAMP - `, - [key, serializedValue] - ); + try { + await db.exec('BEGIN'); + await db.run( + ` + INSERT INTO settings_values (key, value, updated_at) + VALUES (?, ?, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = excluded.value, + updated_at = CURRENT_TIMESTAMP + `, + [key, serializedValue] + ); + if (normalizedKey === MAKEMKV_REGISTRATION_KEY_SETTING_KEY) { + await syncRegistrationKeyToConfig(result.normalized); + } + await db.exec('COMMIT'); + } catch (error) { + await db.exec('ROLLBACK'); + throw error; + } logger.info('setting:updated', { key, value: SENSITIVE_SETTING_KEYS.has(String(key || '').trim().toLowerCase()) ? '[redacted]' : result.normalized }); - if (String(key || '').trim().toLowerCase() === LOG_DIR_SETTING_KEY) { + if (normalizedKey === LOG_DIR_SETTING_KEY) { applyRuntimeLogDirSetting(result.normalized); } - if (SERVER_CONSOLE_LOG_SETTING_KEYS.has(String(key || '').trim().toLowerCase())) { + if (SERVER_CONSOLE_LOG_SETTING_KEYS.has(normalizedKey)) { const map = await this.getSettingsMap({ forceRefresh: true }); applyRuntimeServerConsoleLoggingSettingsFromMap(map); } @@ -1159,6 +1175,12 @@ class SettingsService { [item.key, item.serializedValue] ); } + const makemkvKeyChange = normalizedEntries.find( + (item) => String(item?.key || '').trim().toLowerCase() === MAKEMKV_REGISTRATION_KEY_SETTING_KEY + ); + if (makemkvKeyChange) { + await syncRegistrationKeyToConfig(makemkvKeyChange.value); + } await db.exec('COMMIT'); } catch (error) { await db.exec('ROLLBACK'); @@ -1317,20 +1339,15 @@ class SettingsService { return { cmd, args: [...baseArgs, ...extra] }; } - async buildMakeMKVRegisterConfig() { - const map = await this.getSettingsMap(); - const registrationKey = String(map.makemkv_registration_key || '').trim(); - if (!registrationKey) { - return null; - } - - const cmd = map.makemkv_command || 'makemkvcon'; - const args = ['reg', registrationKey]; - logger.debug('cli:makemkv:register', { cmd, args: ['reg', ''] }); + async syncMakeMKVRegistrationKeyFromSettings(options = {}) { + const map = options?.settingsMap || await this.getSettingsMap(); + const registrationKey = normalizeRegistrationKey(map?.makemkv_registration_key); + const fileInfo = await syncRegistrationKeyToConfig(registrationKey, options); return { - cmd, - args, - argsForLog: ['reg', ''] + applied: Boolean(registrationKey), + key: registrationKey || null, + path: fileInfo?.path || null, + changed: Boolean(fileInfo?.changed) }; } diff --git a/db/schema.sql b/db/schema.sql index caa2f0c..56dbb12 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -397,7 +397,7 @@ VALUES ('makemkv_command', 'Tools', 'MakeMKV Kommando', 'string', 1, 'Pfad oder INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_command', 'makemkvcon'); INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) -VALUES ('makemkv_registration_key', 'Tools', 'MakeMKV Key', 'string', 0, 'Optionaler Registrierungsschlüssel. Wird vor Analyze/Rip automatisch per "makemkvcon reg" gesetzt.', NULL, '[]', '{}', 202); +VALUES ('makemkv_registration_key', 'Tools', 'MakeMKV Key', 'string', 0, 'Optionaler Registrierungsschlüssel. Wird beim Speichern nach ~/.MakeMKV/settings.conf synchronisiert. Darunter kann der aktuelle Betakey per API übernommen werden.', NULL, '[]', '{}', 202); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_registration_key', NULL); INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) diff --git a/docs/architecture/backend.md b/docs/architecture/backend.md index 67c5ec1..f6c1d9a 100644 --- a/docs/architecture/backend.md +++ b/docs/architecture/backend.md @@ -98,7 +98,7 @@ Features: - profilspezifische Auflösung (`resolveEffectiveToolSettings`) - CLI-Config-Building für MakeMKV/HandBrake/MediaInfo/FFmpeg - HandBrake-Preset-Liste via `HandBrakeCLI -z` -- MakeMKV-Registration-Command aus `makemkv_registration_key` +- MakeMKV-Key-Synchronisation aus `makemkv_registration_key` nach `~/.MakeMKV/settings.conf` - Pfadauflösung für alle Medienprofile inkl. Audiobook und Converter --- diff --git a/docs/configuration/settings-reference.md b/docs/configuration/settings-reference.md index 32040a2..3516058 100644 --- a/docs/configuration/settings-reference.md +++ b/docs/configuration/settings-reference.md @@ -209,7 +209,7 @@ Typische Auswirkungen: | GUI-Feld | Key | Typ | Default | Wirkung im System | |---|---|---|---|---| | `MakeMKV Kommando` | `makemkv_command` | `string` | `makemkvcon` | Pfad oder Befehl für makemkvcon. | -| `MakeMKV Key` | `makemkv_registration_key` | `string` | `leer` | Optionaler Registrierungsschlüssel. Wird vor Analyze/Rip automatisch per "makemkvcon reg" gesetzt. | +| `MakeMKV Key` | `makemkv_registration_key` | `string` | `leer` | Optionaler Registrierungsschlüssel. Wird beim Speichern nach `~/.MakeMKV/settings.conf` synchronisiert. In der UI kann darunter der aktuelle Betakey geladen und übernommen werden. | | `Mediainfo Kommando` | `mediainfo_command` | `string` | `mediainfo` | Pfad oder Befehl für mediainfo. | | `Minimale Titellänge (Minuten)` | `makemkv_min_length_minutes` | `number` | `60` | Filtert kurze Titel beim Rip. | | `HandBrake Kommando` | `handbrake_command` | `string` | `HandBrakeCLI` | Pfad oder Befehl für HandBrakeCLI. | diff --git a/docs/tools/makemkv.md b/docs/tools/makemkv.md index 41ddf46..1d74ee9 100644 --- a/docs/tools/makemkv.md +++ b/docs/tools/makemkv.md @@ -34,12 +34,20 @@ makemkvcon backup --decrypt ## Registrierungsschlüssel (optional) -Wenn in `Settings` ein `MakeMKV Key` gesetzt ist, führt Ripster vor Analyse/Rip aus: +Wenn in `Settings` ein `MakeMKV Key` gesetzt ist, synchronisiert Ripster ihn nach: ```bash -makemkvcon reg +~/.MakeMKV/settings.conf ``` +Format: + +```ini +app_Key = "product-key" +``` + +Zusätzlich zeigt die UI unterhalb des Feldes den aktuellen Betakey an, der per Button in das Eingabefeld übernommen werden kann. + --- ## Relevante Felder in `Settings` diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 75495f1..dcae804 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.16.0", + "version": "0.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.16.0", + "version": "0.16.1", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index 31b7d23..93a6dfa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.16.0", + "version": "0.16.1", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 8308582..ef16e02 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -376,6 +376,20 @@ export const api = { }); return normalizeHandBrakePresetPayload(result); }, + getMakeMKVBetaKey(options = {}) { + return requestCachedGet('/settings/makemkv/beta-key', { + ttlMs: 10 * 60 * 1000, + forceRefresh: options.forceRefresh + }); + }, + async applyMakeMKVBetaKey(payload = {}) { + const result = await request('/settings/makemkv/beta-key/apply', { + method: 'POST', + body: JSON.stringify(payload || {}) + }); + afterMutationInvalidate(['/settings/makemkv/beta-key', '/settings']); + return result; + }, getScripts(options = {}) { return requestCachedGet('/settings/scripts', { ttlMs: 2 * 60 * 1000, diff --git a/frontend/src/components/DynamicSettingsForm.jsx b/frontend/src/components/DynamicSettingsForm.jsx index 59fcfc8..64bfe6e 100644 --- a/frontend/src/components/DynamicSettingsForm.jsx +++ b/frontend/src/components/DynamicSettingsForm.jsx @@ -655,6 +655,67 @@ function isReadonlySetting(setting) { } } +function MakeMKVBetaKeyHint({ onApply, disabled = false }) { + const [loading, setLoading] = useState(true); + const [betaKey, setBetaKey] = useState(''); + const [error, setError] = useState(''); + + useEffect(() => { + let active = true; + setLoading(true); + setError(''); + + api.getMakeMKVBetaKey({ forceRefresh: true }) + .then((response) => { + if (!active) { + return; + } + setBetaKey(String(response?.betaKey || '').trim()); + }) + .catch((loadError) => { + if (!active) { + return; + } + setBetaKey(''); + setError(loadError?.message || 'Betakey konnte nicht geladen werden.'); + }) + .finally(() => { + if (active) { + setLoading(false); + } + }); + + return () => { + active = false; + }; + }, []); + + return ( +
+
+ Betakey +
+ {loading ? ( + Aktueller Betakey wird geladen… + ) : null} + {!loading && error ? ( + {error} + ) : null} + {!loading && !error && betaKey ? ( + {betaKey} + ) : null} +
+ ); +} + function SettingField({ setting, value, @@ -782,6 +843,12 @@ function SettingField({ ) : null} + {normalizeSettingKey(setting?.key) === 'makemkv_registration_key' ? ( + onChange?.(setting.key, nextKey)} + /> + ) : null} {error ? ( {error} ) : ( diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index e0a9a5a..518206b 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -2046,6 +2046,43 @@ body { line-height: 1.35; } +.makemkv-beta-key-box { + display: grid; + gap: 0.35rem; + margin-top: 0.15rem; + padding: 0.55rem 0.65rem; + border: 1px dashed rgba(111, 57, 34, 0.32); + border-radius: 0.45rem; + background: rgba(255, 250, 240, 0.72); +} + +.makemkv-beta-key-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; +} + +.makemkv-beta-key-title { + font-size: 0.8rem; + font-weight: 700; + color: var(--rip-brown-800); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.makemkv-beta-key-value { + display: block; + padding: 0.45rem 0.5rem; + border-radius: 0.35rem; + background: rgba(58, 29, 18, 0.06); + color: var(--rip-brown-900); + font-size: 0.82rem; + line-height: 1.35; + overflow-wrap: anywhere; + word-break: break-word; +} + .notification-toggle-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); diff --git a/install.sh b/install.sh index f6ac0ff..03e4e69 100644 --- a/install.sh +++ b/install.sh @@ -440,8 +440,41 @@ install_makemkv() { cd / rm -rf "$tmp_dir" ok "MakeMKV $makemkv_version installiert" - warn "Hinweis: MakeMKV benötigt eine Lizenz oder den Beta-Key." - warn "Beta-Key: https://www.makemkv.com/forum/viewtopic.php?t=1053" + info "Der MakeMKV-Betakey wird nach dem Backend-Start automatisch per API übernommen, sofern noch kein eigener Key gespeichert ist." +} + +sync_makemkv_beta_key() { + header "MakeMKV Betakey synchronisieren" + + local api_url="http://127.0.0.1:${BACKEND_PORT}/settings/makemkv/beta-key/apply" + local response="" + + if ! response=$(curl -fsS \ + -X POST \ + -H 'Content-Type: application/json' \ + --data '{}' \ + "$api_url" 2>/dev/null); then + warn "MakeMKV-Betakey konnte nicht automatisch übernommen werden." + warn "Backend-Endpunkt fehlgeschlagen: $api_url" + return 0 + fi + + local applied preserved settings_file + applied=$(printf '%s' "$response" | jq -r '.applied // false' 2>/dev/null || echo "false") + preserved=$(printf '%s' "$response" | jq -r '.preservedExistingKey // false' 2>/dev/null || echo "false") + settings_file=$(printf '%s' "$response" | jq -r '.settingsFilePath // empty' 2>/dev/null || true) + + if [[ "$applied" == "true" ]]; then + ok "MakeMKV-Betakey automatisch gespeichert${settings_file:+ ($settings_file)}" + return 0 + fi + + if [[ "$preserved" == "true" ]]; then + ok "Vorhandener MakeMKV-Key bleibt erhalten${settings_file:+ ($settings_file)}" + return 0 + fi + + warn "MakeMKV-Betakey wurde nicht übernommen." } select_handbrake_mode() { @@ -987,6 +1020,8 @@ else exit 1 fi +sync_makemkv_beta_key + if [[ "$SKIP_NGINX" == false ]]; then systemctl enable nginx systemctl restart nginx diff --git a/package-lock.json b/package-lock.json index c09e9fd..1ab5fb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster", - "version": "0.16.0", + "version": "0.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster", - "version": "0.16.0", + "version": "0.16.1", "devDependencies": { "concurrently": "^9.1.2" } diff --git a/package.json b/package.json index f1a19b9..30971e1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ripster", "private": true, - "version": "0.16.0", + "version": "0.16.1", "scripts": { "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev:backend": "npm run dev --prefix backend",