@@ -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