From 79cb99749b4f48500d81bd7ee2233b556514f9a6 Mon Sep 17 00:00:00 2001 From: thatben Date: Wed, 19 Feb 2025 14:56:22 +0100 Subject: [PATCH 1/9] debug path selecting --- package.json | 3 +- src/configmenu.js | 105 +++++++++++++++++++++++++++ src/handlers/installationHandlers.js | 5 +- src/main.js | 47 ++++++------ src/utils/pathSelector.js | 92 +++++++++++++++-------- 5 files changed, 196 insertions(+), 56 deletions(-) create mode 100644 src/configmenu.js diff --git a/package.json b/package.json index 60cfb88..75013ec 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "chalk": "^5.3.0", "inquirer": "^9.2.12", "mime-types": "^2.1.35", - "nanospinner": "^1.1.0" + "nanospinner": "^1.1.0", + "fs-extra": "^11.3.0" } } diff --git a/src/configmenu.js b/src/configmenu.js new file mode 100644 index 0000000..140b076 --- /dev/null +++ b/src/configmenu.js @@ -0,0 +1,105 @@ +import inquirer from 'inquirer'; +import chalk from 'chalk'; +import { showErrorMessage, showInfoMessage } from './utils/messages.js'; +import { isDir, showPathSelector } from './utils/pathSelector.js'; +import { saveConfig } from './services/config.js'; +import fs from 'fs-extra'; + +export async function showConfigMenu(config) { + var newDataDir = config.dataDir; + try { + while (true) { + console.log(showInfoMessage("Codex Configuration")); + const { choice } = await inquirer.prompt([ + { + type: 'list', + name: 'choice', + message: 'Select to edit:', + choices: [ + `1. Data path = "${newDataDir}"`, + `2. Logs path = "${config.logsDir}"`, + '3. Storage quota = TODO', + '4. Discovery port = TODO', + '5. P2P listen port = TODO', + '6. API port = TODO', + '7. Save changes and exit', + '8. Discard changes and exit' + ], + pageSize: 8, + loop: true + } + ]).catch(() => { + return; + }); + + switch (choice.split('.')[0]) { + case '1': + newDataDir = await showPathSelector(config.dataDir, false); + if (isDir(newDataDir)) { + console.log(showInfoMessage("Warning: The new data path already exists. Make sure you know what you're doing.")); + } + break; + case '2': + config.logsDir = await showPathSelector(config.logsDir, true); + break; + case '3': + break; + case '4': + break; + case '5': + break; + case '6': + break; + case '7': + // save changes, back to main menu + config = updateDataDir(config, newDataDir); + saveConfig(config); + return; + case '8': + // discard changes, back to main menu + return; + } + } + } catch (error) { + console.error(chalk.red('An error occurred:', error.message)); + return; + } +} + +function updateDataDir(config, newDataDir) { + if (config.dataDir == newDataDir) return config; + + // The Codex dataDir is a little strange: + // If the old one is empty: The new one should not exist, so that codex creates it + // with the correct security permissions. + // If the old one does exist: We move it. + + if (isDir(config.dataDir)) { + console.log(showInfoMessage( + 'Moving Codex data folder...\n' + + `From: "${config.dataDir}"\n` + + `To: "${newDataDir}"` + )); + + try { + fs.moveSync(config.dataDir, newDataDir); + } catch (error) { + console.log(showErrorMessage("Error while moving dataDir: " + error.message)); + throw error; + } + } else { + // Old data dir does not exist. + if (isDir(newDataDir)) { + console.log(showInfoMessage( + "Warning: the selected data path already exists.\n" + + `New data path = "${newDataDir}"\n` + + "Codex may overwrite data in this folder.\n" + + "Codex will fail to start if this folder does not have the required\n" + + "security permissions." + )); + } + } + + config.dataDir = newDataDir; + return config; +} diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 9141bc0..af36b62 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -117,7 +117,6 @@ export async function installCodex(config, showNavigationMenu) { const spinner = createSpinner('Installing Codex...').start(); try { - if (platform === 'win32') { try { try { @@ -198,6 +197,10 @@ export async function installCodex(config, showNavigationMenu) { } catch (error) { throw new Error('Installation completed but Codex command is not available. Please restart your terminal and try again.'); } + + console.log(showInfoMessage( + "Please review the configuration before starting Codex." + )); spinner.success(); await showNavigationMenu(); diff --git a/src/main.js b/src/main.js index 9997f79..deea6a3 100644 --- a/src/main.js +++ b/src/main.js @@ -10,6 +10,7 @@ import { checkCodexInstallation, installCodex, uninstallCodex } from './handlers import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js'; import { showInfoMessage } from './utils/messages.js'; import { loadConfig } from './services/config.js'; +import { showConfigMenu } from './configmenu.js'; async function showNavigationMenu() { console.log('\n') @@ -78,57 +79,59 @@ export async function main() { message: 'Select an option:', choices: [ '1. Download and install Codex', - '2. Run Codex node', - '3. Check node status', - '4. Upload a file', - '5. Download a file', - '6. Show local data', - '7. Uninstall Codex node', - '8. Submit feedback', - '9. Exit' + '2. Edit Codex configuration', + '3. Run Codex node', + '4. Check node status', + '5. Upload a file', + '6. Download a file', + '7. Show local data', + '8. Uninstall Codex node', + '9. Submit feedback', + '10. Exit' ], - pageSize: 9, + pageSize: 10, loop: true } ]).catch(() => { handleExit(); - return { choice: '9' }; + return; }); - - if (choice.startsWith('9')) { - handleExit(); - break; - } - + switch (choice.split('.')[0]) { case '1': await checkCodexInstallation(config, showNavigationMenu); break; case '2': + await showConfigMenu(config); + break; + case '3': await runCodex(config, showNavigationMenu); return; - case '3': + case '4': await checkNodeStatus(showNavigationMenu); break; - case '4': + case '5': await uploadFile(null, handleCommandLineOperation, showNavigationMenu); break; - case '5': + case '6': await downloadFile(null, handleCommandLineOperation, showNavigationMenu); break; - case '6': + case '7': await showLocalFiles(showNavigationMenu); break; - case '7': + case '8': await uninstallCodex(config, showNavigationMenu); break; - case '8': + case '9': const { exec } = await import('child_process'); const url = 'https://docs.google.com/forms/d/1U21xp6shfDkJWzJSKHhUjwIE7fsYk94gmLUKAbxUMcw/edit'; const command = process.platform === 'win32' ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`; exec(command); console.log(showInfoMessage('Opening feedback form in your browser...')); break; + case '10': + handleExit(); + return; } console.log('\n'); diff --git a/src/utils/pathSelector.js b/src/utils/pathSelector.js index 172734f..fa6da7c 100644 --- a/src/utils/pathSelector.js +++ b/src/utils/pathSelector.js @@ -18,9 +18,31 @@ function splitPath(str) { return str.replaceAll("\\", "/").split("/"); } +function dropEmptyParts(parts) { + var result = []; + parts.forEach(function(part) { + if (part.length > 0) { + result.push(part); + } + }) + return result; +} + +function combine(parts) { + const toJoin = dropEmptyParts(parts); + if (toJoin.length == 1) return toJoin[0]; + return path.join(...toJoin); +} + +function combineWith(parts, extra) { + const toJoin = dropEmptyParts(parts); + if (toJoin.length == 1) return path.join(toJoin[0], extra); + return path.join(...toJoin, extra); +} + function showCurrent(currentPath) { const len = currentPath.length; - showMsg(`Current path: [${len}]\n` + path.join(...currentPath)); + showMsg(`Current path: [${len}]\n` + combine(currentPath)); } async function showMain(currentPath) { @@ -34,10 +56,9 @@ async function showMain(currentPath) { '1. Enter path', '2. Go up one', '3. Go down one', - '4. Check path exists', - '5. Create new folder here', - '6. Select this path', - '7. Cancel' + '4. Create new folder here', + '5. Select this path', + '6. Cancel' ], pageSize: 6, loop: true @@ -50,15 +71,15 @@ async function showMain(currentPath) { return choice; } -export async function selectPath() { - var currentPath = splitPath(process.cwd()); +export async function showPathSelector(startingPath, pathMustExist) { + var currentPath = splitPath(startingPath); while (true) { const choice = await showMain(currentPath); switch (choice.split('.')[0]) { case '1': - currentPath = await enterPath(); + currentPath = await enterPath(currentPath, pathMustExist); break; case '2': currentPath = upOne(currentPath); @@ -67,24 +88,22 @@ export async function selectPath() { currentPath = await downOne(currentPath); break; case '4': - await checkExists(currentPath); + currentPath = await createSubDir(currentPath, pathMustExist); break; case '5': - currentPath = await createSubDir(currentPath); - break; - case '6': - if (!isDir(currentPath)) { + if (pathMustExist && !isDir(combine(currentPath))) { console.log("Current path does not exist."); + break; } else { - return currentPath; + return combine(currentPath); } - case '7': - return ""; + case '6': + return combine(currentPath); } } } -async function enterPath() { +async function enterPath(currentPath, pathMustExist) { const response = await inquirer.prompt([ { type: 'input', @@ -92,6 +111,11 @@ async function enterPath() { message: 'Enter Path:' }]); + const newPath = response.path; + if (pathMustExist && !isDir(newPath)) { + console.log("The entered path does not exist."); + return currentPath; + } return splitPath(response.path); } @@ -99,20 +123,30 @@ function upOne(currentPath) { return currentPath.slice(0, currentPath.length - 1); } -function isDir(dir) { - return fs.lstatSync(dir).isDirectory(); +export function isDir(dir) { + try { + return fs.lstatSync(dir).isDirectory(); + } catch { + return false; + } } function isSubDir(currentPath, entry) { - const newPath = path.join(...currentPath, entry); + const newPath = combineWith(currentPath, entry); return isDir(newPath); } function getSubDirOptions(currentPath) { - const entries = fs.readdirSync(path.join(...currentPath)); + const fullPath = combine(currentPath); + currentPath.forEach(function(part) { + console.log("part: '" + part + "'"); + }); + console.log("current: '" + fullPath + "'"); + const entries = fs.readdirSync(fullPath); var result = []; var counter = 1; entries.forEach(function(entry) { + console.log("entry: " + entry); if (isSubDir(currentPath, entry)) { result.push(counter + ". " + entry); } @@ -144,15 +178,7 @@ async function downOne(currentPath) { return [...currentPath, subDir]; } -async function checkExists(currentPath) { - if (!isDir(path.join(...currentPath))) { - console.log("Current path does not exist."); - } else{ - console.log("Current path exists."); - } -} - -async function createSubDir(currentPath) { +async function createSubDir(currentPath, pathMustExist) { const response = await inquirer.prompt([ { type: 'input', @@ -163,7 +189,9 @@ async function createSubDir(currentPath) { const name = response.name; if (name.length < 1) return; - const fullDir = path.join(...currentPath, name); - fs.mkdirSync(fullDir); + const fullDir = combineWith(currentPath, name); + if (pathMustExist && !isDir(fullDir)) { + fs.mkdirSync(fullDir); + } return [...currentPath, name]; } From dc42c557fb846890ba8abbfa852d2e107d23a1ed Mon Sep 17 00:00:00 2001 From: thatben Date: Thu, 20 Feb 2025 09:23:18 +0100 Subject: [PATCH 2/9] wip: testing drive iteration --- package.json | 3 ++- src/main.js | 15 +++++++++++++++ src/utils/pathSelector.js | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 75013ec..cec452f 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "inquirer": "^9.2.12", "mime-types": "^2.1.35", "nanospinner": "^1.1.0", - "fs-extra": "^11.3.0" + "fs-extra": "^11.3.0", + "fs-filesystem": "^2.1.2" } } diff --git a/src/main.js b/src/main.js index deea6a3..a65c217 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,7 @@ import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js'; import { showInfoMessage } from './utils/messages.js'; import { loadConfig } from './services/config.js'; import { showConfigMenu } from './configmenu.js'; +import {filesystemSync} from 'fs-filesystem'; async function showNavigationMenu() { console.log('\n') @@ -52,6 +53,20 @@ function handleExit() { } export async function main() { + + const result = filesystemSync(); + console.log('devices', JSON.stringify(result)); + + Object.keys(result).forEach(function(key) { + var val = result[key]; + val.volumes.forEach(function(volume) { + console.log("mounting point: " + volume.mountPoint); + }); + }); + + return; + + const commandArgs = parseCommandLineArgs(); if (commandArgs) { switch (commandArgs.command) { diff --git a/src/utils/pathSelector.js b/src/utils/pathSelector.js index fa6da7c..f962e1d 100644 --- a/src/utils/pathSelector.js +++ b/src/utils/pathSelector.js @@ -3,6 +3,7 @@ import inquirer from 'inquirer'; import boxen from 'boxen'; import chalk from 'chalk'; import fs from 'fs'; +import { filesystemSync } from 'fs-filesystem'; function showMsg(msg) { console.log(boxen(chalk.white(msg), { @@ -14,6 +15,16 @@ function showMsg(msg) { })); } +// function getAvailableRoots() { +// const platform = os.platform(); +// if (platform === 'win32') { +// const result = await runCommand('for /f "delims=" %a in (\'curl -s --ssl-reqd ip.codex.storage\') do @echo %a'); +// nat = result.trim(); +// } else { +// nat = await runCommand('curl -s https://ip.codex.storage'); +// } +// } + function splitPath(str) { return str.replaceAll("\\", "/").split("/"); } @@ -43,6 +54,15 @@ function combineWith(parts, extra) { function showCurrent(currentPath) { const len = currentPath.length; showMsg(`Current path: [${len}]\n` + combine(currentPath)); + + if (len < 2) { + showMsg( + 'Warning - Known issue:\n' + + 'Path selection does not work in root paths on some platforms.\n' + + 'Use "Enter path" or "Create new folder" to navigate and create folders\n' + + 'if this is the case for you.' + ); + } } async function showMain(currentPath) { From c81232637eb16e5bf6e811676636c29691718a5c Mon Sep 17 00:00:00 2001 From: thatben Date: Thu, 20 Feb 2025 10:04:58 +0100 Subject: [PATCH 3/9] debugging path selection --- src/main.js | 15 --------- src/utils/pathSelector.js | 66 +++++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/main.js b/src/main.js index a65c217..deea6a3 100644 --- a/src/main.js +++ b/src/main.js @@ -11,7 +11,6 @@ import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js'; import { showInfoMessage } from './utils/messages.js'; import { loadConfig } from './services/config.js'; import { showConfigMenu } from './configmenu.js'; -import {filesystemSync} from 'fs-filesystem'; async function showNavigationMenu() { console.log('\n') @@ -53,20 +52,6 @@ function handleExit() { } export async function main() { - - const result = filesystemSync(); - console.log('devices', JSON.stringify(result)); - - Object.keys(result).forEach(function(key) { - var val = result[key]; - val.volumes.forEach(function(volume) { - console.log("mounting point: " + volume.mountPoint); - }); - }); - - return; - - const commandArgs = parseCommandLineArgs(); if (commandArgs) { switch (commandArgs.command) { diff --git a/src/utils/pathSelector.js b/src/utils/pathSelector.js index f962e1d..24d9639 100644 --- a/src/utils/pathSelector.js +++ b/src/utils/pathSelector.js @@ -15,15 +15,21 @@ function showMsg(msg) { })); } -// function getAvailableRoots() { -// const platform = os.platform(); -// if (platform === 'win32') { -// const result = await runCommand('for /f "delims=" %a in (\'curl -s --ssl-reqd ip.codex.storage\') do @echo %a'); -// nat = result.trim(); -// } else { -// nat = await runCommand('curl -s https://ip.codex.storage'); -// } -// } +function getAvailableRoots() { + const devices = filesystemSync(); + var mountPoints = []; + Object.keys(devices).forEach(function(key) { + var val = devices[key]; + val.volumes.forEach(function(volume) { + mountPoints.push(volume.mountPoint); + }); + }); + + if (mountPoints.length < 1) { + throw new Error("Failed to detect file system devices."); + } + return mountPoints; +} function splitPath(str) { return str.replaceAll("\\", "/").split("/"); @@ -65,6 +71,19 @@ function showCurrent(currentPath) { } } +function hasValidRoot(roots, checkPath) { + if (checkPath.length < 1) return false; + var result = false; + roots.forEach(function(root) { + if (root.toLowerCase() == checkPath[0].toLowerCase()) { + console.log("valid root: " + combine(checkPath)); + result = true; + } + }); + if (!result) console.log("invalid root: " + combine(checkPath)); + return result; +} + async function showMain(currentPath) { showCurrent(currentPath); const { choice } = await inquirer.prompt([ @@ -92,23 +111,28 @@ async function showMain(currentPath) { } export async function showPathSelector(startingPath, pathMustExist) { + const roots = getAvailableRoots(); var currentPath = splitPath(startingPath); + if (!hasValidRoot(roots, currentPath)) { + currentPath = [roots[0]]; + } while (true) { const choice = await showMain(currentPath); + var newCurrentPath = currentPath; switch (choice.split('.')[0]) { case '1': - currentPath = await enterPath(currentPath, pathMustExist); + newCurrentPath = await enterPath(currentPath, pathMustExist); break; case '2': - currentPath = upOne(currentPath); + newCurrentPath = upOne(currentPath); break; case '3': - currentPath = await downOne(currentPath); + newCurrentPath = await downOne(currentPath); break; case '4': - currentPath = await createSubDir(currentPath, pathMustExist); + newCurrentPath = await createSubDir(currentPath, pathMustExist); break; case '5': if (pathMustExist && !isDir(combine(currentPath))) { @@ -120,6 +144,12 @@ export async function showPathSelector(startingPath, pathMustExist) { case '6': return combine(currentPath); } + + if (hasValidRoot(roots, newCurrentPath)) { + currentPath = newCurrentPath; + } else { + console.log("Selected path has no valid root."); + } } } @@ -158,17 +188,13 @@ function isSubDir(currentPath, entry) { function getSubDirOptions(currentPath) { const fullPath = combine(currentPath); - currentPath.forEach(function(part) { - console.log("part: '" + part + "'"); - }); - console.log("current: '" + fullPath + "'"); const entries = fs.readdirSync(fullPath); var result = []; var counter = 1; entries.forEach(function(entry) { - console.log("entry: " + entry); if (isSubDir(currentPath, entry)) { - result.push(counter + ". " + entry); + result.push(counter + '. ' + entry); + counter = counter + 1; } }); return result; @@ -194,7 +220,7 @@ async function downOne(currentPath) { return currentPath; }); - const subDir = choice.slice(3); + const subDir = choice.split('. ')[1]; return [...currentPath, subDir]; } From ef5982e86b1696612e49c9cd52c3c7ed0b748d83 Mon Sep 17 00:00:00 2001 From: thatben Date: Thu, 20 Feb 2025 16:27:03 +0100 Subject: [PATCH 4/9] Implements storage quota config editor and argument --- src/configmenu.js | 28 ++++++++++++++++- src/handlers/installationHandlers.js | 7 +++-- src/handlers/nodeHandlers.js | 2 ++ src/services/config.js | 4 +-- src/utils/numberSelector.js | 47 ++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 src/utils/numberSelector.js diff --git a/src/configmenu.js b/src/configmenu.js index 140b076..804fab0 100644 --- a/src/configmenu.js +++ b/src/configmenu.js @@ -3,8 +3,33 @@ import chalk from 'chalk'; import { showErrorMessage, showInfoMessage } from './utils/messages.js'; import { isDir, showPathSelector } from './utils/pathSelector.js'; import { saveConfig } from './services/config.js'; +import { showNumberSelector } from './utils/numberSelector.js'; import fs from 'fs-extra'; +function bytesAmountToString(numBytes) { + const units = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + var value = numBytes; + var index = 0; + while (value > 1024) { + index = index + 1; + value = value / 1024; + } + + if (index == 0) return `${numBytes} Bytes`; + return `${numBytes} Bytes (${value} ${units[index]})`; +} + +async function showStorageQuotaSelector(config) { + console.log(showInfoMessage('You can use: "GB" or "gb", etc.')); + const result = await showNumberSelector(config.storageQuota, "Storage quota", true); + if (result < (100 * 1024 * 1024)) { + console.log(showErrorMessage("Storage quote should be >= 100mb.")); + return config.storageQuota; + } + return result; +} + export async function showConfigMenu(config) { var newDataDir = config.dataDir; try { @@ -18,7 +43,7 @@ export async function showConfigMenu(config) { choices: [ `1. Data path = "${newDataDir}"`, `2. Logs path = "${config.logsDir}"`, - '3. Storage quota = TODO', + `3. Storage quota = ${bytesAmountToString(config.storageQuota)}`, '4. Discovery port = TODO', '5. P2P listen port = TODO', '6. API port = TODO', @@ -43,6 +68,7 @@ export async function showConfigMenu(config) { config.logsDir = await showPathSelector(config.logsDir, true); break; case '3': + config.storageQuota = await showStorageQuotaSelector(config); break; case '4': break; diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index af36b62..fe0d380 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -81,10 +81,11 @@ export async function checkCodexInstallation(config, showNavigationMenu) { } } -async function saveCodexExePathToConfig(config, codexExePath) { +async function saveDefaultCodexConfig(config, codexExePath) { config.codexExe = codexExePath; config.dataDir = getCodexDataDirDefaultPath(); config.logsDir = getCodexLogsPath(); + config.storageQuota = 8 * 1024 * 1024 * 1024; if (!fs.existsSync(config.codexExe)) { console.log(showErrorMessage(`Codex executable not found in expected path: ${config.codexExe}`)); throw new Error("Exe not found"); @@ -128,7 +129,7 @@ export async function installCodex(config, showNavigationMenu) { await runCommand('curl -LO --ssl-no-revoke https://get.codex.storage/install.cmd'); await runCommand(`set "INSTALL_DIR=${installPath}" && "${process.cwd()}\\install.cmd"`); - await saveCodexExePathToConfig(config, path.join(installPath, "codex.exe")); + await saveDefaultCodexConfig(config, path.join(installPath, "codex.exe")); try { await runCommand('del /f install.cmd'); @@ -170,7 +171,7 @@ export async function installCodex(config, showNavigationMenu) { await runCommand(`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`); } - await saveCodexExePathToConfig(config, path.join(installPath, "codex")); + await saveDefaultCodexConfig(config, path.join(installPath, "codex")); } catch (error) { if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) { diff --git a/src/handlers/nodeHandlers.js b/src/handlers/nodeHandlers.js index b6dc09e..f9c4db8 100644 --- a/src/handlers/nodeHandlers.js +++ b/src/handlers/nodeHandlers.js @@ -85,7 +85,9 @@ export async function runCodex(config, showNavigationMenu) { const executable = config.codexExe; const args = [ `--data-dir="${config.dataDir}"`, + `--log-level=DEBUG`, `--log-file="${logFilePath}"`, + `--storage-quota="${config.storageQuota}"`, `--disc-port=${discPort}`, `--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort}`, `--nat=${nat}`, diff --git a/src/services/config.js b/src/services/config.js index 7bc2f4a..353fd5b 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -8,8 +8,8 @@ const defaultConfig = { // TODO: // Save user-selected config options. Use these when starting Codex. dataDir: "", - logsDir: "" - // storageQuota: 0, + logsDir: "", + storageQuota: 0, // ports: { // discPort: 8090, // listenPort: 8070, diff --git a/src/utils/numberSelector.js b/src/utils/numberSelector.js new file mode 100644 index 0000000..0cb74b5 --- /dev/null +++ b/src/utils/numberSelector.js @@ -0,0 +1,47 @@ +import inquirer from 'inquirer'; + +function getMetricsMult(valueStr, allowMetricPostfixes) { + if (!allowMetricPostfixes) return 1; + const lower = valueStr.toLowerCase(); + if (lower.endsWith("tb") || lower.endsWith("t")) return Math.pow(1024, 4); + if (lower.endsWith("gb") || lower.endsWith("g")) return Math.pow(1024, 3); + if (lower.endsWith("mb") || lower.endsWith("m")) return Math.pow(1024, 2); + if (lower.endsWith("kb") || lower.endsWith("k")) return Math.pow(1024, 1); + return 1; +} + +function getNumericValue(valueStr) { + try { + const num = valueStr.match(/\d+/g); + const result = parseInt(num); + if (isNaN(result) || !isFinite(result)) { + throw new Error("Invalid input received."); + } + return result; + } catch (error) { + console.log("Failed to parse input: " + error.message); + throw error; + } +} + +async function promptForValueStr(promptMessage) { + const response = await inquirer.prompt([ + { + type: 'input', + name: 'valueStr', + message: promptMessage + }]); + return response.valueStr; +} + +export async function showNumberSelector(currentValue, promptMessage, allowMetricPostfixes) { + try { + var valueStr = await promptForValueStr(promptMessage); + valueStr = valueStr.replaceAll(" ", ""); + const mult = getMetricsMult(valueStr, allowMetricPostfixes); + const value = getNumericValue(valueStr); + return value * mult; + } catch { + return currentValue; + } +} From 2e3783e4d09c169186a925c430f261eaffd718e6 Mon Sep 17 00:00:00 2001 From: thatben Date: Fri, 21 Feb 2025 10:07:52 +0100 Subject: [PATCH 5/9] cleanup --- src/handlers/installationHandlers.js | 24 +++++++++++------------- src/main.js | 4 ++-- src/services/config.js | 7 ++++--- src/utils/appdata.js | 2 +- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index fe0d380..3076783 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -9,7 +9,7 @@ import { runCommand } from '../utils/command.js'; import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js'; import { checkDependencies } from '../services/nodeService.js'; import { saveConfig } from '../services/config.js'; -import { getCodexRootPath, getCodexBinPath, getCodexDataDirDefaultPath, getCodexLogsPath } from '../utils/appdata.js'; +import { getCodexRootPath, getCodexBinPath } from '../utils/appdata.js'; const platform = os.platform(); @@ -68,7 +68,7 @@ export async function getCodexVersion(config) { } } -export async function checkCodexInstallation(config, showNavigationMenu) { +export async function installCodex(config, showNavigationMenu) { const version = await getCodexVersion(config); if (version.length > 0) { @@ -77,15 +77,12 @@ export async function checkCodexInstallation(config, showNavigationMenu) { await showNavigationMenu(); } else { console.log(chalk.cyanBright('Codex is not installed, proceeding with installation...')); - await installCodex(config, showNavigationMenu); + await performInstall(config, showNavigationMenu); } } -async function saveDefaultCodexConfig(config, codexExePath) { +async function saveCodexExePath(config, codexExePath) { config.codexExe = codexExePath; - config.dataDir = getCodexDataDirDefaultPath(); - config.logsDir = getCodexLogsPath(); - config.storageQuota = 8 * 1024 * 1024 * 1024; if (!fs.existsSync(config.codexExe)) { console.log(showErrorMessage(`Codex executable not found in expected path: ${config.codexExe}`)); throw new Error("Exe not found"); @@ -99,12 +96,10 @@ async function saveDefaultCodexConfig(config, codexExePath) { async function clearCodexExePathFromConfig(config) { config.codexExe = ""; - config.dataDir = ""; - config.logsDir = ""; saveConfig(config); } -export async function installCodex(config, showNavigationMenu) { +async function performInstall(config, showNavigationMenu) { const agreed = await showPrivacyDisclaimer(); if (!agreed) { console.log(showInfoMessage('You can find manual setup instructions at docs.codex.storage')); @@ -129,7 +124,7 @@ export async function installCodex(config, showNavigationMenu) { await runCommand('curl -LO --ssl-no-revoke https://get.codex.storage/install.cmd'); await runCommand(`set "INSTALL_DIR=${installPath}" && "${process.cwd()}\\install.cmd"`); - await saveDefaultCodexConfig(config, path.join(installPath, "codex.exe")); + await saveCodexExePath(config, path.join(installPath, "codex.exe")); try { await runCommand('del /f install.cmd'); @@ -171,7 +166,7 @@ export async function installCodex(config, showNavigationMenu) { await runCommand(`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`); } - await saveDefaultCodexConfig(config, path.join(installPath, "codex")); + await saveCodexExePath(config, path.join(installPath, "codex")); } catch (error) { if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) { @@ -190,10 +185,13 @@ export async function installCodex(config, showNavigationMenu) { try { const version = await getCodexVersion(config); + console.log(chalk.green(version)); + console.log(showSuccessMessage( 'Codex is successfully installed!\n' + `Install path: "${config.codexExe}"\n\n` + - `Version: ${version}` + 'The default configuration should work for most platforms.\n' + + 'Please review the configuration before starting Codex.\n' )); } catch (error) { throw new Error('Installation completed but Codex command is not available. Please restart your terminal and try again.'); diff --git a/src/main.js b/src/main.js index deea6a3..eb66869 100644 --- a/src/main.js +++ b/src/main.js @@ -6,7 +6,7 @@ import boxen from 'boxen'; import { ASCII_ART } from './constants/ascii.js'; import { handleCommandLineOperation, parseCommandLineArgs } from './cli/commandParser.js'; import { uploadFile, downloadFile, showLocalFiles } from './handlers/fileHandlers.js'; -import { checkCodexInstallation, installCodex, uninstallCodex } from './handlers/installationHandlers.js'; +import { installCodex, uninstallCodex } from './handlers/installationHandlers.js'; import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js'; import { showInfoMessage } from './utils/messages.js'; import { loadConfig } from './services/config.js'; @@ -99,7 +99,7 @@ export async function main() { switch (choice.split('.')[0]) { case '1': - await checkCodexInstallation(config, showNavigationMenu); + await installCodex(config, showNavigationMenu); break; case '2': await showConfigMenu(config); diff --git a/src/services/config.js b/src/services/config.js index 353fd5b..4cc79f6 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -1,15 +1,16 @@ import fs from 'fs'; import path from 'path'; import { getAppDataDir } from '../utils/appdata.js'; +import { getCodexDataDirDefaultPath, getCodexLogsDefaultPath } from '../utils/appdata.js'; const defaultConfig = { codexExe: "", // TODO: // Save user-selected config options. Use these when starting Codex. - dataDir: "", - logsDir: "", - storageQuota: 0, + dataDir: getCodexDataDirDefaultPath(), + logsDir: getCodexLogsDefaultPath(), + storageQuota: 8 * 1024 * 1024 * 1024 // ports: { // discPort: 8090, // listenPort: 8070, diff --git a/src/utils/appdata.js b/src/utils/appdata.js index 1ff6059..ad67024 100644 --- a/src/utils/appdata.js +++ b/src/utils/appdata.js @@ -19,7 +19,7 @@ export function getCodexDataDirDefaultPath() { return path.join(appData("codex"), "datadir"); } -export function getCodexLogsPath() { +export function getCodexLogsDefaultPath() { return ensureExists(path.join(appData("codex"), "logs")); } From 0c7681d06f446a6a86bfe4eb67a4a4b4472e3365 Mon Sep 17 00:00:00 2001 From: thatben Date: Fri, 21 Feb 2025 10:19:02 +0100 Subject: [PATCH 6/9] Implements port configing --- src/configmenu.js | 9 ++++++--- src/services/config.js | 16 +++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/configmenu.js b/src/configmenu.js index 804fab0..fe0aa9c 100644 --- a/src/configmenu.js +++ b/src/configmenu.js @@ -44,9 +44,9 @@ export async function showConfigMenu(config) { `1. Data path = "${newDataDir}"`, `2. Logs path = "${config.logsDir}"`, `3. Storage quota = ${bytesAmountToString(config.storageQuota)}`, - '4. Discovery port = TODO', - '5. P2P listen port = TODO', - '6. API port = TODO', + `4. Discovery port = ${config.ports.discPort}`, + `5. P2P listen port = ${config.ports.listenPort}`, + `6. API port = ${config.ports.apiPort}`, '7. Save changes and exit', '8. Discard changes and exit' ], @@ -71,10 +71,13 @@ export async function showConfigMenu(config) { config.storageQuota = await showStorageQuotaSelector(config); break; case '4': + config.ports.discPort = await showNumberSelector(config.ports.discPort, "Discovery Port (UDP)", false); break; case '5': + config.ports.listenPort = await showNumberSelector(config.ports.listenPort, "Listen Port (TCP)", false); break; case '6': + config.ports.apiPort = await showNumberSelector(config.ports.apiPort, "API Port (TCP)", false); break; case '7': // save changes, back to main menu diff --git a/src/services/config.js b/src/services/config.js index 4cc79f6..07fddf3 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -5,17 +5,15 @@ import { getCodexDataDirDefaultPath, getCodexLogsDefaultPath } from '../utils/ap const defaultConfig = { codexExe: "", - - // TODO: - // Save user-selected config options. Use these when starting Codex. + // User-selected config options: dataDir: getCodexDataDirDefaultPath(), logsDir: getCodexLogsDefaultPath(), - storageQuota: 8 * 1024 * 1024 * 1024 - // ports: { - // discPort: 8090, - // listenPort: 8070, - // apiPort: 8080 - // } + storageQuota: 8 * 1024 * 1024 * 1024, + ports: { + discPort: 8090, + listenPort: 8070, + apiPort: 8080 + } }; function getConfigFilename() { From e26b65daf606beb7e3e0aac9b15a56ad248066a7 Mon Sep 17 00:00:00 2001 From: thatben Date: Fri, 21 Feb 2025 11:05:31 +0100 Subject: [PATCH 7/9] applies ports to codex start and api calls --- src/handlers/fileHandlers.js | 20 ++++++++++---------- src/handlers/nodeHandlers.js | 32 +++++++++----------------------- src/main.js | 8 ++++---- src/services/nodeService.js | 8 ++++---- 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/handlers/fileHandlers.js b/src/handlers/fileHandlers.js index ab67dde..47b299a 100644 --- a/src/handlers/fileHandlers.js +++ b/src/handlers/fileHandlers.js @@ -10,8 +10,8 @@ import path from 'path'; import mime from 'mime-types'; import axios from 'axios'; -export async function uploadFile(filePath = null, handleCommandLineOperation, showNavigationMenu) { - const nodeRunning = await isNodeRunning(); +export async function uploadFile(config, filePath = null, handleCommandLineOperation, showNavigationMenu) { + const nodeRunning = await isNodeRunning(config); if (!nodeRunning) { console.log(showErrorMessage('Codex node is not running. Try again after starting the node')); return handleCommandLineOperation() ? process.exit(1) : showNavigationMenu(); @@ -51,7 +51,7 @@ export async function uploadFile(filePath = null, handleCommandLineOperation, sh const spinner = createSpinner('Uploading file').start(); try { const result = await runCommand( - `curl -X POST http://localhost:8080/api/codex/v1/data ` + + `curl -X POST http://localhost:${config.ports.apiPort}/api/codex/v1/data ` + `-H 'Content-Type: ${contentType}' ` + `-H 'Content-Disposition: attachment; filename="${filename}"' ` + `-w '\\n' -T "${fileToUpload}"` @@ -71,8 +71,8 @@ export async function uploadFile(filePath = null, handleCommandLineOperation, sh return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu(); } -export async function downloadFile(cid = null, handleCommandLineOperation, showNavigationMenu) { - const nodeRunning = await isNodeRunning(); +export async function downloadFile(config, cid = null, handleCommandLineOperation, showNavigationMenu) { + const nodeRunning = await isNodeRunning(config); if (!nodeRunning) { console.log(showErrorMessage('Codex node is not running. Try again after starting the node')); return handleCommandLineOperation() ? process.exit(1) : showNavigationMenu(); @@ -95,7 +95,7 @@ export async function downloadFile(cid = null, handleCommandLineOperation, showN const spinner = createSpinner('Fetching file metadata...').start(); try { // First, get the file metadata - const metadataResponse = await axios.post(`http://localhost:8080/api/codex/v1/data/${cidToDownload}/network`); + const metadataResponse = await axios.post(`http://localhost:${config.ports.apiPort}/api/codex/v1/data/${cidToDownload}/network`); const { manifest } = metadataResponse.data; const { filename, mimetype } = manifest; @@ -103,7 +103,7 @@ export async function downloadFile(cid = null, handleCommandLineOperation, showN spinner.start('Downloading file...'); // Then download the file with the correct filename - await runCommand(`curl "http://localhost:8080/api/codex/v1/data/${cidToDownload}/network/stream" -o "${filename}"`); + await runCommand(`curl "http://localhost:${config.ports.apiPort}/api/codex/v1/data/${cidToDownload}/network/stream" -o "${filename}"`); spinner.success(); console.log(showSuccessMessage( @@ -144,8 +144,8 @@ export async function downloadFile(cid = null, handleCommandLineOperation, showN return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu(); } -export async function showLocalFiles(showNavigationMenu) { - const nodeRunning = await isNodeRunning(); +export async function showLocalFiles(config, showNavigationMenu) { + const nodeRunning = await isNodeRunning(config); if (!nodeRunning) { console.log(showErrorMessage('Codex node is not running. Try again after starting the node')); await showNavigationMenu(); @@ -154,7 +154,7 @@ export async function showLocalFiles(showNavigationMenu) { try { const spinner = createSpinner('Fetching local files...').start(); - const filesResponse = await runCommand('curl http://localhost:8080/api/codex/v1/data -w \'\\n\''); + const filesResponse = await runCommand(`curl http://localhost:${config.ports.apiPort}/api/codex/v1/data -w \'\\n\'`); spinner.success(); const filesData = JSON.parse(filesResponse); diff --git a/src/handlers/nodeHandlers.js b/src/handlers/nodeHandlers.js index f9c4db8..b986794 100644 --- a/src/handlers/nodeHandlers.js +++ b/src/handlers/nodeHandlers.js @@ -43,27 +43,12 @@ export async function runCodex(config, showNavigationMenu) { return; } - const nodeAlreadyRunning = await isNodeRunning(); + const nodeAlreadyRunning = await isNodeRunning(config); if (nodeAlreadyRunning) { console.log(showInfoMessage('A Codex node is already running.')); await showNavigationMenu(); } else { - 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 { let nat; if (platform === 'win32') { @@ -88,8 +73,9 @@ export async function runCodex(config, showNavigationMenu) { `--log-level=DEBUG`, `--log-file="${logFilePath}"`, `--storage-quota="${config.storageQuota}"`, - `--disc-port=${discPort}`, - `--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort}`, + `--disc-port=${config.ports.discPort}`, + `--listen-addrs=/ip4/0.0.0.0/tcp/${config.ports.listenPort}`, + `--api-port=${config.ports.apiPort}`, `--nat=${nat}`, `--api-cors-origin="*"`, `--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P` @@ -110,7 +96,7 @@ export async function runCodex(config, showNavigationMenu) { await new Promise(resolve => setTimeout(resolve, 5000)); try { - const response = await axios.get('http://localhost:8080/api/codex/v1/debug/info'); + const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`); if (response.status === 200) { // Check if wallet exists try { @@ -128,7 +114,7 @@ export async function runCodex(config, showNavigationMenu) { } // Start periodic logging - const stopLogging = await startPeriodicLogging(); + const stopLogging = await startPeriodicLogging(config); nodeProcess.on('exit', () => { stopLogging(); @@ -262,13 +248,13 @@ async function showNodeDetails(data, showNavigationMenu) { } } -export async function checkNodeStatus(showNavigationMenu) { +export async function checkNodeStatus(config, showNavigationMenu) { try { - const nodeRunning = await isNodeRunning(); + const nodeRunning = await isNodeRunning(config); if (nodeRunning) { const spinner = createSpinner('Checking node status...').start(); - const response = await runCommand('curl http://localhost:8080/api/codex/v1/debug/info'); + const response = await runCommand(`curl http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`); spinner.success(); const data = JSON.parse(response); diff --git a/src/main.js b/src/main.js index eb66869..0e7b2cd 100644 --- a/src/main.js +++ b/src/main.js @@ -108,16 +108,16 @@ export async function main() { await runCodex(config, showNavigationMenu); return; case '4': - await checkNodeStatus(showNavigationMenu); + await checkNodeStatus(config, showNavigationMenu); break; case '5': - await uploadFile(null, handleCommandLineOperation, showNavigationMenu); + await uploadFile(config, null, handleCommandLineOperation, showNavigationMenu); break; case '6': - await downloadFile(null, handleCommandLineOperation, showNavigationMenu); + await downloadFile(config, null, handleCommandLineOperation, showNavigationMenu); break; case '7': - await showLocalFiles(showNavigationMenu); + await showLocalFiles(config, showNavigationMenu); break; case '8': await uninstallCodex(config, showNavigationMenu); diff --git a/src/services/nodeService.js b/src/services/nodeService.js index 810760b..daa65db 100644 --- a/src/services/nodeService.js +++ b/src/services/nodeService.js @@ -21,9 +21,9 @@ export async function getWalletAddress() { return currentWallet; } -export async function isNodeRunning() { +export async function isNodeRunning(config) { try { - const response = await axios.get('http://localhost:8080/api/codex/v1/debug/info'); + const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`); return response.status === 200; } catch (error) { return false; @@ -105,12 +105,12 @@ export async function checkDependencies() { return true; } -export async function startPeriodicLogging() { +export async function startPeriodicLogging(config) { const FIFTEEN_MINUTES = 15 * 60 * 1000; // 15 minutes in milliseconds const logNodeInfo = async () => { try { - const response = await axios.get('http://localhost:8080/api/codex/v1/debug/info'); + const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`); if (response.status === 200) { await logToSupabase(response.data); } From b9d6fd70b9c9c11e827c7f8e4bece1adba2453fe Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 24 Feb 2025 10:36:27 +0100 Subject: [PATCH 8/9] Displays default config after successful install. --- src/handlers/installationHandlers.js | 10 +++++----- src/handlers/nodeHandlers.js | 3 ++- src/main.js | 5 ++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 3076783..8234144 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -75,9 +75,10 @@ export async function installCodex(config, showNavigationMenu) { console.log(chalk.green('Codex is already installed. Version:')); console.log(chalk.green(version)); await showNavigationMenu(); + return false; } else { console.log(chalk.cyanBright('Codex is not installed, proceeding with installation...')); - await performInstall(config, showNavigationMenu); + return await performInstall(config); } } @@ -99,12 +100,11 @@ async function clearCodexExePathFromConfig(config) { saveConfig(config); } -async function performInstall(config, showNavigationMenu) { +async function performInstall(config) { const agreed = await showPrivacyDisclaimer(); if (!agreed) { console.log(showInfoMessage('You can find manual setup instructions at docs.codex.storage')); process.exit(0); - return; } const installPath = getCodexBinPath(); @@ -202,11 +202,11 @@ async function performInstall(config, showNavigationMenu) { )); spinner.success(); - await showNavigationMenu(); + return true; } catch (error) { spinner.error(); console.log(showErrorMessage(`Failed to install Codex: ${error.message}`)); - await showNavigationMenu(); + return false; } } diff --git a/src/handlers/nodeHandlers.js b/src/handlers/nodeHandlers.js index b986794..ef0eddb 100644 --- a/src/handlers/nodeHandlers.js +++ b/src/handlers/nodeHandlers.js @@ -64,7 +64,8 @@ export async function runCodex(config, showNavigationMenu) { console.log(showInfoMessage( `Data location: ${config.dataDir}\n` + - `Logs: ${logFilePath}` + `Logs: ${logFilePath}\n` + + `API port: ${config.ports.apiPort}` )); const executable = config.codexExe; diff --git a/src/main.js b/src/main.js index 0e7b2cd..4b35b25 100644 --- a/src/main.js +++ b/src/main.js @@ -99,7 +99,10 @@ export async function main() { switch (choice.split('.')[0]) { case '1': - await installCodex(config, showNavigationMenu); + const installed = await installCodex(config, showNavigationMenu); + if (installed) { + await showConfigMenu(config); + } break; case '2': await showConfigMenu(config); From 8fa57058176af5833d01bf732bf8c8233917d332 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 24 Feb 2025 10:54:11 +0100 Subject: [PATCH 9/9] Fixes local-datasets lookup --- src/handlers/fileHandlers.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/handlers/fileHandlers.js b/src/handlers/fileHandlers.js index 47b299a..d0f458b 100644 --- a/src/handlers/fileHandlers.js +++ b/src/handlers/fileHandlers.js @@ -154,11 +154,10 @@ export async function showLocalFiles(config, showNavigationMenu) { try { const spinner = createSpinner('Fetching local files...').start(); - const filesResponse = await runCommand(`curl http://localhost:${config.ports.apiPort}/api/codex/v1/data -w \'\\n\'`); + const filesResponse = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/data`); + const filesData = filesResponse.data; spinner.success(); - const filesData = JSON.parse(filesResponse); - if (filesData.content && filesData.content.length > 0) { console.log(showInfoMessage(`Found ${filesData.content.length} local file(s)`)); @@ -187,6 +186,8 @@ export async function showLocalFiles(config, showNavigationMenu) { } )); }); + } else { + console.log(showInfoMessage("Node contains no datasets.")); } } catch (error) { console.log(showErrorMessage(`Failed to fetch local files: ${error.message}`));