diff --git a/backend/package-lock.json b/backend/package-lock.json index 37a3c9f..287a6fa 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-backend", - "version": "0.16.1", + "version": "0.16.1-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-backend", - "version": "0.16.1", + "version": "0.16.1-1", "dependencies": { "archiver": "^7.0.1", "cors": "^2.8.5", diff --git a/backend/package.json b/backend/package.json index 2733bf1..602f4c2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.16.1", + "version": "0.16.1-1", "private": true, "type": "commonjs", "scripts": { diff --git a/backend/src/services/makemkvKeyService.js b/backend/src/services/makemkvKeyService.js index b0e143a..85c3610 100644 --- a/backend/src/services/makemkvKeyService.js +++ b/backend/src/services/makemkvKeyService.js @@ -1,13 +1,18 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); +const { execFile } = require('child_process'); 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_URL = 'https://cable.ayra.ch/makemkv/api.php?json'; 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'; + || 'admin@example.invalid'; +const MAKEMKV_BETA_KEY_API_TIMEOUT_MS = Math.max( + 1000, + Number(process.env.MAKEMKV_BETA_KEY_API_TIMEOUT_MS || 15000) +); function normalizeRegistrationKey(rawValue) { return String(rawValue || '').trim(); @@ -89,27 +94,42 @@ async function syncRegistrationKeyToConfig(rawValue, options = {}) { } 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' - } - }); + const errors = []; - if (!response.ok) { - const error = new Error(`Betakey konnte nicht geladen werden (HTTP ${response.status}).`); + try { + return await fetchCurrentBetaKeyViaCurl(); + } catch (error) { + errors.push(error?.message || String(error)); + logger.warn('beta-key:curl-failed', { + error: error?.message || String(error) + }); + } + + try { + return await fetchCurrentBetaKeyViaFetch(); + } catch (error) { + errors.push(error?.message || String(error)); + logger.warn('beta-key:fetch-failed', { + error: error?.message || String(error) + }); + } + + const error = new Error(`Betakey konnte nicht geladen werden. Details: ${errors.join(' | ')}`); + error.statusCode = 502; + throw error; +} + +function parseBetaKeyResponse(rawBody) { + let payload = null; + try { + payload = JSON.parse(String(rawBody || '{}')); + } catch (_error) { + const error = new Error('Betakey-Antwort ist kein gültiges JSON.'); error.statusCode = 502; throw error; } - const rawBody = await response.text(); - const betaKey = String(rawBody || '') - .split(/\r?\n/) - .map((line) => line.trim()) - .find(Boolean) || ''; + const betaKey = String(payload?.key || '').trim(); if (!/^[A-Za-z0-9][A-Za-z0-9-]{15,}$/.test(betaKey)) { const error = new Error('Betakey-Antwort ist ungültig.'); @@ -123,6 +143,68 @@ async function fetchCurrentBetaKey() { }; } +function fetchCurrentBetaKeyViaCurl() { + return new Promise((resolve, reject) => { + execFile( + 'curl', + [ + '-fsSL', + '--max-time', String(Math.ceil(MAKEMKV_BETA_KEY_API_TIMEOUT_MS / 1000)), + '-A', MAKEMKV_BETA_KEY_API_USER_AGENT, + '-H', `From: ${MAKEMKV_BETA_KEY_API_FROM}`, + '-H', 'Accept: text/plain, text/html;q=0.9, */*;q=0.8', + '-H', 'Accept-Language: de,en-US;q=0.7,en;q=0.3', + '-H', 'Cache-Control: no-cache', + '-H', 'Pragma: no-cache', + MAKEMKV_BETA_KEY_API_URL + ], + { + timeout: MAKEMKV_BETA_KEY_API_TIMEOUT_MS, + maxBuffer: 1024 * 1024 + }, + (error, stdout, stderr) => { + if (error) { + const resolvedError = new Error( + `curl fehlgeschlagen (${error.code || 'unknown'}): ${String(stderr || error.message || '').trim() || 'keine Details'}` + ); + resolvedError.statusCode = 502; + reject(resolvedError); + return; + } + + try { + resolve(parseBetaKeyResponse(stdout)); + } catch (parseError) { + reject(parseError); + } + } + ); + }); +} + +async function fetchCurrentBetaKeyViaFetch() { + 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', + 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3', + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache' + }, + signal: AbortSignal.timeout(MAKEMKV_BETA_KEY_API_TIMEOUT_MS) + }); + + if (!response.ok) { + const error = new Error(`HTTP ${response.status}`); + error.statusCode = 502; + throw error; + } + + const rawBody = await response.text(); + return parseBetaKeyResponse(rawBody); +} + module.exports = { MAKEMKV_BETA_KEY_API_URL, fetchCurrentBetaKey, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dcae804..5f60109 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.16.1", + "version": "0.16.1-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.16.1", + "version": "0.16.1-1", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index 93a6dfa..b5e446f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.16.1", + "version": "0.16.1-1", "private": true, "type": "module", "scripts": { diff --git a/install.sh b/install.sh index 03e4e69..c64c7f0 100644 --- a/install.sh +++ b/install.sh @@ -446,35 +446,89 @@ install_makemkv() { 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="" + local backend_settings_url="http://127.0.0.1:${BACKEND_PORT}/settings" + local beta_api_url="https://cable.ayra.ch/makemkv/api.php?json" + local settings_response="" + local current_key="" + local beta_response="" + local beta_key="" + local save_response="" + local makemkv_settings_file="${SERVICE_HOME}/.MakeMKV/settings.conf" - if ! response=$(curl -fsS \ - -X POST \ + update_makemkv_settings_file_key() { + local settings_file="$1" + local registration_key="$2" + local tmp_file + tmp_file=$(mktemp) + + if [[ -f "$settings_file" ]]; then + grep -vE '^[[:space:]]*app_Key[[:space:]]*=' "$settings_file" > "$tmp_file" || true + else + : > "$tmp_file" + fi + + printf 'app_Key = "%s"\n' "$registration_key" >> "$tmp_file" + mv "$tmp_file" "$settings_file" + chown "$SERVICE_USER:$SERVICE_USER" "$settings_file" 2>/dev/null || true + chmod 600 "$settings_file" 2>/dev/null || true + } + + if ! settings_response=$(curl -fsS "$backend_settings_url" 2>/dev/null); then + warn "Backend-Settings konnten nicht geladen werden." + warn "Betakey-Synchronisierung übersprungen: $backend_settings_url" + return 0 + fi + + current_key=$(printf '%s' "$settings_response" | jq -r ' + (.categories // []) + | map(.settings // []) + | add + | map(select(.key == "makemkv_registration_key")) + | .[0].value // "" + ' 2>/dev/null || true) + current_key="${current_key:-}" + + mkdir -p "${SERVICE_HOME}/.MakeMKV" + chown "$SERVICE_USER:$SERVICE_USER" "${SERVICE_HOME}/.MakeMKV" 2>/dev/null || true + chmod 700 "${SERVICE_HOME}/.MakeMKV" 2>/dev/null || true + + if [[ -n "$current_key" ]]; then + update_makemkv_settings_file_key "$makemkv_settings_file" "$current_key" + ok "Vorhandener MakeMKV-Key bleibt erhalten ($makemkv_settings_file)" + return 0 + fi + + if ! beta_response=$(curl -fsS \ + -A 'Ripster/1.0 (MakeMKV beta key installer)' \ + -H 'From: admin@example.invalid' \ + -H 'Accept: application/json, text/plain;q=0.9, */*;q=0.8' \ + -H 'Accept-Language: de,en-US;q=0.7,en;q=0.3' \ + -H 'Cache-Control: no-cache' \ + -H 'Pragma: no-cache' \ + "$beta_api_url" 2>/dev/null); then + warn "MakeMKV-Betakey konnte nicht von der JSON-API geladen werden." + warn "API-Endpunkt fehlgeschlagen: $beta_api_url" + return 0 + fi + + beta_key=$(printf '%s' "$beta_response" | jq -r '.key // ""' 2>/dev/null || true) + beta_key="${beta_key:-}" + if [[ -z "$beta_key" ]]; then + warn "MakeMKV-Betakey fehlt in der JSON-Antwort." + return 0 + fi + + if ! save_response=$(curl -fsS \ + -X PUT \ -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" + --data "{\"value\":$(printf '%s' "$beta_key" | jq -Rs .)}" \ + "${backend_settings_url}/makemkv_registration_key" 2>/dev/null); then + warn "MakeMKV-Betakey konnte nicht in den Settings gespeichert werden." 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." + update_makemkv_settings_file_key "$makemkv_settings_file" "$beta_key" + ok "MakeMKV-Betakey automatisch gespeichert ($makemkv_settings_file)" } select_handbrake_mode() { @@ -756,8 +810,15 @@ header "Repository holen (Git)" # Prüfen ob der gewünschte Branch auf dem Remote existiert info "Prüfe Branch '$GIT_BRANCH' auf Remote..." -if ! git ls-remote --exit-code --heads "$REPO_URL" "$GIT_BRANCH" &>/dev/null; then - fatal "Branch '$GIT_BRANCH' existiert nicht im Repository $REPO_URL.\nVerfügbare Branches: $(git ls-remote --heads "$REPO_URL" | awk '{print $2}' | sed 's|refs/heads/||' | tr '\n' ' ')" +branch_check_output="" +branch_check_status=0 +if ! branch_check_output=$(git ls-remote --exit-code --heads "$REPO_URL" "$GIT_BRANCH" 2>&1); then + branch_check_status=$? + if [[ "$branch_check_status" -eq 2 ]]; then + available_branches=$(git ls-remote --heads "$REPO_URL" 2>/dev/null | awk '{print $2}' | sed 's|refs/heads/||' | tr '\n' ' ') + fatal "Branch '$GIT_BRANCH' existiert nicht im Repository $REPO_URL.\nVerfügbare Branches: ${available_branches:-keine oder Remote nicht erreichbar}" + fi + fatal "Remote $REPO_URL ist nicht erreichbar.\nGit-Fehler: ${branch_check_output:-unbekannt}" fi ok "Branch '$GIT_BRANCH' gefunden" diff --git a/package-lock.json b/package-lock.json index 1ab5fb6..34ff2ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster", - "version": "0.16.1", + "version": "0.16.1-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster", - "version": "0.16.1", + "version": "0.16.1-1", "devDependencies": { "concurrently": "^9.1.2" } diff --git a/package.json b/package.json index 30971e1..2ff335f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ripster", "private": true, - "version": "0.16.1", + "version": "0.16.1-1", "scripts": { "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev:backend": "npm run dev --prefix backend",