@@ -887,77 +887,142 @@ export const installedScriptsRouter = createTRPCRouter({
887887 ) ;
888888
889889
890+ // Group scripts by server to batch check containers
891+ const scriptsByServer = new Map < number , any [ ] > ( ) ;
890892 for ( const script of scriptsToCheck ) {
893+ const scriptData = script as any ;
894+ if ( ! scriptData . server_id ) continue ;
895+
896+ if ( ! scriptsByServer . has ( scriptData . server_id ) ) {
897+ scriptsByServer . set ( scriptData . server_id , [ ] ) ;
898+ }
899+ scriptsByServer . get ( scriptData . server_id ) ! . push ( scriptData ) ;
900+ }
901+
902+ // Process each server
903+ for ( const [ serverId , serverScripts ] of scriptsByServer . entries ( ) ) {
891904 try {
892- const scriptData = script as any ;
893- const server = allServers . find ( ( s : any ) => s . id === scriptData . server_id ) ;
905+ const server = allServers . find ( ( s : any ) => s . id === serverId ) ;
894906 if ( ! server ) {
895- await db . deleteInstalledScript ( Number ( scriptData . id ) ) ;
896- deletedScripts . push ( String ( scriptData . script_name ) ) ;
907+ // Server doesn't exist, delete all scripts for this server
908+ for ( const scriptData of serverScripts ) {
909+ await db . deleteInstalledScript ( Number ( scriptData . id ) ) ;
910+ deletedScripts . push ( String ( scriptData . script_name ) ) ;
911+ }
897912 continue ;
898913 }
899914
900-
901915 // Test SSH connection
902-
903916 const connectionTest = await sshService . testSSHConnection ( server as Server ) ;
904917 if ( ! ( connectionTest as any ) . success ) {
918+ console . warn ( `cleanupOrphanedScripts: SSH connection failed for server ${ String ( ( server as any ) . name ) } , skipping ${ serverScripts . length } scripts` ) ;
905919 continue ;
906920 }
907921
908- // Check if the container config file still exists
909- const checkCommand = `test -f "/etc/pve/lxc/${ scriptData . container_id } .conf" && echo "exists" || echo "not_found"` ;
922+ // Get all existing containers from pct list (more reliable than checking config files)
923+ const listCommand = 'pct list' ;
924+ let listOutput = '' ;
910925
911- // Await full command completion to avoid early false negatives
912- const containerExists = await new Promise < boolean > ( ( resolve ) => {
913- let combinedOutput = '' ;
914- let resolved = false ;
915-
916- const finish = ( ) => {
917- if ( resolved ) return ;
918- resolved = true ;
919- const out = combinedOutput . trim ( ) ;
920- if ( out . includes ( 'exists' ) ) {
921- resolve ( true ) ;
922- } else if ( out . includes ( 'not_found' ) ) {
923- resolve ( false ) ;
924- } else {
925- // Unknown output; treat as not found but log for diagnostics
926- console . warn ( `cleanupOrphanedScripts: unexpected output for ${ String ( scriptData . script_name ) } (${ String ( scriptData . container_id ) } ): ${ out } ` ) ;
927- resolve ( false ) ;
928- }
929- } ;
930-
931- // Add a guard timeout so we don't hang indefinitely
932- const timer = setTimeout ( ( ) => {
933- console . warn ( `cleanupOrphanedScripts: timeout while checking ${ String ( scriptData . script_name ) } on server ${ String ( ( server as any ) . name ) } ` ) ;
934- finish ( ) ;
935- } , 15000 ) ;
926+ const existingContainerIds = await new Promise < Set < string > > ( ( resolve , reject ) => {
927+ const timeout = setTimeout ( ( ) => {
928+ console . warn ( `cleanupOrphanedScripts: timeout while getting container list from server ${ String ( ( server as any ) . name ) } ` ) ;
929+ resolve ( new Set ( ) ) ; // Treat timeout as no containers found
930+ } , 20000 ) ;
936931
937932 void sshExecutionService . executeCommand (
938933 server as Server ,
939- checkCommand ,
934+ listCommand ,
940935 ( data : string ) => {
941- combinedOutput += data ;
936+ listOutput += data ;
942937 } ,
943938 ( error : string ) => {
944- combinedOutput += error ;
939+ console . error ( `cleanupOrphanedScripts: error getting container list from server ${ String ( ( server as any ) . name ) } :` , error ) ;
940+ clearTimeout ( timeout ) ;
941+ resolve ( new Set ( ) ) ; // Treat error as no containers found
945942 } ,
946943 ( _exitCode : number ) => {
947- clearTimeout ( timer ) ;
948- finish ( ) ;
944+ clearTimeout ( timeout ) ;
945+
946+ // Parse pct list output to extract container IDs
947+ const containerIds = new Set < string > ( ) ;
948+ const lines = listOutput . split ( '\n' ) . filter ( line => line . trim ( ) ) ;
949+
950+ for ( const line of lines ) {
951+ // pct list format: CTID Status Name
952+ // Skip header line if present
953+ if ( line . includes ( 'CTID' ) || line . includes ( 'VMID' ) ) continue ;
954+
955+ const parts = line . trim ( ) . split ( / \s + / ) ;
956+ if ( parts . length > 0 ) {
957+ const containerId = parts [ 0 ] ?. trim ( ) ;
958+ if ( containerId && / ^ \d { 3 , 4 } $ / . test ( containerId ) ) {
959+ containerIds . add ( containerId ) ;
960+ }
961+ }
962+ }
963+
964+ resolve ( containerIds ) ;
949965 }
950966 ) ;
951967 } ) ;
952968
953- if ( ! containerExists ) {
954- await db . deleteInstalledScript ( Number ( scriptData . id ) ) ;
955- deletedScripts . push ( String ( scriptData . script_name ) ) ;
956- } else {
957- }
969+ // Check each script against the list of existing containers
970+ for ( const scriptData of serverScripts ) {
971+ try {
972+ const containerId = String ( scriptData . container_id ) . trim ( ) ;
973+
974+ // Check if container exists in pct list
975+ if ( ! existingContainerIds . has ( containerId ) ) {
976+ // Also verify config file doesn't exist as a double-check
977+ const checkCommand = `test -f "/etc/pve/lxc/${ containerId } .conf" && echo "exists" || echo "not_found"` ;
978+
979+ const configExists = await new Promise < boolean > ( ( resolve ) => {
980+ let combinedOutput = '' ;
981+ let resolved = false ;
982+
983+ const finish = ( ) => {
984+ if ( resolved ) return ;
985+ resolved = true ;
986+ const out = combinedOutput . trim ( ) ;
987+ resolve ( out . includes ( 'exists' ) ) ;
988+ } ;
989+
990+ const timer = setTimeout ( ( ) => {
991+ finish ( ) ;
992+ } , 10000 ) ;
958993
994+ void sshExecutionService . executeCommand (
995+ server as Server ,
996+ checkCommand ,
997+ ( data : string ) => {
998+ combinedOutput += data ;
999+ } ,
1000+ ( _error : string ) => {
1001+ // Ignore errors, just check output
1002+ } ,
1003+ ( _exitCode : number ) => {
1004+ clearTimeout ( timer ) ;
1005+ finish ( ) ;
1006+ }
1007+ ) ;
1008+ } ) ;
1009+
1010+ // If container is not in pct list AND config file doesn't exist, it's orphaned
1011+ if ( ! configExists ) {
1012+ console . log ( `cleanupOrphanedScripts: Removing orphaned script ${ String ( scriptData . script_name ) } (container ${ containerId } ) from server ${ String ( ( server as any ) . name ) } ` ) ;
1013+ await db . deleteInstalledScript ( Number ( scriptData . id ) ) ;
1014+ deletedScripts . push ( String ( scriptData . script_name ) ) ;
1015+ } else {
1016+ // Config exists but not in pct list - might be in a transitional state, log but don't delete
1017+ console . warn ( `cleanupOrphanedScripts: Container ${ containerId } (${ String ( scriptData . script_name ) } ) config exists but not in pct list - may be in transitional state` ) ;
1018+ }
1019+ }
1020+ } catch ( error ) {
1021+ console . error ( `cleanupOrphanedScripts: Error checking script ${ String ( ( scriptData as any ) . script_name ) } :` , error ) ;
1022+ }
1023+ }
9591024 } catch ( error ) {
960- console . error ( `Error checking script ${ ( script as any ) . script_name } :` , error ) ;
1025+ console . error ( `cleanupOrphanedScripts: Error processing server ${ serverId } :` , error ) ;
9611026 }
9621027 }
9631028
0 commit comments