Files
moxytool/ts/moxytool.cli.ts

446 lines
16 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
import denoConfig from '../deno.json' with { type: 'json' };
export const runCli = async () => {
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
});
const smartcliInstance = new plugins.smartcli.Smartcli();
smartcliInstance.version = denoConfig.version;
// 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 () => {
console.log('\x1b[1m\x1b[36m╔════════════════════════════════════════════╗\x1b[0m');
console.log('\x1b[1m\x1b[36m║\x1b[0m \x1b[1mMOXYTOOL\x1b[0m - Proxmox Administration \x1b[1m\x1b[36m║\x1b[0m');
console.log('\x1b[1m\x1b[36m╚════════════════════════════════════════════╝\x1b[0m');
console.log('');
console.log('\x1b[1mCommands:\x1b[0m');
console.log(' \x1b[36m►\x1b[0m vgpu-setup Install and configure Proxmox vGPU support');
console.log(' \x1b[36m►\x1b[0m scripts Manage Proxmox community scripts (400+)');
console.log(' \x1b[36m►\x1b[0m update Update MOXYTOOL to the latest version');
console.log('');
console.log('\x1b[2mUsage: moxytool <command> [options]\x1b[0m');
});
// vGPU setup command
smartcliInstance.addCommand('vgpu-setup').subscribe(async (argvArg) => {
logger.log('ok', 'Starting Proxmox vGPU setup process');
logger.log('info', 'This will install vGPU support for NVIDIA GPUs on Proxmox');
logger.log('info', '');
// Check for arguments
const step = argvArg.step;
const url = argvArg.url;
const file = argvArg.file;
const debug = argvArg.debug;
// Check if running on Proxmox
try {
const result = await smartshellInstance.exec('which pveversion');
if (result.exitCode !== 0) {
logger.log('error', 'This system does not appear to be running Proxmox');
logger.log('error', 'Please run this command on a Proxmox host');
Deno.exit(1);
}
} catch (e) {
logger.log('error', 'Failed to verify Proxmox installation');
logger.log('error', 'Please ensure you are running this on a Proxmox host');
Deno.exit(1);
}
// Create temporary directory
const tmpDir = '/tmp/moxytool-vgpu-setup';
await smartshellInstance.exec(`mkdir -p ${tmpDir}`);
try {
// Step 1: Clone and run the installer script
logger.log('info', 'Step 1: Downloading Proxmox vGPU installer (supports v9)...');
const cloneResult = await smartshellInstance.exec(
`cd ${tmpDir} && git clone https://github.com/anomixer/proxmox-vgpu-installer.git`,
);
if (cloneResult.exitCode !== 0) {
logger.log('error', 'Failed to clone the installer repository');
logger.log('error', cloneResult.stderr || 'Unknown error');
Deno.exit(1);
}
logger.log('ok', 'Installer downloaded successfully');
logger.log('info', '');
// Build command with arguments
let command = 'bash proxmox-installer.sh';
if (step) {
command += ` --step ${step}`;
}
if (url) {
command += ` --url ${url}`;
}
if (file) {
command += ` --file ${file}`;
}
if (debug) {
command += ' --debug';
}
logger.log('info', 'Running installer script...');
logger.log('info', 'Please follow the interactive prompts');
logger.log('info', '');
// Run the installer script interactively
const installResult = await smartshellInstance.exec(
`cd ${tmpDir}/proxmox-vgpu-installer && ${command}`,
);
if (installResult.exitCode !== 0) {
logger.log('error', 'Installer script failed');
logger.log('error', installResult.stderr || 'Unknown error');
Deno.exit(1);
}
logger.log('ok', 'vGPU setup process completed');
logger.log('info', '');
logger.log('info', 'Next steps:');
logger.log('info', '1. If prompted, reboot your system');
logger.log('info', '2. After reboot, run this command again to continue setup');
logger.log('info', '3. Verify installation with: mdevctl types');
logger.log('info', '4. Configure your VMs with vGPU in the Proxmox web UI');
} catch (error) {
logger.log('error', `Setup failed: ${error instanceof Error ? error.message : String(error)}`);
Deno.exit(1);
}
});
// Update command
smartcliInstance.addCommand('update').subscribe(async (argvArg) => {
logger.log('info', 'Checking for updates...');
logger.log('info', '');
try {
// Get current version from deno.json
const denoJsonPath = plugins.path.join(paths.packageDir, 'deno.json');
let currentVersion = '1.1.0'; // fallback
try {
const denoJsonContent = await Deno.readTextFile(denoJsonPath);
const denoJson = JSON.parse(denoJsonContent);
currentVersion = denoJson.version || currentVersion;
} catch {
// Use fallback version
}
// Fetch latest version from Gitea API
const apiUrl = 'https://code.foss.global/api/v1/repos/serve.zone/moxytool/releases/latest';
const response = await fetch(apiUrl);
if (!response.ok) {
logger.log('error', 'Failed to check for updates');
logger.log('error', `HTTP ${response.status}: ${response.statusText}`);
Deno.exit(1);
}
const release = await response.json();
const latestVersion = release.tag_name; // e.g., "v1.1.0"
// Normalize versions for comparison (ensure both have "v" prefix)
const normalizedCurrent = currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}`;
const normalizedLatest = latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}`;
logger.log('info', `Current version: ${normalizedCurrent}`);
logger.log('info', `Latest version: ${normalizedLatest}`);
logger.log('info', '');
// Compare normalized versions
if (normalizedCurrent === normalizedLatest) {
logger.log('success', 'Already up to date!');
logger.log('info', '');
return;
}
logger.log('ok', `New version available: ${latestVersion}`);
logger.log('info', 'Downloading and installing...');
logger.log('info', '');
// Download and run the install script
const installUrl = 'https://code.foss.global/serve.zone/moxytool/raw/branch/main/install.sh';
const updateResult = await smartshellInstance.exec(
`curl -sSL ${installUrl} | bash`
);
if (updateResult.exitCode !== 0) {
logger.log('error', 'Update failed');
logger.log('error', updateResult.stderr || 'Unknown error');
Deno.exit(1);
}
logger.log('info', '');
logger.log('success', `Updated to ${latestVersion}`);
logger.log('info', '');
} catch (error) {
logger.log('error', `Update failed: ${error instanceof Error ? error.message : String(error)}`);
Deno.exit(1);
}
});
// Scripts management commands
smartcliInstance.addCommand('scripts').subscribe(async (argvArg) => {
const subcommand = argvArg._[1]; // _[0] is 'scripts', _[1] is the subcommand
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 pveScripts = scripts.filter(s => s.type === 'pve');
const containers = scripts.filter(s => s.type === 'ct');
const vms = scripts.filter(s => s.type === 'vm');
const otherScripts = scripts.filter(s => s.type !== 'pve' && s.type !== 'ct' && s.type !== 'vm');
if (pveScripts.length > 0) {
logger.log('info', 'Proxmox VE Host Scripts:');
pveScripts.forEach(script => {
logger.log('info', `${script.slug.padEnd(25)} - ${script.name}`);
});
logger.log('info', '');
}
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', '');
}
if (otherScripts.length > 0) {
logger.log('info', 'Other:');
otherScripts.forEach(script => {
logger.log('info', `${script.slug.padEnd(25)} - ${script.name} (${script.type})`);
});
}
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._[2]; // _[0]=scripts, _[1]=search, _[2]=query
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._[2]; // _[0]=scripts, _[1]=info, _[2]=slug
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._[2]; // _[0]=scripts, _[1]=run, _[2]=slug
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();
};