import * as plugins from './moxytool.plugins.ts'; import { logger } from './moxytool.logging.ts'; import type { IScriptMetadata } from './moxytool.classes.scriptindex.ts'; /** * ScriptRunner class handles the execution of Proxmox community scripts * - Executes scripts via bash with curl * - Ensures proper stdin/stdout/stderr passthrough for interactive prompts * - Handles script exit codes */ export class ScriptRunner { private static readonly SCRIPT_BASE_URL = 'https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main'; /** * Execute a community script * @param script The script metadata * @returns The exit code of the script */ public async execute(script: IScriptMetadata): Promise { try { // Get the script URL from install_methods if (!script.install_methods || script.install_methods.length === 0) { logger.log('error', 'Script has no install methods defined'); return 1; } const installMethod = script.install_methods[0]; const scriptPath = installMethod.script; if (!scriptPath) { logger.log('error', 'Script path is not defined'); return 1; } // Construct the full script URL const scriptUrl = `${ScriptRunner.SCRIPT_BASE_URL}${scriptPath}`; logger.log('info', `Executing script: ${script.name}`); logger.log('info', `URL: ${scriptUrl}`); logger.log('info', ''); // Show script details if (script.description) { logger.log('info', `Description: ${script.description}`); } if (script.notes && script.notes.length > 0) { logger.log('info', ''); logger.log('info', 'Important Notes:'); for (const note of script.notes) { const prefix = note.type === 'warning' ? '⚠️ ' : 'ℹ️ '; logger.log('warn', `${prefix}${note.content}`); } } if (installMethod.resources) { logger.log('info', ''); logger.log('info', 'Resource Requirements:'); if (installMethod.resources.cpu) { logger.log('info', ` CPU: ${installMethod.resources.cpu} cores`); } if (installMethod.resources.ram) { logger.log('info', ` RAM: ${installMethod.resources.ram} MB`); } if (installMethod.resources.hdd) { logger.log('info', ` Disk: ${installMethod.resources.hdd} GB`); } if (installMethod.resources.os) { logger.log( 'info', ` OS: ${installMethod.resources.os} ${installMethod.resources.version || ''}`, ); } } logger.log('info', ''); logger.log('info', 'Starting installation...'); logger.log('info', '═'.repeat(60)); logger.log('info', ''); // Execute the script using smartshell // The command structure: bash -c "$(curl -fsSL )" const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); // Construct the command that will be executed const command = `bash -c "$(curl -fsSL ${scriptUrl})"`; // Execute with inherited stdio for full interactivity const result = await smartshellInstance.exec(command); logger.log('info', ''); logger.log('info', '═'.repeat(60)); if (result.exitCode === 0) { logger.log('success', `✓ Script completed successfully`); if (script.interface_port) { logger.log('info', ''); logger.log('info', `Access the service at: http://:${script.interface_port}`); } if (script.default_credentials) { logger.log('info', ''); logger.log('info', 'Default Credentials:'); if (script.default_credentials.username) { logger.log('info', ` Username: ${script.default_credentials.username}`); } if (script.default_credentials.password) { logger.log('info', ` Password: ${script.default_credentials.password}`); } } if (script.documentation) { logger.log('info', ''); logger.log('info', `Documentation: ${script.documentation}`); } } else { logger.log('error', `✗ Script failed with exit code: ${result.exitCode}`); if (result.stderr) { logger.log('error', `Error output: ${result.stderr}`); } } return result.exitCode; } catch (error) { logger.log('error', `Failed to execute script: ${error}`); return 1; } } /** * Validate that we're running on a Proxmox host */ public async validateProxmoxHost(): Promise { try { const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); const result = await smartshellInstance.exec('which pveversion'); return result.exitCode === 0; } catch { return false; } } /** * Get Proxmox version information */ public async getProxmoxVersion(): Promise { try { const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); const result = await smartshellInstance.exec('pveversion'); if (result.exitCode === 0 && result.stdout) { return result.stdout.trim(); } return null; } catch { return null; } } }