feat(scripts): Add community scripts subsystem: script index, runner, and CLI commands with background refresh; update docs and paths
This commit is contained in:
170
ts/moxytool.classes.scriptrunner.ts
Normal file
170
ts/moxytool.classes.scriptrunner.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user