Prototype
This commit is contained in:
@@ -62,6 +62,7 @@ const PRE_ENCODE_PROGRESS_RESERVE = 10;
|
|||||||
const POST_ENCODE_PROGRESS_RESERVE = 10;
|
const POST_ENCODE_PROGRESS_RESERVE = 10;
|
||||||
const POST_ENCODE_FINISH_BUFFER = 1;
|
const POST_ENCODE_FINISH_BUFFER = 1;
|
||||||
const MIN_EXTENSIONLESS_DISC_IMAGE_BYTES = 256 * 1024 * 1024;
|
const MIN_EXTENSIONLESS_DISC_IMAGE_BYTES = 256 * 1024 * 1024;
|
||||||
|
const MAKEMKV_BACKUP_FAILURE_MSG_CODES = new Set([5069, 5080]);
|
||||||
const RAW_INCOMPLETE_PREFIX = 'Incomplete_';
|
const RAW_INCOMPLETE_PREFIX = 'Incomplete_';
|
||||||
const RAW_RIP_COMPLETE_PREFIX = 'Rip_Complete_';
|
const RAW_RIP_COMPLETE_PREFIX = 'Rip_Complete_';
|
||||||
const RAW_FOLDER_STATES = Object.freeze({
|
const RAW_FOLDER_STATES = Object.freeze({
|
||||||
@@ -552,6 +553,37 @@ function composeEncodeScriptStatusText(percent, phase, itemType, index, total, l
|
|||||||
return `ENCODING ${percent.toFixed(2)}% - ${phaseLabel} ${itemLabel}${position}${status}${detail ? `: ${detail}` : ''}`;
|
return `ENCODING ${percent.toFixed(2)}% - ${phaseLabel} ${itemLabel}${position}${status}${detail ? `: ${detail}` : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseMakeMkvMessageCode(line) {
|
||||||
|
const match = String(line || '').match(/\bMSG:(\d+),/i);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const code = Number(match[1]);
|
||||||
|
if (!Number.isFinite(code)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Math.trunc(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMakeMkvBackupFailureMarker(line) {
|
||||||
|
const text = String(line || '').trim();
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const code = parseMakeMkvMessageCode(text);
|
||||||
|
if (code !== null && MAKEMKV_BACKUP_FAILURE_MSG_CODES.has(code)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return /backup\s+failed/i.test(text) || /backup\s+fehlgeschlagen/i.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMakeMkvBackupFailureMarker(lines) {
|
||||||
|
if (!Array.isArray(lines)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lines.find((line) => isMakeMkvBackupFailureMarker(line)) || null;
|
||||||
|
}
|
||||||
|
|
||||||
function createEncodeScriptProgressTracker({
|
function createEncodeScriptProgressTracker({
|
||||||
jobId,
|
jobId,
|
||||||
preSteps = 0,
|
preSteps = 0,
|
||||||
@@ -664,7 +696,8 @@ function createEncodeScriptProgressTracker({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shouldKeepHighlight(line) {
|
function shouldKeepHighlight(line) {
|
||||||
return /error|fail|warn|title\s+#|saving|encoding:|muxing|copying|decrypt/i.test(line);
|
return /error|fail|warn|fehl|title\s+#|saving|encoding:|muxing|copying|decrypt/i.test(line)
|
||||||
|
|| isMakeMkvBackupFailureMarker(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeNonNegativeInteger(rawValue) {
|
function normalizeNonNegativeInteger(rawValue) {
|
||||||
@@ -8626,13 +8659,14 @@ class PipelineService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for MakeMKV backup failure even when exit code is 0.
|
// Check for MakeMKV backup failure even when exit code is 0.
|
||||||
// MakeMKV can exit 0 but still output "Backup failed" in stdout.
|
// MakeMKV can emit localized failure text while still exiting with 0.
|
||||||
const backupFailed = Array.isArray(makemkvInfo?.highlights) &&
|
const backupFailureLine = ripMode === 'backup'
|
||||||
makemkvInfo.highlights.some(line => /backup failed/i.test(line));
|
? findMakeMkvBackupFailureMarker(makemkvInfo?.highlights)
|
||||||
if (backupFailed) {
|
: null;
|
||||||
const failMsg = makemkvInfo.highlights.find(line => /backup failed/i.test(line)) || 'Backup failed';
|
if (backupFailureLine) {
|
||||||
|
const msgCode = parseMakeMkvMessageCode(backupFailureLine);
|
||||||
throw Object.assign(
|
throw Object.assign(
|
||||||
new Error(`MakeMKV Backup fehlgeschlagen (Exit Code 0): ${failMsg}`),
|
new Error(`MakeMKV Backup fehlgeschlagen${msgCode !== null ? ` (MSG:${msgCode})` : ''}: ${backupFailureLine}`),
|
||||||
{ runInfo: makemkvInfo }
|
{ runInfo: makemkvInfo }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -10082,9 +10116,16 @@ class PipelineService extends EventEmitter {
|
|||||||
settings.cd_output_template || cdRipService.DEFAULT_CD_OUTPUT_TEMPLATE
|
settings.cd_output_template || cdRipService.DEFAULT_CD_OUTPUT_TEMPLATE
|
||||||
).trim() || cdRipService.DEFAULT_CD_OUTPUT_TEMPLATE;
|
).trim() || cdRipService.DEFAULT_CD_OUTPUT_TEMPLATE;
|
||||||
const cdBaseDir = String(settings.raw_dir_cd || '').trim() || 'data/output/cd';
|
const cdBaseDir = String(settings.raw_dir_cd || '').trim() || 'data/output/cd';
|
||||||
|
const cdOutputOwner = String(settings.raw_dir_owner || '').trim();
|
||||||
const jobDir = `CD_Job${jobId}_${Date.now()}`;
|
const jobDir = `CD_Job${jobId}_${Date.now()}`;
|
||||||
const rawWavDir = path.join(cdBaseDir, '.tmp', jobDir, 'wav');
|
const rawWavDir = path.join(cdBaseDir, '.tmp', jobDir, 'wav');
|
||||||
|
const cdTempJobDir = path.dirname(rawWavDir);
|
||||||
const outputDir = cdRipService.buildOutputDir(effectiveSelectedMeta, cdBaseDir, cdOutputTemplate);
|
const outputDir = cdRipService.buildOutputDir(effectiveSelectedMeta, cdBaseDir, cdOutputTemplate);
|
||||||
|
ensureDir(cdBaseDir);
|
||||||
|
ensureDir(cdTempJobDir);
|
||||||
|
ensureDir(outputDir);
|
||||||
|
chownRecursive(cdTempJobDir, cdOutputOwner);
|
||||||
|
chownRecursive(outputDir, cdOutputOwner);
|
||||||
const previewTrackPos = effectiveSelectedTrackPositions[0] || mergedTracks[0]?.position || 1;
|
const previewTrackPos = effectiveSelectedTrackPositions[0] || mergedTracks[0]?.position || 1;
|
||||||
const previewWavPath = path.join(rawWavDir, `track${String(previewTrackPos).padStart(2, '0')}.cdda.wav`);
|
const previewWavPath = path.join(rawWavDir, `track${String(previewTrackPos).padStart(2, '0')}.cdda.wav`);
|
||||||
const cdparanoiaCommandPreview = `${cdparanoiaCmd} -d ${devicePath} ${previewTrackPos} ${previewWavPath}`;
|
const cdparanoiaCommandPreview = `${cdparanoiaCmd} -d ${devicePath} ${previewTrackPos} ${previewWavPath}`;
|
||||||
@@ -10149,6 +10190,7 @@ class PipelineService extends EventEmitter {
|
|||||||
format,
|
format,
|
||||||
formatOptions,
|
formatOptions,
|
||||||
outputTemplate: cdOutputTemplate,
|
outputTemplate: cdOutputTemplate,
|
||||||
|
outputOwner: cdOutputOwner,
|
||||||
selectedTrackPositions: effectiveSelectedTrackPositions,
|
selectedTrackPositions: effectiveSelectedTrackPositions,
|
||||||
tocTracks: mergedTracks,
|
tocTracks: mergedTracks,
|
||||||
selectedMeta: effectiveSelectedMeta
|
selectedMeta: effectiveSelectedMeta
|
||||||
@@ -10168,6 +10210,7 @@ class PipelineService extends EventEmitter {
|
|||||||
format,
|
format,
|
||||||
formatOptions,
|
formatOptions,
|
||||||
outputTemplate,
|
outputTemplate,
|
||||||
|
outputOwner,
|
||||||
selectedTrackPositions,
|
selectedTrackPositions,
|
||||||
tocTracks,
|
tocTracks,
|
||||||
selectedMeta
|
selectedMeta
|
||||||
@@ -10275,6 +10318,7 @@ class PipelineService extends EventEmitter {
|
|||||||
rip_successful: 1,
|
rip_successful: 1,
|
||||||
output_path: outputDir
|
output_path: outputDir
|
||||||
});
|
});
|
||||||
|
chownRecursive(outputDir, outputOwner);
|
||||||
await historyService.appendLog(jobId, 'SYSTEM', `CD-Rip abgeschlossen. Ausgabe: ${outputDir}`);
|
await historyService.appendLog(jobId, 'SYSTEM', `CD-Rip abgeschlossen. Ausgabe: ${outputDir}`);
|
||||||
|
|
||||||
await this.setState('FINISHED', {
|
await this.setState('FINISHED', {
|
||||||
|
|||||||
@@ -1317,6 +1317,13 @@ class SettingsService {
|
|||||||
return `dev:${device}`;
|
return `dev:${device}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const devicePath = String(deviceInfo?.path || '').trim();
|
||||||
|
if (devicePath) {
|
||||||
|
// Prefer stable Linux device path over MakeMKV disc index mapping.
|
||||||
|
// MakeMKV drive indices (disc:N) do not reliably match /dev/srN numbering.
|
||||||
|
return `dev:${devicePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (deviceInfo && deviceInfo.index !== undefined && deviceInfo.index !== null) {
|
if (deviceInfo && deviceInfo.index !== undefined && deviceInfo.index !== null) {
|
||||||
return `disc:${deviceInfo.index}`;
|
return `disc:${deviceInfo.index}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -658,6 +658,15 @@ else
|
|||||||
ok "Benutzer '$SERVICE_USER' angelegt"
|
ok "Benutzer '$SERVICE_USER' angelegt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
SERVICE_HOME="$(getent passwd "$SERVICE_USER" | cut -d: -f6)"
|
||||||
|
if [[ -z "$SERVICE_HOME" || "$SERVICE_HOME" == "/" || "$SERVICE_HOME" == "/nonexistent" ]]; then
|
||||||
|
SERVICE_HOME="/home/$SERVICE_USER"
|
||||||
|
fi
|
||||||
|
mkdir -p "$SERVICE_HOME"
|
||||||
|
chown "$SERVICE_USER:$SERVICE_USER" "$SERVICE_HOME" 2>/dev/null || true
|
||||||
|
chmod 755 "$SERVICE_HOME" 2>/dev/null || true
|
||||||
|
info "Service-Home für '$SERVICE_USER': $SERVICE_HOME"
|
||||||
|
|
||||||
# Optisches Laufwerk: Benutzer zur cdrom/optical-Gruppe hinzufügen
|
# Optisches Laufwerk: Benutzer zur cdrom/optical-Gruppe hinzufügen
|
||||||
for grp in cdrom optical disk; do
|
for grp in cdrom optical disk; do
|
||||||
if getent group "$grp" &>/dev/null; then
|
if getent group "$grp" &>/dev/null; then
|
||||||
@@ -767,22 +776,15 @@ chmod -R 755 "$INSTALL_DIR"
|
|||||||
chmod 600 "$ENV_FILE"
|
chmod 600 "$ENV_FILE"
|
||||||
|
|
||||||
# MakeMKV erwartet pro Benutzer ein eigenes Konfigurationsverzeichnis.
|
# MakeMKV erwartet pro Benutzer ein eigenes Konfigurationsverzeichnis.
|
||||||
ACTUAL_USER="${SUDO_USER:-}"
|
MAKEMKV_SERVICE_DIR="${SERVICE_HOME}/.MakeMKV"
|
||||||
if [[ -n "$ACTUAL_USER" && "$ACTUAL_USER" != "root" ]]; then
|
if [[ ! -d "$MAKEMKV_SERVICE_DIR" ]]; then
|
||||||
ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" | cut -d: -f6)"
|
mkdir -p "$MAKEMKV_SERVICE_DIR"
|
||||||
if [[ -z "$ACTUAL_HOME" ]]; then
|
ok "MakeMKV-Verzeichnis erstellt: $MAKEMKV_SERVICE_DIR"
|
||||||
ACTUAL_HOME="/home/$ACTUAL_USER"
|
else
|
||||||
fi
|
info "MakeMKV-Verzeichnis vorhanden: $MAKEMKV_SERVICE_DIR"
|
||||||
MAKEMKV_USER_DIR="${ACTUAL_HOME}/.MakeMKV"
|
|
||||||
if [[ ! -d "$MAKEMKV_USER_DIR" ]]; then
|
|
||||||
mkdir -p "$MAKEMKV_USER_DIR"
|
|
||||||
ok "MakeMKV-Verzeichnis erstellt: $MAKEMKV_USER_DIR"
|
|
||||||
else
|
|
||||||
info "MakeMKV-Verzeichnis vorhanden: $MAKEMKV_USER_DIR"
|
|
||||||
fi
|
|
||||||
chown "$ACTUAL_USER:$ACTUAL_USER" "$MAKEMKV_USER_DIR" 2>/dev/null || true
|
|
||||||
chmod 700 "$MAKEMKV_USER_DIR" 2>/dev/null || true
|
|
||||||
fi
|
fi
|
||||||
|
chown "$SERVICE_USER:$SERVICE_USER" "$MAKEMKV_SERVICE_DIR" 2>/dev/null || true
|
||||||
|
chmod 700 "$MAKEMKV_SERVICE_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
# --- Systemd-Dienst: Backend -------------------------------------------------
|
# --- Systemd-Dienst: Backend -------------------------------------------------
|
||||||
header "Systemd-Dienst (Backend) erstellen"
|
header "Systemd-Dienst (Backend) erstellen"
|
||||||
@@ -807,6 +809,7 @@ StartLimitBurst=3
|
|||||||
|
|
||||||
# Umgebung
|
# Umgebung
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
|
Environment=HOME=${SERVICE_HOME}
|
||||||
Environment=LANG=C.UTF-8
|
Environment=LANG=C.UTF-8
|
||||||
Environment=LC_ALL=C.UTF-8
|
Environment=LC_ALL=C.UTF-8
|
||||||
Environment=LANGUAGE=C.UTF-8
|
Environment=LANGUAGE=C.UTF-8
|
||||||
@@ -823,7 +826,7 @@ SyslogIdentifier=ripster-backend
|
|||||||
NoNewPrivileges=false
|
NoNewPrivileges=false
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
ProtectHome=read-only
|
ProtectHome=read-only
|
||||||
ReadWritePaths=${INSTALL_DIR}/backend/data ${INSTALL_DIR}/backend/logs /tmp
|
ReadWritePaths=${INSTALL_DIR}/backend/data ${INSTALL_DIR}/backend/logs /tmp ${SERVICE_HOME} ${MAKEMKV_SERVICE_DIR}
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
39
install.sh
39
install.sh
@@ -402,6 +402,15 @@ else
|
|||||||
ok "Benutzer '$SERVICE_USER' angelegt"
|
ok "Benutzer '$SERVICE_USER' angelegt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
SERVICE_HOME="$(getent passwd "$SERVICE_USER" | cut -d: -f6)"
|
||||||
|
if [[ -z "$SERVICE_HOME" || "$SERVICE_HOME" == "/" || "$SERVICE_HOME" == "/nonexistent" ]]; then
|
||||||
|
SERVICE_HOME="/home/$SERVICE_USER"
|
||||||
|
fi
|
||||||
|
mkdir -p "$SERVICE_HOME"
|
||||||
|
chown "$SERVICE_USER:$SERVICE_USER" "$SERVICE_HOME" 2>/dev/null || true
|
||||||
|
chmod 755 "$SERVICE_HOME" 2>/dev/null || true
|
||||||
|
info "Service-Home für '$SERVICE_USER': $SERVICE_HOME"
|
||||||
|
|
||||||
for grp in cdrom optical disk video render; do
|
for grp in cdrom optical disk video render; do
|
||||||
if getent group "$grp" &>/dev/null; then
|
if getent group "$grp" &>/dev/null; then
|
||||||
usermod -aG "$grp" "$SERVICE_USER" 2>/dev/null || true
|
usermod -aG "$grp" "$SERVICE_USER" 2>/dev/null || true
|
||||||
@@ -530,25 +539,22 @@ if [[ -n "$ACTUAL_USER" && "$ACTUAL_USER" != "root" ]]; then
|
|||||||
"$INSTALL_DIR/backend/data/output" \
|
"$INSTALL_DIR/backend/data/output" \
|
||||||
"$INSTALL_DIR/backend/data/logs"
|
"$INSTALL_DIR/backend/data/logs"
|
||||||
ok "Verzeichnisse $ACTUAL_USER:$SERVICE_USER (775) zugewiesen"
|
ok "Verzeichnisse $ACTUAL_USER:$SERVICE_USER (775) zugewiesen"
|
||||||
|
|
||||||
# MakeMKV erwartet pro Benutzer ein eigenes Konfigurationsverzeichnis.
|
|
||||||
ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" | cut -d: -f6)"
|
|
||||||
if [[ -z "$ACTUAL_HOME" ]]; then
|
|
||||||
ACTUAL_HOME="/home/$ACTUAL_USER"
|
|
||||||
fi
|
|
||||||
MAKEMKV_USER_DIR="${ACTUAL_HOME}/.MakeMKV"
|
|
||||||
if [[ ! -d "$MAKEMKV_USER_DIR" ]]; then
|
|
||||||
mkdir -p "$MAKEMKV_USER_DIR"
|
|
||||||
ok "MakeMKV-Verzeichnis erstellt: $MAKEMKV_USER_DIR"
|
|
||||||
else
|
|
||||||
info "MakeMKV-Verzeichnis vorhanden: $MAKEMKV_USER_DIR"
|
|
||||||
fi
|
|
||||||
chown "$ACTUAL_USER:$ACTUAL_USER" "$MAKEMKV_USER_DIR" 2>/dev/null || true
|
|
||||||
chmod 700 "$MAKEMKV_USER_DIR" 2>/dev/null || true
|
|
||||||
else
|
else
|
||||||
ok "Verzeichnisse bereits $SERVICE_USER gehörig (kein SUDO_USER erkannt)"
|
ok "Verzeichnisse bereits $SERVICE_USER gehörig (kein SUDO_USER erkannt)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# MakeMKV erwartet pro Benutzer ein eigenes Konfigurationsverzeichnis.
|
||||||
|
# Laufzeit-relevant ist das Verzeichnis des Service-Users.
|
||||||
|
MAKEMKV_SERVICE_DIR="${SERVICE_HOME}/.MakeMKV"
|
||||||
|
if [[ ! -d "$MAKEMKV_SERVICE_DIR" ]]; then
|
||||||
|
mkdir -p "$MAKEMKV_SERVICE_DIR"
|
||||||
|
ok "MakeMKV-Verzeichnis erstellt: $MAKEMKV_SERVICE_DIR"
|
||||||
|
else
|
||||||
|
info "MakeMKV-Verzeichnis vorhanden: $MAKEMKV_SERVICE_DIR"
|
||||||
|
fi
|
||||||
|
chown "$SERVICE_USER:$SERVICE_USER" "$MAKEMKV_SERVICE_DIR" 2>/dev/null || true
|
||||||
|
chmod 700 "$MAKEMKV_SERVICE_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
# --- Systemd-Dienst: Backend -------------------------------------------------
|
# --- Systemd-Dienst: Backend -------------------------------------------------
|
||||||
header "Systemd-Dienst (Backend) erstellen"
|
header "Systemd-Dienst (Backend) erstellen"
|
||||||
|
|
||||||
@@ -570,6 +576,7 @@ StartLimitIntervalSec=60
|
|||||||
StartLimitBurst=3
|
StartLimitBurst=3
|
||||||
|
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
|
Environment=HOME=${SERVICE_HOME}
|
||||||
Environment=LANG=C.UTF-8
|
Environment=LANG=C.UTF-8
|
||||||
Environment=LC_ALL=C.UTF-8
|
Environment=LC_ALL=C.UTF-8
|
||||||
Environment=LANGUAGE=C.UTF-8
|
Environment=LANGUAGE=C.UTF-8
|
||||||
@@ -589,7 +596,7 @@ SupplementaryGroups=video render cdrom disk
|
|||||||
NoNewPrivileges=false
|
NoNewPrivileges=false
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
ProtectHome=read-only
|
ProtectHome=read-only
|
||||||
ReadWritePaths=${INSTALL_DIR}/backend/data ${INSTALL_DIR}/backend/logs /tmp
|
ReadWritePaths=${INSTALL_DIR}/backend/data ${INSTALL_DIR}/backend/logs /tmp ${SERVICE_HOME} ${MAKEMKV_SERVICE_DIR}
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
Reference in New Issue
Block a user