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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|