0.16.1 Registry Fix MakeMKV
Deploy Docs to GitHub Pages / Build Documentation (push) Has been cancelled
Deploy Docs to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled

This commit is contained in:
2026-04-29 08:14:06 +00:00
parent d679eface4
commit 6636eb11df
20 changed files with 420 additions and 64 deletions
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ripster-backend",
"version": "0.16.0",
"version": "0.16.1",
"private": true,
"type": "commonjs",
"scripts": {
+6
View File
@@ -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,
+2 -9
View File
@@ -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];
+50
View File
@@ -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) => {
+133
View File
@@ -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
};
+10 -14
View File
@@ -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) {
+42 -25
View File
@@ -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', '<redacted>'] });
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', '<redacted>']
applied: Boolean(registrationKey),
key: registrationKey || null,
path: fileInfo?.path || null,
changed: Boolean(fileInfo?.changed)
};
}