171 lines
5.3 KiB
TypeScript
171 lines
5.3 KiB
TypeScript
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<number> {
|
||
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 <url>)"
|
||
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://<your-ip>:${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<boolean> {
|
||
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<string | null> {
|
||
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;
|
||
}
|
||
}
|
||
}
|