feat(scripts): Add community scripts subsystem: script index, runner, and CLI commands with background refresh; update docs and paths
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import * as plugins from './moxytool.plugins.ts';
|
||||
import * as paths from './moxytool.paths.ts';
|
||||
import { logger } from './moxytool.logging.ts';
|
||||
import { ScriptIndex } from './moxytool.classes.scriptindex.ts';
|
||||
import { ScriptRunner } from './moxytool.classes.scriptrunner.ts';
|
||||
|
||||
export const runCli = async () => {
|
||||
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||
@@ -9,12 +11,31 @@ export const runCli = async () => {
|
||||
|
||||
const smartcliInstance = new plugins.smartcli.Smartcli();
|
||||
|
||||
// Initialize script index and check if refresh is needed
|
||||
const scriptIndex = new ScriptIndex();
|
||||
|
||||
// Silently check and refresh index in the background if needed
|
||||
(async () => {
|
||||
try {
|
||||
await scriptIndex.loadCache();
|
||||
if (await scriptIndex.needsRefresh()) {
|
||||
// Don't block CLI startup, refresh in background
|
||||
scriptIndex.fetchIndex().catch(() => {
|
||||
// Silently fail, will use cached data
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Silently fail on index errors
|
||||
}
|
||||
})();
|
||||
|
||||
// Standard command (no arguments)
|
||||
smartcliInstance.standardCommand().subscribe(async () => {
|
||||
logger.log('info', 'MOXYTOOL - Proxmox Administration Tool');
|
||||
logger.log('info', '');
|
||||
logger.log('info', 'Available commands:');
|
||||
logger.log('info', '* vgpu-setup - Install and configure Proxmox vGPU support');
|
||||
logger.log('info', '* scripts - Manage Proxmox community scripts');
|
||||
logger.log('info', '');
|
||||
logger.log('info', 'Usage: moxytool <command> [options]');
|
||||
});
|
||||
@@ -108,5 +129,222 @@ export const runCli = async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Scripts management commands
|
||||
smartcliInstance.addCommand('scripts').subscribe(async (argvArg) => {
|
||||
const subcommand = argvArg._[0];
|
||||
|
||||
if (!subcommand) {
|
||||
logger.log('info', 'MOXYTOOL Scripts - Proxmox Community Scripts Management');
|
||||
logger.log('info', '');
|
||||
logger.log('info', 'Available subcommands:');
|
||||
logger.log('info', '* list - List all available scripts');
|
||||
logger.log('info', '* search <query> - Search for scripts by name or description');
|
||||
logger.log('info', '* info <slug> - Show detailed information about a script');
|
||||
logger.log('info', '* run <slug> - Execute a script');
|
||||
logger.log('info', '* refresh - Force refresh the script index');
|
||||
logger.log('info', '');
|
||||
logger.log('info', 'Usage: moxytool scripts <subcommand> [options]');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure index is loaded
|
||||
await scriptIndex.loadCache();
|
||||
|
||||
switch (subcommand) {
|
||||
case 'list': {
|
||||
const scripts = scriptIndex.getAll();
|
||||
|
||||
if (scripts.length === 0) {
|
||||
logger.log('warn', 'No scripts found. Run "moxytool scripts refresh" to fetch the index.');
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = scriptIndex.getStats();
|
||||
logger.log('info', `Available Scripts (${stats.count} total, indexed ${stats.age})`);
|
||||
logger.log('info', '');
|
||||
|
||||
// Group by type
|
||||
const containers = scripts.filter(s => s.type === 'ct');
|
||||
const vms = scripts.filter(s => s.type === 'vm');
|
||||
|
||||
if (containers.length > 0) {
|
||||
logger.log('info', 'Containers (LXC):');
|
||||
containers.forEach(script => {
|
||||
logger.log('info', ` • ${script.slug.padEnd(25)} - ${script.name}`);
|
||||
});
|
||||
logger.log('info', '');
|
||||
}
|
||||
|
||||
if (vms.length > 0) {
|
||||
logger.log('info', 'Virtual Machines:');
|
||||
vms.forEach(script => {
|
||||
logger.log('info', ` • ${script.slug.padEnd(25)} - ${script.name}`);
|
||||
});
|
||||
}
|
||||
|
||||
logger.log('info', '');
|
||||
logger.log('info', 'Use "moxytool scripts info <slug>" for more details');
|
||||
logger.log('info', 'Use "moxytool scripts run <slug>" to install');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'search': {
|
||||
const query = argvArg._[1];
|
||||
|
||||
if (!query) {
|
||||
logger.log('error', 'Please provide a search query');
|
||||
logger.log('info', 'Usage: moxytool scripts search <query>');
|
||||
return;
|
||||
}
|
||||
|
||||
const results = scriptIndex.search(query as string);
|
||||
|
||||
if (results.length === 0) {
|
||||
logger.log('warn', `No scripts found matching "${query}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('info', `Found ${results.length} script(s) matching "${query}":`);
|
||||
logger.log('info', '');
|
||||
|
||||
results.forEach(script => {
|
||||
logger.log('info', `${script.slug} (${script.type})`);
|
||||
logger.log('info', ` ${script.name}`);
|
||||
logger.log('info', ` ${script.description.substring(0, 80)}...`);
|
||||
logger.log('info', '');
|
||||
});
|
||||
|
||||
logger.log('info', 'Use "moxytool scripts info <slug>" for more details');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'info': {
|
||||
const slug = argvArg._[1];
|
||||
|
||||
if (!slug) {
|
||||
logger.log('error', 'Please provide a script slug');
|
||||
logger.log('info', 'Usage: moxytool scripts info <slug>');
|
||||
return;
|
||||
}
|
||||
|
||||
const script = scriptIndex.getBySlug(slug as string);
|
||||
|
||||
if (!script) {
|
||||
logger.log('error', `Script "${slug}" not found`);
|
||||
logger.log('info', 'Use "moxytool scripts search <query>" to find scripts');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('info', '═'.repeat(60));
|
||||
logger.log('info', `${script.name}`);
|
||||
logger.log('info', '═'.repeat(60));
|
||||
logger.log('info', '');
|
||||
logger.log('info', `Slug: ${script.slug}`);
|
||||
logger.log('info', `Type: ${script.type === 'ct' ? 'Container (LXC)' : 'Virtual Machine'}`);
|
||||
logger.log('info', '');
|
||||
logger.log('info', 'Description:');
|
||||
logger.log('info', script.description);
|
||||
logger.log('info', '');
|
||||
|
||||
if (script.install_methods && script.install_methods[0]?.resources) {
|
||||
const res = script.install_methods[0].resources;
|
||||
logger.log('info', 'Resource Requirements:');
|
||||
if (res.cpu) logger.log('info', ` CPU: ${res.cpu} cores`);
|
||||
if (res.ram) logger.log('info', ` RAM: ${res.ram} MB`);
|
||||
if (res.hdd) logger.log('info', ` Disk: ${res.hdd} GB`);
|
||||
if (res.os) logger.log('info', ` OS: ${res.os} ${res.version || ''}`);
|
||||
logger.log('info', '');
|
||||
}
|
||||
|
||||
if (script.interface_port) {
|
||||
logger.log('info', `Web Interface: http://<your-ip>:${script.interface_port}`);
|
||||
logger.log('info', '');
|
||||
}
|
||||
|
||||
if (script.default_credentials) {
|
||||
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}`);
|
||||
}
|
||||
logger.log('info', '');
|
||||
}
|
||||
|
||||
if (script.notes && script.notes.length > 0) {
|
||||
logger.log('info', 'Important Notes:');
|
||||
script.notes.forEach(note => {
|
||||
const prefix = note.type === 'warning' ? '⚠️ ' : 'ℹ️ ';
|
||||
logger.log('warn', `${prefix}${note.content}`);
|
||||
});
|
||||
logger.log('info', '');
|
||||
}
|
||||
|
||||
if (script.documentation) {
|
||||
logger.log('info', `Documentation: ${script.documentation}`);
|
||||
}
|
||||
if (script.website) {
|
||||
logger.log('info', `Website: ${script.website}`);
|
||||
}
|
||||
|
||||
logger.log('info', '');
|
||||
logger.log('info', `To install: sudo moxytool scripts run ${script.slug}`);
|
||||
logger.log('info', '═'.repeat(60));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'run': {
|
||||
const slug = argvArg._[1];
|
||||
|
||||
if (!slug) {
|
||||
logger.log('error', 'Please provide a script slug');
|
||||
logger.log('info', 'Usage: sudo moxytool scripts run <slug>');
|
||||
return;
|
||||
}
|
||||
|
||||
const script = scriptIndex.getBySlug(slug as string);
|
||||
|
||||
if (!script) {
|
||||
logger.log('error', `Script "${slug}" not found`);
|
||||
logger.log('info', 'Use "moxytool scripts search <query>" to find scripts');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate Proxmox host
|
||||
const runner = new ScriptRunner();
|
||||
const isProxmox = await runner.validateProxmoxHost();
|
||||
|
||||
if (!isProxmox) {
|
||||
logger.log('error', 'This system does not appear to be running Proxmox');
|
||||
logger.log('error', 'Community scripts can only be run on Proxmox hosts');
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
// Execute the script
|
||||
const exitCode = await runner.execute(script);
|
||||
Deno.exit(exitCode);
|
||||
}
|
||||
|
||||
case 'refresh': {
|
||||
logger.log('info', 'Refreshing script index...');
|
||||
|
||||
try {
|
||||
await scriptIndex.fetchIndex();
|
||||
const stats = scriptIndex.getStats();
|
||||
logger.log('success', `Index refreshed: ${stats.count} scripts cached`);
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to refresh index: ${error}`);
|
||||
Deno.exit(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.log('error', `Unknown subcommand: ${subcommand}`);
|
||||
logger.log('info', 'Run "moxytool scripts" to see available subcommands');
|
||||
}
|
||||
});
|
||||
|
||||
smartcliInstance.startParse();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user