mirror of
https://github.com/logos-storage/logos-storage-installer.git
synced 2026-01-03 22:13:07 +00:00
Merge pull request #6 from codex-storage/feature/codex-config
Config management
This commit is contained in:
commit
72f48bf7d6
@ -23,6 +23,8 @@
|
||||
"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",
|
||||
"fs-filesystem": "^2.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
134
src/configmenu.js
Normal file
134
src/configmenu.js
Normal file
@ -0,0 +1,134 @@
|
||||
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 { 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 {
|
||||
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 = ${bytesAmountToString(config.storageQuota)}`,
|
||||
`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'
|
||||
],
|
||||
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':
|
||||
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
|
||||
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;
|
||||
}
|
||||
@ -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,11 +154,10 @@ 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 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(showNavigationMenu) {
|
||||
}
|
||||
));
|
||||
});
|
||||
} else {
|
||||
console.log(showInfoMessage("Node contains no datasets."));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(showErrorMessage(`Failed to fetch local files: ${error.message}`));
|
||||
|
||||
@ -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,23 +68,22 @@ 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) {
|
||||
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 installCodex(config, showNavigationMenu);
|
||||
return await performInstall(config);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCodexExePathToConfig(config, codexExePath) {
|
||||
async function saveCodexExePath(config, codexExePath) {
|
||||
config.codexExe = codexExePath;
|
||||
config.dataDir = getCodexDataDirDefaultPath();
|
||||
config.logsDir = getCodexLogsPath();
|
||||
if (!fs.existsSync(config.codexExe)) {
|
||||
console.log(showErrorMessage(`Codex executable not found in expected path: ${config.codexExe}`));
|
||||
throw new Error("Exe not found");
|
||||
@ -98,17 +97,14 @@ async function saveCodexExePathToConfig(config, codexExePath) {
|
||||
|
||||
async function clearCodexExePathFromConfig(config) {
|
||||
config.codexExe = "";
|
||||
config.dataDir = "";
|
||||
config.logsDir = "";
|
||||
saveConfig(config);
|
||||
}
|
||||
|
||||
export async function installCodex(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();
|
||||
@ -117,7 +113,6 @@ export async function installCodex(config, showNavigationMenu) {
|
||||
const spinner = createSpinner('Installing Codex...').start();
|
||||
|
||||
try {
|
||||
|
||||
if (platform === 'win32') {
|
||||
try {
|
||||
try {
|
||||
@ -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 saveCodexExePathToConfig(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 saveCodexExePathToConfig(config, path.join(installPath, "codex"));
|
||||
await saveCodexExePath(config, path.join(installPath, "codex"));
|
||||
|
||||
} catch (error) {
|
||||
if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {
|
||||
@ -190,21 +185,28 @@ 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.');
|
||||
}
|
||||
|
||||
console.log(showInfoMessage(
|
||||
"Please review the configuration before starting Codex."
|
||||
));
|
||||
|
||||
spinner.success();
|
||||
await showNavigationMenu();
|
||||
return true;
|
||||
} catch (error) {
|
||||
spinner.error();
|
||||
console.log(showErrorMessage(`Failed to install Codex: ${error.message}`));
|
||||
await showNavigationMenu();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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') {
|
||||
@ -79,15 +64,19 @@ 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;
|
||||
const args = [
|
||||
`--data-dir="${config.dataDir}"`,
|
||||
`--log-level=DEBUG`,
|
||||
`--log-file="${logFilePath}"`,
|
||||
`--disc-port=${discPort}`,
|
||||
`--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort}`,
|
||||
`--storage-quota="${config.storageQuota}"`,
|
||||
`--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`
|
||||
@ -108,7 +97,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 {
|
||||
@ -126,7 +115,7 @@ export async function runCodex(config, showNavigationMenu) {
|
||||
}
|
||||
|
||||
// Start periodic logging
|
||||
const stopLogging = await startPeriodicLogging();
|
||||
const stopLogging = await startPeriodicLogging(config);
|
||||
|
||||
nodeProcess.on('exit', () => {
|
||||
stopLogging();
|
||||
@ -260,13 +249,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);
|
||||
|
||||
56
src/main.js
56
src/main.js
@ -6,10 +6,11 @@ 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';
|
||||
import { showConfigMenu } from './configmenu.js';
|
||||
|
||||
async function showNavigationMenu() {
|
||||
console.log('\n')
|
||||
@ -78,57 +79,62 @@ 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);
|
||||
const installed = await installCodex(config, showNavigationMenu);
|
||||
if (installed) {
|
||||
await showConfigMenu(config);
|
||||
}
|
||||
break;
|
||||
case '2':
|
||||
await showConfigMenu(config);
|
||||
break;
|
||||
case '3':
|
||||
await runCodex(config, showNavigationMenu);
|
||||
return;
|
||||
case '3':
|
||||
await checkNodeStatus(showNavigationMenu);
|
||||
break;
|
||||
case '4':
|
||||
await uploadFile(null, handleCommandLineOperation, showNavigationMenu);
|
||||
await checkNodeStatus(config, showNavigationMenu);
|
||||
break;
|
||||
case '5':
|
||||
await downloadFile(null, handleCommandLineOperation, showNavigationMenu);
|
||||
await uploadFile(config, null, handleCommandLineOperation, showNavigationMenu);
|
||||
break;
|
||||
case '6':
|
||||
await showLocalFiles(showNavigationMenu);
|
||||
await downloadFile(config, null, handleCommandLineOperation, showNavigationMenu);
|
||||
break;
|
||||
case '7':
|
||||
await uninstallCodex(config, showNavigationMenu);
|
||||
await showLocalFiles(config, showNavigationMenu);
|
||||
break;
|
||||
case '8':
|
||||
await uninstallCodex(config, showNavigationMenu);
|
||||
break;
|
||||
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');
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
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,
|
||||
// ports: {
|
||||
// discPort: 8090,
|
||||
// listenPort: 8070,
|
||||
// apiPort: 8080
|
||||
// }
|
||||
// User-selected config options:
|
||||
dataDir: getCodexDataDirDefaultPath(),
|
||||
logsDir: getCodexLogsDefaultPath(),
|
||||
storageQuota: 8 * 1024 * 1024 * 1024,
|
||||
ports: {
|
||||
discPort: 8090,
|
||||
listenPort: 8070,
|
||||
apiPort: 8080
|
||||
}
|
||||
};
|
||||
|
||||
function getConfigFilename() {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
|
||||
|
||||
47
src/utils/numberSelector.js
Normal file
47
src/utils/numberSelector.js
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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,13 +15,73 @@ function showMsg(msg) {
|
||||
}));
|
||||
}
|
||||
|
||||
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("/");
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -34,10 +95,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,41 +110,50 @@ async function showMain(currentPath) {
|
||||
return choice;
|
||||
}
|
||||
|
||||
export async function selectPath() {
|
||||
var currentPath = splitPath(process.cwd());
|
||||
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();
|
||||
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':
|
||||
await checkExists(currentPath);
|
||||
newCurrentPath = 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);
|
||||
}
|
||||
|
||||
if (hasValidRoot(roots, newCurrentPath)) {
|
||||
currentPath = newCurrentPath;
|
||||
} else {
|
||||
console.log("Selected path has no valid root.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function enterPath() {
|
||||
async function enterPath(currentPath, pathMustExist) {
|
||||
const response = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
@ -92,6 +161,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,22 +173,28 @@ 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);
|
||||
const entries = fs.readdirSync(fullPath);
|
||||
var result = [];
|
||||
var counter = 1;
|
||||
entries.forEach(function(entry) {
|
||||
if (isSubDir(currentPath, entry)) {
|
||||
result.push(counter + ". " + entry);
|
||||
result.push(counter + '. ' + entry);
|
||||
counter = counter + 1;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
@ -140,19 +220,11 @@ async function downOne(currentPath) {
|
||||
return currentPath;
|
||||
});
|
||||
|
||||
const subDir = choice.slice(3);
|
||||
const subDir = choice.split('. ')[1];
|
||||
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 +235,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];
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user