Sync DNS2 production changes - removed obsolete test suite and refactored structure

This commit is contained in:
Krystie
2026-03-23 10:47:15 +01:00
parent 1ac50918ab
commit d76644d948
288 changed files with 8965 additions and 15731 deletions

View File

@@ -11,12 +11,12 @@ module.exports = function(ctx, helpers) {
const results = { radarr: null, sonarr: null };
// Step 1: Authenticate with Overseerr via Plex token
const overseerrUrl = `http://host.docker.internal:${APP_PORTS.overseerr}`;
let overseerrUrl = `http://host.docker.internal:${APP_PORTS.overseerr}`;
const overseerrSession = await helpers.getOverseerrSession();
if (!overseerrSession) {
return ctx.errorResponse(res, 502, 'Could not authenticate with Overseerr. Make sure Plex and Overseerr are running.', {
hint: 'Complete Overseerr setup wizard and link your Plex account first, then try again.',
hint: 'Complete Overseerr setup wizard and link your Plex account first, then try again.'
});
}
@@ -30,8 +30,8 @@ module.exports = function(ctx, helpers) {
headers: {
'Content-Type': 'application/json',
'Cookie': overseerrSession.cookie,
...options.headers,
},
...options.headers
}
});
return response;
};
@@ -41,12 +41,12 @@ module.exports = function(ctx, helpers) {
const statusRes = await overseerrFetch('/api/v1/status');
if (!statusRes.ok) {
return ctx.errorResponse(res, 502, 'Cannot connect to Overseerr', {
hint: 'Make sure Overseerr is running on port 5055',
hint: 'Make sure Overseerr is running on port 5055'
});
}
} catch (e) {
return ctx.errorResponse(res, 502, `Cannot reach Overseerr: ${e.message}`, {
hint: 'Check if Overseerr container is running',
hint: 'Check if Overseerr container is running'
});
}
@@ -59,14 +59,14 @@ module.exports = function(ctx, helpers) {
// Fetch quality profiles from Radarr
const profilesRes = await ctx.fetchT(`${radarrBaseUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': radarr.apiKey },
headers: { 'X-Api-Key': radarr.apiKey }
});
const profiles = profilesRes.ok ? await profilesRes.json() : [];
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
// Fetch root folders from Radarr
const rootFoldersRes = await ctx.fetchT(`${radarrBaseUrl}/api/v3/rootfolder`, {
headers: { 'X-Api-Key': radarr.apiKey },
headers: { 'X-Api-Key': radarr.apiKey }
});
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
const defaultRootFolder = rootFolders[0]?.path || '/movies';
@@ -87,12 +87,12 @@ module.exports = function(ctx, helpers) {
minimumAvailability: 'released',
isDefault: true,
externalUrl: radarr.url,
tags: [],
tags: []
};
const radarrRes = await overseerrFetch('/api/v1/settings/radarr', {
method: 'POST',
body: JSON.stringify(radarrConfig),
body: JSON.stringify(radarrConfig)
});
if (radarrRes.ok) {
@@ -115,14 +115,14 @@ module.exports = function(ctx, helpers) {
// Fetch quality profiles from Sonarr
const profilesRes = await ctx.fetchT(`${sonarrBaseUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': sonarr.apiKey },
headers: { 'X-Api-Key': sonarr.apiKey }
});
const profiles = profilesRes.ok ? await profilesRes.json() : [];
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
// Fetch root folders from Sonarr
const rootFoldersRes = await ctx.fetchT(`${sonarrBaseUrl}/api/v3/rootfolder`, {
headers: { 'X-Api-Key': sonarr.apiKey },
headers: { 'X-Api-Key': sonarr.apiKey }
});
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
const defaultRootFolder = rootFolders[0]?.path || '/tv';
@@ -131,7 +131,7 @@ module.exports = function(ctx, helpers) {
let languageProfileId = 1;
try {
const langRes = await ctx.fetchT(`${sonarrBaseUrl}/api/v3/languageprofile`, {
headers: { 'X-Api-Key': sonarr.apiKey },
headers: { 'X-Api-Key': sonarr.apiKey }
});
if (langRes.ok) {
const langProfiles = await langRes.json();
@@ -158,12 +158,12 @@ module.exports = function(ctx, helpers) {
isDefault: true,
enableSeasonFolders: true,
externalUrl: sonarr.url,
tags: [],
tags: []
};
const sonarrRes = await overseerrFetch('/api/v1/settings/sonarr', {
method: 'POST',
body: JSON.stringify(sonarrConfig),
body: JSON.stringify(sonarrConfig)
});
if (sonarrRes.ok) {
@@ -182,7 +182,7 @@ module.exports = function(ctx, helpers) {
res.json({
success: anyConfigured,
message: anyConfigured ? 'Services configured in Overseerr' : 'Configuration failed',
results,
results
});
}, 'arr-configure-overseerr'));
@@ -210,7 +210,7 @@ module.exports = function(ctx, helpers) {
}
// Normalize URL - remove trailing slash
const baseUrl = url.replace(/\/+$/, '');
let baseUrl = url.replace(/\/+$/, '');
// Build the API endpoint
let apiEndpoint;
@@ -233,7 +233,7 @@ module.exports = function(ctx, helpers) {
const response = await ctx.fetchT(apiEndpoint, {
method: 'GET',
headers,
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
if (response.ok) {
@@ -244,7 +244,7 @@ module.exports = function(ctx, helpers) {
return res.json({
success: true,
version,
appName,
appName
});
} else if (response.status === 401) {
return ctx.errorResponse(res, 401, 'Invalid API key');
@@ -288,7 +288,7 @@ module.exports = function(ctx, helpers) {
containerName: container.Names[0]?.replace(/^\//, ''),
port: exposedPort,
url: `http://host.docker.internal:${exposedPort}`,
localUrl: `http://localhost:${exposedPort}`,
localUrl: `http://localhost:${exposedPort}`
};
// Extract API key for arr services
@@ -305,7 +305,7 @@ module.exports = function(ctx, helpers) {
radarrFound: !!detected.radarr?.apiKey,
sonarrFound: !!detected.sonarr?.apiKey,
lidarrFound: !!detected.lidarr?.apiKey,
prowlarrFound: !!detected.prowlarr?.apiKey,
prowlarrFound: !!detected.prowlarr?.apiKey
};
ctx.log.info('arr', 'Detected services', summary);
@@ -313,14 +313,14 @@ module.exports = function(ctx, helpers) {
if (!summary.overseerrFound) {
return ctx.errorResponse(res, 400, 'Overseerr is not running. Deploy it first.', {
detected,
summary,
summary
});
}
if (!summary.radarrFound && !summary.sonarrFound) {
return ctx.errorResponse(res, 400, 'No Radarr or Sonarr found with valid API keys. Deploy at least one first.', {
detected,
summary,
summary
});
}
@@ -331,7 +331,7 @@ module.exports = function(ctx, helpers) {
return ctx.errorResponse(res, 502, 'Could not authenticate with Overseerr. Make sure Plex and Overseerr are running.', {
setupUrl: detected.overseerr.localUrl,
detected,
summary,
summary
});
}
@@ -344,8 +344,8 @@ module.exports = function(ctx, helpers) {
headers: {
'Content-Type': 'application/json',
'Cookie': overseerrSession.cookie,
...options.headers,
},
...options.headers
}
});
};
@@ -356,14 +356,14 @@ module.exports = function(ctx, helpers) {
try {
// Fetch quality profiles from Radarr
const profilesRes = await ctx.fetchT(`${detected.radarr.localUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': detected.radarr.apiKey },
headers: { 'X-Api-Key': detected.radarr.apiKey }
});
const profiles = profilesRes.ok ? await profilesRes.json() : [];
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
// Fetch root folders from Radarr
const rootFoldersRes = await ctx.fetchT(`${detected.radarr.localUrl}/api/v3/rootfolder`, {
headers: { 'X-Api-Key': detected.radarr.apiKey },
headers: { 'X-Api-Key': detected.radarr.apiKey }
});
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
const defaultRootFolder = rootFolders[0]?.path || '/movies';
@@ -384,12 +384,12 @@ module.exports = function(ctx, helpers) {
minimumAvailability: 'released',
isDefault: true,
externalUrl: detected.radarr.localUrl,
tags: [],
tags: []
};
const resp = await overseerrFetch('/api/v1/settings/radarr', {
method: 'POST',
body: JSON.stringify(radarrConfig),
body: JSON.stringify(radarrConfig)
});
configResults.radarr = resp.ok ? 'configured' : `failed: ${await resp.text()}`;
@@ -403,14 +403,14 @@ module.exports = function(ctx, helpers) {
try {
// Fetch quality profiles from Sonarr
const profilesRes = await ctx.fetchT(`${detected.sonarr.localUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': detected.sonarr.apiKey },
headers: { 'X-Api-Key': detected.sonarr.apiKey }
});
const profiles = profilesRes.ok ? await profilesRes.json() : [];
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
// Fetch root folders from Sonarr
const rootFoldersRes = await ctx.fetchT(`${detected.sonarr.localUrl}/api/v3/rootfolder`, {
headers: { 'X-Api-Key': detected.sonarr.apiKey },
headers: { 'X-Api-Key': detected.sonarr.apiKey }
});
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
const defaultRootFolder = rootFolders[0]?.path || '/tv';
@@ -419,7 +419,7 @@ module.exports = function(ctx, helpers) {
let languageProfileId = 1;
try {
const langRes = await ctx.fetchT(`${detected.sonarr.localUrl}/api/v3/languageprofile`, {
headers: { 'X-Api-Key': detected.sonarr.apiKey },
headers: { 'X-Api-Key': detected.sonarr.apiKey }
});
if (langRes.ok) {
const langProfiles = await langRes.json();
@@ -444,12 +444,12 @@ module.exports = function(ctx, helpers) {
isDefault: true,
enableSeasonFolders: true,
externalUrl: detected.sonarr.localUrl,
tags: [],
tags: []
};
const resp = await overseerrFetch('/api/v1/settings/sonarr', {
method: 'POST',
body: JSON.stringify(sonarrConfig),
body: JSON.stringify(sonarrConfig)
});
configResults.sonarr = resp.ok ? 'configured' : `failed: ${await resp.text()}`;
@@ -466,7 +466,7 @@ module.exports = function(ctx, helpers) {
'deploymentSuccess',
'Arr Stack Auto-Connected',
`Overseerr configured: ${Object.entries(configResults).filter(([k,v]) => v === 'configured').map(([k]) => k).join(', ')}`,
'success',
'success'
);
}
@@ -475,7 +475,7 @@ module.exports = function(ctx, helpers) {
message: anyConfigured ? 'Auto-setup completed successfully!' : 'Configuration failed',
detected,
configResults,
summary,
summary
});
}, 'arr-auto-setup'));

View File

@@ -39,7 +39,7 @@ module.exports = function(ctx, helpers) {
service,
source: url ? 'external' : 'local',
url: url || null,
storedAt: new Date().toISOString(),
storedAt: new Date().toISOString()
};
// Test connection if URL is known
@@ -77,7 +77,7 @@ module.exports = function(ctx, helpers) {
return ctx.errorResponse(res, 400, 'Invalid seedbox base URL');
}
await ctx.credentialManager.store('arr.seedbox.baseurl', seedboxBaseUrl, {
storedAt: new Date().toISOString(),
storedAt: new Date().toISOString()
});
}
@@ -87,7 +87,7 @@ module.exports = function(ctx, helpers) {
success: true,
message: `${service} API key stored`,
connectionTest,
url: resolvedUrl,
url: resolvedUrl
});
}, 'arr-credentials-store'));
@@ -106,7 +106,7 @@ module.exports = function(ctx, helpers) {
url: metadata?.url || null,
lastVerified: metadata?.lastVerified || null,
version: metadata?.version || null,
source: metadata?.source || null,
source: metadata?.source || null
};
}

View File

@@ -13,7 +13,7 @@ module.exports = function(ctx, helpers) {
sonarr: null,
overseerr: null,
lidarr: null,
prowlarr: null,
prowlarr: null
};
// Service detection patterns
@@ -35,7 +35,7 @@ module.exports = function(ctx, helpers) {
image: container.Image,
port: exposedPort,
status: container.State,
url: helpers.getServiceUrl(containerName, exposedPort),
url: helpers.getServiceUrl(containerName, exposedPort)
};
// Get API key for arr services (not Plex or Overseerr)
@@ -58,8 +58,8 @@ module.exports = function(ctx, helpers) {
plexReady: !!(detected.plex?.token),
radarrReady: !!(detected.radarr?.apiKey),
sonarrReady: !!(detected.sonarr?.apiKey),
overseerrRunning: !!detected.overseerr,
},
overseerrRunning: !!detected.overseerr
}
});
}, 'arr-detect'));
@@ -86,7 +86,7 @@ module.exports = function(ctx, helpers) {
containerId: container.Id,
containerName: container.Names[0]?.replace(/^\//, ''),
port: portInfo?.PublicPort || config.port,
status: container.State,
status: container.State
};
}
}
@@ -122,7 +122,7 @@ module.exports = function(ctx, helpers) {
hasToken: false,
containerId: null,
containerName: null,
version: null,
version: null
};
// Check Docker first
@@ -143,7 +143,7 @@ module.exports = function(ctx, helpers) {
// Store for later use
await ctx.credentialManager.store('arr.plex.token', token, {
service: 'plex', source: 'local', url: entry.url,
lastVerified: new Date().toISOString(),
lastVerified: new Date().toISOString()
});
} else {
entry.status = 'needs_key';
@@ -160,7 +160,7 @@ module.exports = function(ctx, helpers) {
try {
const radarrCheck = await ctx.fetchT(`http://host.docker.internal:${dc.port}/api/v1/settings/radarr`, {
headers: { 'Cookie': session.cookie },
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(5000)
});
if (radarrCheck.ok) {
const radarrSettings = await radarrCheck.json();
@@ -170,7 +170,7 @@ module.exports = function(ctx, helpers) {
try {
const sonarrCheck = await ctx.fetchT(`http://host.docker.internal:${dc.port}/api/v1/settings/sonarr`, {
headers: { 'Cookie': session.cookie },
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(5000)
});
if (sonarrCheck.ok) {
const sonarrSettings = await sonarrCheck.json();
@@ -180,7 +180,7 @@ module.exports = function(ctx, helpers) {
try {
const plexCheck = await ctx.fetchT(`http://host.docker.internal:${dc.port}/api/v1/settings/plex`, {
headers: { 'Cookie': session.cookie },
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(5000)
});
if (plexCheck.ok) {
const plexSettings = await plexCheck.json();
@@ -273,7 +273,7 @@ module.exports = function(ctx, helpers) {
fullyConnected: statuses.filter(s => s.status === 'connected').length,
needsApiKey: statuses.filter(s => s.status === 'needs_key').length,
errors: statuses.filter(s => s.status === 'error').length,
readyForAutoConnect: statuses.filter(s => s.status === 'connected').length >= 2,
readyForAutoConnect: statuses.filter(s => s.status === 'connected').length >= 2
};
res.json({ success: true, services: result, seedboxBaseUrl: detectedSeedboxUrl, summary });

View File

@@ -12,7 +12,7 @@ module.exports = function(ctx) {
const exec = await dockerContainer.exec({
Cmd: ['cat', '/config/config.xml'],
AttachStdout: true,
AttachStderr: true,
AttachStderr: true
});
const stream = await exec.start();
@@ -38,7 +38,7 @@ module.exports = function(ctx) {
try {
const containers = await ctx.docker.client.listContainers({ all: false });
const container = containers.find(c =>
c.Names.some(n => n.toLowerCase().includes(containerName.toLowerCase()) || n.toLowerCase().includes('plex')),
c.Names.some(n => n.toLowerCase().includes(containerName.toLowerCase()) || n.toLowerCase().includes('plex'))
);
if (!container) return null;
@@ -47,7 +47,7 @@ module.exports = function(ctx) {
const exec = await dockerContainer.exec({
Cmd: ['cat', '/config/Library/Application Support/Plex Media Server/Preferences.xml'],
AttachStdout: true,
AttachStderr: true,
AttachStderr: true
});
const stream = await exec.start();
@@ -97,7 +97,7 @@ module.exports = function(ctx) {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ authToken: plexToken }),
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
if (!authRes.ok) {
@@ -125,7 +125,7 @@ module.exports = function(ctx) {
// 1. Get Plex server identity (for return info)
const identityRes = await ctx.fetchT(`${plexUrl}/identity`, {
headers: { 'X-Plex-Token': plexToken, 'Accept': 'application/json' },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
if (!identityRes.ok) throw new Error('Cannot reach Plex server');
const identity = await identityRes.json();
@@ -136,16 +136,16 @@ module.exports = function(ctx) {
const plexConfig = {
ip: 'host.docker.internal',
port: APP_PORTS.plex,
useSsl: false,
useSsl: false
};
const configRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': sessionCookie,
'Cookie': sessionCookie
},
body: JSON.stringify(plexConfig),
body: JSON.stringify(plexConfig)
});
if (!configRes.ok) {
@@ -157,7 +157,7 @@ module.exports = function(ctx) {
await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex/sync`, {
method: 'POST',
headers: { 'Cookie': sessionCookie },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
} catch (e) {
ctx.log.warn('arr', 'Plex library sync trigger failed (non-fatal)', { error: e.message });
@@ -168,7 +168,7 @@ module.exports = function(ctx) {
try {
const libRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
headers: { 'Cookie': sessionCookie },
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(5000)
});
if (libRes.ok) {
const plexSettings = await libRes.json();
@@ -188,7 +188,7 @@ module.exports = function(ctx) {
try {
const existingRes = await ctx.fetchT(`${prowlarrUrl}/api/v1/applications`, {
headers: { 'X-Api-Key': prowlarrApiKey },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
existingApps = existingRes.ok ? await existingRes.json() : [];
} catch (e) {
@@ -217,8 +217,8 @@ module.exports = function(ctx) {
{ name: 'prowlarrUrl', value: prowlarrUrl },
{ name: 'baseUrl', value: config.url },
{ name: 'apiKey', value: config.apiKey },
{ name: 'syncCategories', value: syncCategories },
],
{ name: 'syncCategories', value: syncCategories }
]
};
try {
@@ -226,10 +226,10 @@ module.exports = function(ctx) {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': prowlarrApiKey,
'X-Api-Key': prowlarrApiKey
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
results[appName] = res.ok ? 'configured' : `failed: ${await res.text()}`;
} catch (e) {
@@ -262,7 +262,7 @@ module.exports = function(ctx) {
const response = await ctx.fetchT(apiEndpoint, {
method: 'GET',
headers,
signal: AbortSignal.timeout(15000),
signal: AbortSignal.timeout(15000)
});
if (response.ok) {
@@ -297,6 +297,6 @@ module.exports = function(ctx) {
getOverseerrApiKey,
connectPlexToOverseerr,
configureProwlarrApps,
testServiceConnection,
testServiceConnection
};
};

View File

@@ -14,7 +14,7 @@ module.exports = function(ctx, helpers) {
if (!plexToken) {
return ctx.errorResponse(res, 400, 'No Plex token available. Claim your Plex server first.', {
hint: 'Deploy Plex with a claim token or manually configure it.',
hint: 'Deploy Plex with a claim token or manually configure it.'
});
}
@@ -32,7 +32,7 @@ module.exports = function(ctx, helpers) {
// Fetch libraries
const libRes = await ctx.fetchT(`${plexUrl}/library/sections`, {
headers: { 'X-Plex-Token': plexToken, 'Accept': 'application/json' },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
if (!libRes.ok) {
@@ -45,7 +45,7 @@ module.exports = function(ctx, helpers) {
title: dir.title,
type: dir.type,
count: parseInt(dir.count) || 0,
scannedAt: dir.scannedAt,
scannedAt: dir.scannedAt
}));
// Get server name
@@ -54,7 +54,7 @@ module.exports = function(ctx, helpers) {
try {
const identityRes = await ctx.fetchT(`${plexUrl}/identity`, {
headers: { 'X-Plex-Token': plexToken, 'Accept': 'application/json' },
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(5000)
});
if (identityRes.ok) {
const identity = await identityRes.json();
@@ -66,7 +66,7 @@ module.exports = function(ctx, helpers) {
// Store token for future use
await ctx.credentialManager.store('arr.plex.token', plexToken, {
service: 'plex', source: 'local', url: plexUrl,
lastVerified: new Date().toISOString(),
lastVerified: new Date().toISOString()
});
res.json({ success: true, serverName, version, libraries });

View File

@@ -44,7 +44,7 @@ module.exports = function(ctx, helpers) {
steps.push({
step: `Test ${svc.charAt(0).toUpperCase() + svc.slice(1)} connection`,
status: test.success ? 'success' : 'failed',
details: test.success ? `v${test.version}` : test.error,
details: test.success ? `v${test.version}` : test.error
});
if (test.success) {
@@ -55,12 +55,12 @@ module.exports = function(ctx, helpers) {
const stored = await ctx.credentialManager.store(`arr.${svc}.apikey`, apiKey, {
service: svc, source: 'external', url,
lastVerified: new Date().toISOString(),
version: test.version,
version: test.version
});
steps.push({
step: `Save ${svc} credentials`,
status: stored ? 'success' : 'failed',
details: stored ? 'Encrypted and saved' : 'Storage failed',
details: stored ? 'Encrypted and saved' : 'Storage failed'
});
}
}
@@ -94,7 +94,7 @@ module.exports = function(ctx, helpers) {
steps.push({
step: 'Get Overseerr API key',
status: 'failed',
details: 'Could not authenticate with Overseerr (Plex not running or not linked)',
details: 'Could not authenticate with Overseerr (Plex not running or not linked)'
});
} else {
steps.push({ step: 'Get Overseerr API key', status: 'success', details: 'Extracted from container' });
@@ -110,7 +110,7 @@ module.exports = function(ctx, helpers) {
// Fetch quality profiles
const profilesRes = await ctx.fetchT(`${radarrUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': connectedServices.radarr.apiKey },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
const profiles = profilesRes.ok ? await profilesRes.json() : [];
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
@@ -118,7 +118,7 @@ module.exports = function(ctx, helpers) {
// Fetch root folders
const rootFoldersRes = await ctx.fetchT(`${radarrUrl}/api/v3/rootfolder`, {
headers: { 'X-Api-Key': connectedServices.radarr.apiKey },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
const defaultRootFolder = rootFolders[0]?.path || '/movies';
@@ -141,20 +141,20 @@ module.exports = function(ctx, helpers) {
minimumAvailability: 'released',
isDefault: true,
externalUrl: connectedServices.radarr.url,
tags: [],
tags: []
};
const radarrRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/radarr`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Cookie': overseerrCookie },
body: JSON.stringify(radarrConfig),
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
steps.push({
step: 'Configure Radarr in Overseerr',
status: radarrRes.ok ? 'success' : 'failed',
details: radarrRes.ok ? `Profile: ${defaultProfile.name}, Root: ${defaultRootFolder}` : await radarrRes.text(),
details: radarrRes.ok ? `Profile: ${defaultProfile.name}, Root: ${defaultRootFolder}` : await radarrRes.text()
});
} catch (e) {
steps.push({ step: 'Configure Radarr in Overseerr', status: 'failed', details: e.message });
@@ -170,14 +170,14 @@ module.exports = function(ctx, helpers) {
const profilesRes = await ctx.fetchT(`${sonarrUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
const profiles = profilesRes.ok ? await profilesRes.json() : [];
const defaultProfile = profiles[0] || { id: 1, name: 'Any' };
const rootFoldersRes = await ctx.fetchT(`${sonarrUrl}/api/v3/rootfolder`, {
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
const rootFolders = rootFoldersRes.ok ? await rootFoldersRes.json() : [];
const defaultRootFolder = rootFolders[0]?.path || '/tv';
@@ -186,7 +186,7 @@ module.exports = function(ctx, helpers) {
try {
const langRes = await ctx.fetchT(`${sonarrUrl}/api/v3/languageprofile`, {
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(5000)
});
if (langRes.ok) {
const langProfiles = await langRes.json();
@@ -212,20 +212,20 @@ module.exports = function(ctx, helpers) {
isDefault: true,
enableSeasonFolders: true,
externalUrl: connectedServices.sonarr.url,
tags: [],
tags: []
};
const sonarrRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/sonarr`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Cookie': overseerrCookie },
body: JSON.stringify(sonarrConfig),
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000)
});
steps.push({
step: 'Configure Sonarr in Overseerr',
status: sonarrRes.ok ? 'success' : 'failed',
details: sonarrRes.ok ? `Profile: ${defaultProfile.name}, Root: ${defaultRootFolder}` : await sonarrRes.text(),
details: sonarrRes.ok ? `Profile: ${defaultProfile.name}, Root: ${defaultRootFolder}` : await sonarrRes.text()
});
} catch (e) {
steps.push({ step: 'Configure Sonarr in Overseerr', status: 'failed', details: e.message });
@@ -239,7 +239,7 @@ module.exports = function(ctx, helpers) {
steps.push({
step: 'Connect Plex to Overseerr',
status: 'success',
details: `${plexResult.serverName} - ${plexResult.libraries.length} libraries synced`,
details: `${plexResult.serverName} - ${plexResult.libraries.length} libraries synced`
});
} catch (e) {
steps.push({ step: 'Connect Plex to Overseerr', status: 'failed', details: e.message });
@@ -259,13 +259,13 @@ module.exports = function(ctx, helpers) {
const prowlarrResults = await helpers.configureProwlarrApps(
connectedServices.prowlarr.url.replace(/\/+$/, ''),
connectedServices.prowlarr.apiKey,
appsToConnect,
appsToConnect
);
for (const [app, status] of Object.entries(prowlarrResults)) {
steps.push({
step: `Add ${app.charAt(0).toUpperCase() + app.slice(1)} to Prowlarr`,
status: status === 'configured' || status === 'already_configured' ? 'success' : 'failed',
details: status,
details: status
});
}
} catch (e) {
@@ -283,14 +283,14 @@ module.exports = function(ctx, helpers) {
'deploymentSuccess',
'Smart Arr Connect Complete',
`${succeeded}/${steps.length} steps completed successfully`,
'success',
'success'
);
}
res.json({
success: succeeded > 0,
steps,
summary: { totalSteps: steps.length, succeeded, failed },
summary: { totalSteps: steps.length, succeeded, failed }
});
}, 'smart-connect'));