cli/index.js

452 lines
17 KiB
JavaScript
Raw Normal View History

2024-10-31 02:19:42 +00:00
#!/usr/bin/env node
import inquirer from 'inquirer';
import { exec } from 'child_process';
import { promisify } from 'util';
import os from 'os';
import { createSpinner } from 'nanospinner';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import axios from 'axios';
2024-10-31 15:27:33 +00:00
import * as fs from 'fs/promises';
2024-10-31 02:19:42 +00:00
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const execAsync = promisify(exec);
const platform = os.platform();
const ASCII_ART = `
+--------------------------------------------------------------------+
| Docs : docs.codex.storage | Discord : discord.gg/codex-storage |
+--------------------------------------------------------------------+
`;
async function runCommand(command) {
try {
const { stdout, stderr } = await execAsync(command);
return stdout;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
async function showNavigationMenu() {
console.log('\n')
const { choice } = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: 'What would you like to do?',
choices: [
'1. Back to main menu',
'2. Exit'
]
}
]);
switch (choice.split('.')[0]) {
case '1':
return main(); // Returns to main menu
case '2':
console.log(chalk.cyanBright('\nGoodbye! 👋\n'));
process.exit(0);
}
}
async function checkCodexInstallation() {
try {
const version = await runCommand('codex --version');
console.log(chalk.green('Codex is already installed. Version:'));
console.log(chalk.green(version));
await showNavigationMenu();
} catch (error) {
console.log(chalk.cyanBright('Codex is not installed, proceeding with installation...'));
await installCodex();
}
}
async function installCodex() { // TODO : TEST INSTALLATION TO SEE IF BACKGROUND SHELL WORKS CORRECTLY
if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!'));
return;
}
try {
console.log(chalk.cyanBright('Downloading Codex binaries...'));
const downloadCommand = 'curl -# -L https://get.codex.storage/install.sh | bash';
await runCommand(downloadCommand);
console.log(chalk.green('Codex binaries downloaded'));
2024-10-31 15:27:33 +00:00
console.log(chalk.cyanBright('\nInstalling dependencies...'));
2024-10-31 02:19:42 +00:00
await runCommand('sudo apt update && sudo apt install libgomp1');
2024-10-31 15:27:33 +00:00
console.log(chalk.green('Dependencies installed successfully!'));
const version = await runCommand('\ncodex --version');
console.log(chalk.green('Codex is successfully installed!'));
console.log(chalk.cyanBright(version));
await showNavigationMenu();
2024-10-31 02:19:42 +00:00
} catch (error) {
console.error(chalk.red('Failed to install Codex:', error.message));
2024-10-31 15:27:33 +00:00
await showNavigationMenu();
2024-10-31 02:19:42 +00:00
}
}
async function isNodeRunning() {
try {
const response = await axios.get('http://localhost:8080/api/codex/v1/debug/info');
return response.status === 200;
} catch (error) {
return false;
}
}
async function runCodex() {
// Check if a Codex node is already running
const nodeAlreadyRunning = await isNodeRunning();
if (nodeAlreadyRunning) {
console.log(chalk.green('A Codex node is already running.'));
await showNavigationMenu();
}
else {
2024-10-31 15:27:33 +00:00
2024-10-31 02:19:42 +00:00
const { discPort, listenPort } = await inquirer.prompt([
{
type: 'number',
name: 'discPort',
message: 'Enter the discovery port (default is 8090):',
default: 8090
},
{
type: 'number',
name: 'listenPort',
message: 'Enter the listening port (default is 8070):',
default: 8070
}
]);
try {
const command = `codex \
--data-dir=datadir \
--disc-port=${discPort} \
--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort} \
--nat=\`curl -s https://ip.codex.storage\` \
--api-cors-origin="*" \
--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`;
2024-10-31 14:16:24 +00:00
console.log(chalk.cyanBright('\n\n Codex node is running...'));
2024-10-31 02:19:42 +00:00
console.log(chalk.cyanBright('\n Please keep this terminal open. Start a new terminal to start interacting with the node'));
console.log(chalk.cyanBright('\n Press CTRL+C to stop the node'));
await runCommand(command);
// TODO : HANDLE THIS CHECKING PART
const peerIdResponse = await runCommand('curl http://localhost:8080/api/codex/v1/peerid -w \'\\n\'');
console.log(chalk.green('Codex node is successfully running. Peer ID:'));
console.log(chalk.cyanBright(peerIdResponse.trim()));
await showNavigationMenu();
} catch (error) {
console.error(chalk.red('Failed to run Codex:', error.message));
await showNavigationMenu();
}
}
}
async function checkNodeStatus() {
if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!'));
return;
}
try {
// Check if a Codex node is already running
2024-10-31 02:57:39 +00:00
const nodeRunning = await isNodeRunning();
if (nodeRunning) {
2024-10-31 02:19:42 +00:00
const spinner = createSpinner('Checking node status...').start();
const response = await runCommand('curl http://localhost:8080/api/codex/v1/debug/info -w \'\\n\'');
spinner.success();
2024-10-31 02:57:39 +00:00
2024-10-31 02:19:42 +00:00
// Parse the JSON response
const data = JSON.parse(response);
// Determine if node is online and discoverable based on the connected peers
const peerCount = data.table.nodes.length;
const isOnline = peerCount > 2;
// Display node status based on connected peers
const statusMessage = isOnline
2024-10-31 02:57:39 +00:00
? chalk.bgGreen(" Node status : ONLINE & DISCOVERABLE ")
: chalk.bgRed(" Node status : OFFLINE ");
2024-10-31 02:19:42 +00:00
const peerMessage = `Connected peers : ${peerCount}`;
console.log('\n' + chalk.bold.cyanBright('📊 Node Status Summary'));
console.log('━'.repeat(50));
// Version Information
console.log(chalk.bold.cyanBright('🔹 Version Info'));
console.log(` Version: ${data.codex.version}`);
console.log(` Revision: ${data.codex.revision}\n`);
// Local Node Information
console.log(chalk.bold.cyanBright('🔹 Local Node'));
console.log(` Node ID: ${data.table.localNode.nodeId}`);
console.log(` Peer ID: ${data.table.localNode.peerId}`);
console.log(` Listening Address: ${data.table.localNode.address}\n`);
// Network Information
console.log(chalk.bold.cyanBright('🔹 Network Status'));
console.log(` Public IP: ${data.announceAddresses[0].split('/')[2]}`);
console.log(` Port: ${data.announceAddresses[0].split('/')[4]}\n`);
// Connected Peers Details
if (peerCount > 0) {
console.log(chalk.bold.cyanBright('🔹 Connected Peers'));
data.table.nodes.forEach((node, index) => {
console.log(` ${index + 1}. Peer ${chalk.cyan(node.peerId)}`);
console.log(` Address: ${node.address}`);
console.log(` Status: ${node.seen ? chalk.green('Active') : chalk.gray('Inactive')}`);
if (index < peerCount - 1) console.log(''); // Add spacing between peers
});
} else {
console.log(chalk.red('No connected peers.'));
}
console.log('━'.repeat(50));
await showNavigationMenu();
2024-10-31 02:57:39 +00:00
}
else{
console.log(chalk.red('\nOops...Codex node is not running. Try again after starting the node'));
2024-10-31 02:57:39 +00:00
await showNavigationMenu();
}
2024-10-31 02:19:42 +00:00
} catch (error) {
console.error(chalk.red('Failed to check node status:', error.message));
await showNavigationMenu();
}
}
async function uploadFile() {
if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!'));
return;
}
const nodeRunning = await isNodeRunning();
2024-10-31 02:19:42 +00:00
if (nodeRunning) {
2024-10-31 02:19:42 +00:00
console.log(chalk.bgYellow('\n ⚠️ Warning: Codex does not encrypt files. Anything uploaded will be available publicly on testnet. The testnet does not provide any guarentees - please do not use in production ⚠️ \n'));
const { filePath } = await inquirer.prompt([
{
type: 'input',
name: 'filePath',
message: 'Enter the file path to upload:',
validate: input => input.length > 0
}
]);
try {
const spinner = createSpinner('Uploading file').start();
// TODO : Upload along with metadata like file name, extension etc.,
const result = await runCommand(`curl -X POST http://localhost:8080/api/codex/v1/data -H 'Content-Type: application/octet-stream' -w '\\n' -T ${filePath}`);
spinner.success();
console.log(chalk.green('Successfully uploaded!'));
console.log(chalk.bgGreen('\nCID:', result.trim()));
await showNavigationMenu();
} catch (error) {
console.error(chalk.red('Failed to upload file:', error.message));
await showNavigationMenu();
}
}
else{
console.log(chalk.red('\nOops...Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
}
}
2024-10-31 02:19:42 +00:00
async function downloadFile() {
if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!'));
return;
}
const nodeRunning = await isNodeRunning();
if (nodeRunning) {
2024-10-31 02:19:42 +00:00
const { cid } = await inquirer.prompt([
{
type: 'input',
name: 'cid',
message: 'Enter the CID:',
validate: input => input.length > 0
}
]);
try {
const spinner = createSpinner('Downloading file').start();
await runCommand(`curl "http://localhost:8080/api/codex/v1/data/${cid}/network/stream" -o "${cid}.png"`);
spinner.success();
console.log(chalk.green(`Successfully downloaded!`));
console.log(chalk.bgGreen(`\nFile saved as ${cid}.png`));
await showNavigationMenu();
} catch (error) {
console.error(chalk.red('Failed to download file:', error.message));
await showNavigationMenu();
}
}
else{
console.log(chalk.red('\nOops...Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
}
}
2024-10-31 02:19:42 +00:00
async function showLocalFiles() {
if (platform === 'win32') {
console.log(chalk.yellow('Coming soon for Windows!'));
return;
}
const nodeRunning = await isNodeRunning();
2024-10-31 02:19:42 +00:00
if (nodeRunning) {
2024-10-31 02:19:42 +00:00
try {
const spinner = createSpinner('Fetching local files...').start();
const filesResponse = await runCommand('curl http://localhost:8080/api/codex/v1/data -w \'\\n\'');
spinner.success();
// Parse the JSON response
const filesData = JSON.parse(filesResponse);
if (filesData.content && filesData.content.length > 0) {
console.log(chalk.cyanBright('\nLocal Files:'));
console.log('━'.repeat(50));
// Iterate through each file and display information
filesData.content.forEach((file, index) => {
const { cid, manifest } = file;
const { rootHash, originalBytes, blockSize, protected: isProtected, filename, mimetype, uploadedAt } = manifest;
// Convert the UNIX timestamp to a readable format
const uploadedDate = new Date(uploadedAt * 1000).toLocaleString();
console.log(`📁 File ${index + 1}:`);
console.log(` Filename : ${chalk.green(filename)}`);
console.log(` CID : ${chalk.cyan(cid)}`);
console.log(` Root Hash : ${chalk.cyan(rootHash)}`);
console.log(` Original Bytes : ${chalk.yellow(originalBytes)}`);
console.log(` Block Size : ${chalk.yellow(blockSize)}`);
console.log(` Protected : ${chalk.yellow(isProtected ? 'Yes' : 'No')}`);
console.log(` MIME Type : ${chalk.green(mimetype)}`);
console.log(` Uploaded At : ${chalk.magenta(uploadedDate)}`);
console.log('━'.repeat(50));
});
await showNavigationMenu();
} else {
console.log(chalk.red('No local files found.'));
await showNavigationMenu();
}
} catch (error) {
console.error(chalk.red('Failed to show local files:', error.message));
await showNavigationMenu();
}
}
else{
console.log(chalk.red('\nOops...Codex node is not running. Try again after starting the node'));
await showNavigationMenu();
}
}
2024-10-31 02:19:42 +00:00
2024-10-31 15:27:33 +00:00
async function uninstallCodex() {
const binaryPath = '/usr/local/bin/codex';
try {
console.log(`Attempting to remove Codex binary from ${binaryPath} using sudo...`);
// Use sudo rm to delete the Codex binary
await runCommand(`sudo rm ${binaryPath}`);
console.log(`Codex binary removed from ${binaryPath}.`);
await showNavigationMenu();
} catch (error) {
if (error.code === 'ENOENT') {
console.log('Codex binary not found, nothing to uninstall.');
} else {
console.error(chalk.red(`Looks like Codex is not installed yet. Please install before trying to remove the Codex binaries.`));
}
await showNavigationMenu();
}
}
2024-10-31 02:19:42 +00:00
async function main() {
while (true) {
console.log('\n' + chalk.cyanBright(ASCII_ART));
const { choice } = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: 'Select an option:',
choices: [
2024-10-31 15:27:33 +00:00
'1. Download and install Codex',
2024-10-31 02:19:42 +00:00
'2. Run Codex node',
'3. Check node status',
'4. Upload a file',
'5. Download a file',
'6. Show local data',
2024-10-31 15:27:33 +00:00
'7. Uninstall Codex node',
2024-10-31 02:19:42 +00:00
]
}
]);
2024-10-31 15:27:33 +00:00
if (choice.startsWith('8')) {
2024-10-31 02:19:42 +00:00
console.log(chalk.cyanBright('\nGoodbye! 👋\n'));
break;
}
switch (choice.split('.')[0]) {
case '1':
await checkCodexInstallation();
break;
case '2':
await runCodex();
return;
2024-10-31 15:27:33 +00:00
case '3':
2024-10-31 02:19:42 +00:00
await checkNodeStatus();
break;
case '4':
await uploadFile();
break;
case '5':
await downloadFile();
break;
case '6':
await showLocalFiles();
break;
2024-10-31 15:27:33 +00:00
case '7':
await uninstallCodex();
break;
2024-10-31 02:19:42 +00:00
}
console.log('\n'); // Add some spacing between operations
}
}
// Run the CLI
main().catch(console.error);