Files
sofarr/server/utils/config.js
T
gronod ed4237debb
Docs Check / Markdown lint (push) Successful in 1m43s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m1s
CI / Security audit (push) Successful in 2m48s
Docs Check / Mermaid diagram parse check (push) Successful in 3m8s
CI / Tests & coverage (push) Failing after 3m33s
CI / Swagger Validation & Coverage (push) Successful in 3m34s
Build and Push Docker Image / build (push) Successful in 4m36s
feat(ombi): Add Ombi PALDRA integration for request management
- Add OmbiRetriever extending ArrRetriever for PALDRA compliance
- Add OmbiClient for low-level Ombi API communication
- Add getOmbiInstances() to config.js following multi-instance pattern
- Register Ombi in PALDRA registry with Ombi-specific methods
- Add external ID matching (TMDB/TVDB/IMDB) to Ombi requests
- Update DownloadMatcher to be async and enrich downloads with Ombi links
- Add getOmbiLink/getOmbiSearchLink helpers to DownloadAssembler
- Implement new service icon layout (Ombi + Sonarr/Radarr icons)
- Add CSS styling for service icons
- Update dashboard routes to include Ombi configuration
- Extend OpenAPI with Ombi tag and NormalizedDownload properties
- Update documentation (README, ARCHITECTURE, SECURITY, CHANGELOG)
- Add Ombi configuration to .env.sample
2026-05-21 17:00:04 +01:00

146 lines
3.8 KiB
JavaScript

// Copyright (c) 2026 Gordon Bolton. MIT License.
const { logToFile } = require('./logger');
// Validate that a configured service URL is well-formed and uses http(s).
// Emits a warning (never throws) so a misconfigured instance degrades
// gracefully rather than crashing the whole server.
function validateInstanceUrl(url, instanceId) {
if (!url || typeof url !== 'string') {
logToFile(`[Config] WARNING: instance "${instanceId}" has no URL configured`);
return false;
}
let parsed;
try {
parsed = new URL(url);
} catch {
logToFile(`[Config] WARNING: instance "${instanceId}" has an invalid URL: "${url}"`);
return false;
}
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
logToFile(`[Config] WARNING: instance "${instanceId}" URL must use http or https, got "${parsed.protocol}"`);
return false;
}
return true;
}
function parseInstances(envVar, legacyUrl, legacyKey, legacyUsername, legacyPassword) {
// Try to parse JSON array format first
if (envVar) {
try {
// Handle multi-line JSON by removing newlines and extra spaces
const cleaned = envVar.replace(/\s+/g, ' ').trim();
const instances = JSON.parse(cleaned);
if (Array.isArray(instances) && instances.length > 0) {
logToFile(`[Config] Parsed ${instances.length} instances from JSON array`);
return instances.map((inst, idx) => {
const id = inst.name || `instance-${idx + 1}`;
validateInstanceUrl(inst.url, id);
return { ...inst, id };
});
}
} catch (err) {
logToFile(`[Config] Failed to parse JSON array: ${err.message}`);
}
}
// Fall back to legacy single-instance format
if (legacyUrl && legacyKey) {
logToFile(`[Config] Using legacy single-instance format`);
validateInstanceUrl(legacyUrl, 'default');
return [{
id: 'default',
name: 'Default',
url: legacyUrl,
apiKey: legacyKey,
username: legacyUsername,
password: legacyPassword
}];
}
return [];
}
function getSABnzbdInstances() {
return parseInstances(
process.env.SABNZBD_INSTANCES,
process.env.SABNZBD_URL,
process.env.SABNZBD_API_KEY
);
}
function getSonarrInstances() {
return parseInstances(
process.env.SONARR_INSTANCES,
process.env.SONARR_URL,
process.env.SONARR_API_KEY
);
}
function getRadarrInstances() {
return parseInstances(
process.env.RADARR_INSTANCES,
process.env.RADARR_URL,
process.env.RADARR_API_KEY
);
}
function getOmbiInstances() {
return parseInstances(
process.env.OMBI_INSTANCES,
process.env.OMBI_URL,
process.env.OMBI_API_KEY
);
}
function getQbittorrentInstances() {
return parseInstances(
process.env.QBITTORRENT_INSTANCES,
process.env.QBITTORRENT_URL,
null, // no apiKey for qBittorrent
process.env.QBITTORRENT_USERNAME,
process.env.QBITTORRENT_PASSWORD
);
}
function getTransmissionInstances() {
return parseInstances(
process.env.TRANSMISSION_INSTANCES,
process.env.TRANSMISSION_URL,
null, // no apiKey for Transmission
process.env.TRANSMISSION_USERNAME,
process.env.TRANSMISSION_PASSWORD
);
}
function getRtorrentInstances() {
return parseInstances(
process.env.RTORRENT_INSTANCES,
process.env.RTORRENT_URL,
null, // no apiKey for rtorrent
process.env.RTORRENT_USERNAME,
process.env.RTORRENT_PASSWORD
);
}
function getWebhookSecret() {
return process.env.SOFARR_WEBHOOK_SECRET || '';
}
function getSofarrBaseUrl() {
return process.env.SOFARR_BASE_URL || '';
}
module.exports = {
getSABnzbdInstances,
getSonarrInstances,
getRadarrInstances,
getOmbiInstances,
getQbittorrentInstances,
getTransmissionInstances,
getRtorrentInstances,
getWebhookSecret,
getSofarrBaseUrl,
parseInstances,
validateInstanceUrl
};