Skip to content

Commit b130fb2

Browse files
committed
Handle per-method script download failures
Wrap each install-method file download in its own try/catch so a missing or failing variant no longer aborts the entire script load. The service now records method-level errors, continues downloading remaining methods, and includes a combined error message in the result when no files were downloaded.
1 parent f381e79 commit b130fb2

1 file changed

Lines changed: 68 additions & 53 deletions

File tree

‎src/server/services/scriptDownloader.js‎

Lines changed: 68 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -223,67 +223,79 @@ export class ScriptDownloaderService {
223223
await this.ensureDirectoryExists(join(this.scriptsDirectory, 'tools'));
224224
await this.ensureDirectoryExists(join(this.scriptsDirectory, 'vm'));
225225

226+
/** @type {string[]} */
227+
const methodErrors = [];
228+
226229
if (script.install_methods?.length) {
227230
for (const method of script.install_methods) {
228231
const scriptPath = this.resolveScriptPath(script, method);
229232
if (scriptPath) {
230233
const fileName = scriptPath.split('/').pop();
231234

232235
if (fileName) {
233-
console.log(`Downloading script file: ${scriptPath} from ${repoUrl}`);
234-
const content = await this.downloadFileFromRepo(repoUrl, scriptPath, branch);
235-
236-
// Determine target directory based on script path
237-
let targetDir;
238-
let finalTargetDir;
239-
let filePath;
236+
try {
237+
console.log(`Downloading script file: ${scriptPath} from ${repoUrl}`);
238+
const content = await this.downloadFileFromRepo(repoUrl, scriptPath, branch);
239+
240+
// Determine target directory based on script path
241+
let targetDir;
242+
let finalTargetDir;
243+
let filePath;
244+
245+
if (scriptPath.startsWith('ct/')) {
246+
targetDir = 'ct';
247+
finalTargetDir = targetDir;
248+
// Validate and sanitize finalTargetDir
249+
finalTargetDir = this.validateTargetDir(targetDir, finalTargetDir);
250+
// Modify the content for CT scripts
251+
const modifiedContent = this.modifyScriptContent(content);
252+
filePath = join(this.scriptsDirectory, targetDir, fileName);
253+
await writeFile(filePath, modifiedContent, 'utf-8');
254+
} else if (scriptPath.startsWith('tools/')) {
255+
targetDir = 'tools';
256+
// Preserve subdirectory structure for tools scripts
257+
const subPath = scriptPath.replace('tools/', '');
258+
const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : '';
259+
finalTargetDir = subDir ? join(targetDir, subDir) : targetDir;
260+
// Validate and sanitize finalTargetDir
261+
finalTargetDir = this.validateTargetDir(targetDir, finalTargetDir);
262+
// Ensure the subdirectory exists
263+
await this.ensureDirectoryExists(join(this.scriptsDirectory, finalTargetDir));
264+
filePath = join(this.scriptsDirectory, finalTargetDir, fileName);
265+
await writeFile(filePath, content, 'utf-8');
266+
} else if (scriptPath.startsWith('vm/')) {
267+
targetDir = 'vm';
268+
// Preserve subdirectory structure for VM scripts
269+
const subPath = scriptPath.replace('vm/', '');
270+
const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : '';
271+
finalTargetDir = subDir ? join(targetDir, subDir) : targetDir;
272+
// Validate and sanitize finalTargetDir
273+
finalTargetDir = this.validateTargetDir(targetDir, finalTargetDir);
274+
// Ensure the subdirectory exists
275+
await this.ensureDirectoryExists(join(this.scriptsDirectory, finalTargetDir));
276+
filePath = join(this.scriptsDirectory, finalTargetDir, fileName);
277+
await writeFile(filePath, content, 'utf-8');
278+
} else {
279+
// Handle other script types (fallback to ct directory)
280+
targetDir = 'ct';
281+
finalTargetDir = targetDir;
282+
// Validate and sanitize finalTargetDir
283+
finalTargetDir = this.validateTargetDir(targetDir, finalTargetDir);
284+
const modifiedContent = this.modifyScriptContent(content);
285+
filePath = join(this.scriptsDirectory, targetDir, fileName);
286+
await writeFile(filePath, modifiedContent, 'utf-8');
287+
}
240288

241-
if (scriptPath.startsWith('ct/')) {
242-
targetDir = 'ct';
243-
finalTargetDir = targetDir;
244-
// Validate and sanitize finalTargetDir
245-
finalTargetDir = this.validateTargetDir(targetDir, finalTargetDir);
246-
// Modify the content for CT scripts
247-
const modifiedContent = this.modifyScriptContent(content);
248-
filePath = join(this.scriptsDirectory, targetDir, fileName);
249-
await writeFile(filePath, modifiedContent, 'utf-8');
250-
} else if (scriptPath.startsWith('tools/')) {
251-
targetDir = 'tools';
252-
// Preserve subdirectory structure for tools scripts
253-
const subPath = scriptPath.replace('tools/', '');
254-
const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : '';
255-
finalTargetDir = subDir ? join(targetDir, subDir) : targetDir;
256-
// Validate and sanitize finalTargetDir
257-
finalTargetDir = this.validateTargetDir(targetDir, finalTargetDir);
258-
// Ensure the subdirectory exists
259-
await this.ensureDirectoryExists(join(this.scriptsDirectory, finalTargetDir));
260-
filePath = join(this.scriptsDirectory, finalTargetDir, fileName);
261-
await writeFile(filePath, content, 'utf-8');
262-
} else if (scriptPath.startsWith('vm/')) {
263-
targetDir = 'vm';
264-
// Preserve subdirectory structure for VM scripts
265-
const subPath = scriptPath.replace('vm/', '');
266-
const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : '';
267-
finalTargetDir = subDir ? join(targetDir, subDir) : targetDir;
268-
// Validate and sanitize finalTargetDir
269-
finalTargetDir = this.validateTargetDir(targetDir, finalTargetDir);
270-
// Ensure the subdirectory exists
271-
await this.ensureDirectoryExists(join(this.scriptsDirectory, finalTargetDir));
272-
filePath = join(this.scriptsDirectory, finalTargetDir, fileName);
273-
await writeFile(filePath, content, 'utf-8');
274-
} else {
275-
// Handle other script types (fallback to ct directory)
276-
targetDir = 'ct';
277-
finalTargetDir = targetDir;
278-
// Validate and sanitize finalTargetDir
279-
finalTargetDir = this.validateTargetDir(targetDir, finalTargetDir);
280-
const modifiedContent = this.modifyScriptContent(content);
281-
filePath = join(this.scriptsDirectory, targetDir, fileName);
282-
await writeFile(filePath, modifiedContent, 'utf-8');
289+
files.push(`${finalTargetDir}/${fileName}`);
290+
console.log(`Successfully downloaded: ${finalTargetDir}/${fileName}`);
291+
} catch (methodError) {
292+
// Don't let one missing install-method file (e.g. a 404 for an
293+
// alpine/default variant) abort the whole script download —
294+
// record it and keep trying the remaining install methods.
295+
const msg = methodError instanceof Error ? methodError.message : String(methodError);
296+
console.error(`Failed to download script file ${scriptPath}: ${msg}`);
297+
methodErrors.push(`${scriptPath}: ${msg}`);
283298
}
284-
285-
files.push(`${finalTargetDir}/${fileName}`);
286-
console.log(`Successfully downloaded: ${finalTargetDir}/${fileName}`);
287299
}
288300
}
289301
}
@@ -384,7 +396,10 @@ export class ScriptDownloaderService {
384396
message: files.length > 0
385397
? `Successfully loaded ${files.length} script(s) for ${script.name}`
386398
: `No script files found in repository for ${script.name}. The script may not be available yet.`,
387-
files
399+
files,
400+
...(files.length === 0 && methodErrors.length > 0
401+
? { error: `No script files could be downloaded for ${script.name}: ${methodErrors.join('; ')}` }
402+
: {}),
388403
};
389404
} catch (error) {
390405
console.error('Error loading script:', error);

0 commit comments

Comments
 (0)