#!/usr/bin/env node import chalk from 'chalk' import inquirer from 'inquirer' import { execSync } from 'child_process' import { detectOS, updateSoftware, installRequired, setNodeVersion } from './scripts/system.js' import { checkAoEnvFile, aoEnv, setAoEnv, AO_ENV_FILE_PATH } from './scripts/settings.js' import { unicornPortal, asciiArt, clearScreen, spinnerWait } from './scripts/console.js' import { welcome, exclaim, roger, farewell } from './scripts/welcome.js' import { printManualPage, manualFolderAsMenu, AO_MANUAL_PATH } from './scripts/manual.js' import { isFolder } from './scripts/files.js' import { sleep } from './scripts/util.js' import { tests } from './scripts/tests.js' import { headerStyle } from './scripts/chalkStyles.js' import './scripts/strings.js' import { installAoAlias, downloadManual, updateManual } from './scripts/features.js' import { startPublicBootstrap } from './scripts/bootstrap.js' // These should become .env variables that are loaded intelligently let distro let memberName // This does not work function exitIfRoot() { try { execSync('[ "$EUID" -eq 0 ]') console.log(`${chalk.red.bold(exclaim())} Seems you're running this script as a superuser.`) console.log('That might cause some issues with permissions and whatnot. Run this script as your default user (without sudo) and I\'ll ask you when I need superuser permissions') process.exit(1) } catch(err) {} } // Prints the AO Main Menu and executes the user's choice async function mainMenu() { console.log(`\n${headerStyle('AO Main Menu')}\n`) const mainMenuChoices = [ 'Chat', 'Alchemy', 'Deck', 'Admin', 'Tests', 'Manual', 'Log Out', 'Exit', ] const answer = await inquirer.prompt({ name: 'main_menu', type: 'list', message: 'Please choose:', choices: mainMenuChoices, pageSize: mainMenuChoices.length }) switch(answer.main_menu) { case 'Chat': while(await chatMenu()) {} break case 'Alchemy': while(await alchemyMenu()) {} break case 'Deck': await todoList('My Todo List', ['Add full AO install process to ao-cli in convenient format', 'Add AO server unit tests to ao-cli', 'Get groceries', 'Play music every day']) break case 'Admin': while(await adminMenu()) {} break case 'Tests': while(await testsMenu()) {} break case 'Manual': if(!isFolder(AO_MANUAL_PATH)) { console.log("Downloading the AO manual...") if(downloadManual()) { console.log("Downloaded the AO Manual from the official git repo via http and saved to", AO_MANUAL_PATH + '.') } else { console.log('Failed to download the AO manual, sorry.') return false } } else { updateManual() } await printManualPage(AO_MANUAL_PATH) // Fencepost case - print overview page let previousChoice = 0 do { previousChoice = await manualFolderAsMenu(AO_MANUAL_PATH, 'AO User Manual', 'Back to Main Menu', previousChoice + 1) } while(previousChoice !== false) break case 'Log Out': await spinnerWait('Logging out... (just kidding)') break case 'Exit': farewell() await sleep(310) return false } return true } // Prints a menu that allows you to join the global AO chatrooms let publicBootstrapStarted = false async function chatMenu() { let answers = {} const PUBLIC_BOOTSTRAP_ENABLED = aoEnv('PUBLIC_BOOTSTRAP_ENABLED') if(PUBLIC_BOOTSTRAP_ENABLED) { // They previously enabled public bootstrapping, so check to make sure it is working and then hide the option // todo: start and then verify functioning of p2p boostrap method here // if it's already started don't start it again if(!publicBootstrapStarted) { console.log("\nBootstrapping public AO swarm...") startPublicBootstrap() console.log("Bootstrapped (just kidding)") publicBootstrapStarted = true } //answers['chat_menu'] = 'Enable p2p bootstrap' } const publicBootstrapMenuItem = PUBLIC_BOOTSTRAP_ENABLED ? 'Disable p2p bootstrap' : 'Enable p2p bootstrap' const chatChoices = [ publicBootstrapMenuItem, 'Join public chatroom', 'Join chatroom', 'Address Book', 'Back to Main Menu', ] console.log(`\n${headerStyle('AO Public Chatrooms')}\n`) const answer = await inquirer.prompt({ name: 'chat_menu', type: 'list', message: 'Please choose:', choices: chatChoices }) switch(answer.chat_menu) { case 'Enable p2p bootstrap': console.log('In order to join AO public chatrooms, AO uses the hyperswarm protocol. Joining hyperswarm may expose your IP address to other users. (For high-security installations, don\'t use public bootstrap: you can still add tor addresses to your address book manually and join those chatrooms by name.)') setAoEnv('PUBLIC_BOOTSTRAP_ENABLED', true) //message: Type \'public\' and press Enter to enable:') break case 'Disable p2p bootstrap': setAoEnv('PUBLIC_BOOTSTRAP_ENABLED', false) console.log(roger(), 'Disabled public bootstrapping.') if(publicBootstrapStarted) { // stop the bootstrap thing here publicBootstrapStarted = false } break case 'Join public chatroom': console.log('Not yet implemented') break case 'Join chatroom': console.log('Not yet implemented') break case 'Address Book': console.log('The point of this address book is to make it possible to type short, one-word names and have them resolve to tor addresses.') console.log('Name a piece of data by saying name=data. For example, doge=5uc41o1...onion. Then \'doge\' will return the .onion address.') console.log('Querying with any synonym in a chain will return the final meanings they all point to.') console.log('Keys can have multiple values.') break case 'Back to Main Menu': return false } return true } // Prints the AO Admin Menu and executes the user's choice async function adminMenu() { console.log(`\n${headerStyle('AO Admin Menu')}`) const adminChoices = [ 'Install \'ao\' alias for \'ao-cli\'', 'Check AO install', 'Update AO', 'Configure AO features', 'Switch AO database', 'Import/Export state/decks', 'Check AO service', 'Watch logs now', 'Start/Stop AO service', 'Back to Main Menu' ] const answer = await inquirer.prompt({ name: 'admin_menu', type: 'list', message: 'Please choose:', choices: adminChoices, pageSize: adminChoices.length, }) switch(answer.admin_menu) { case adminChoices[0]: installAoAlias() break case adminChoices[1]: case adminChoices[2]: case adminChoices[3]: while(await featuresMenu()) {} break case adminChoices[4]: case adminChoices[5]: case adminChoices[6]: case adminChoices[7]: case adminChoices[8]: console.log("Not yet implemented.") break default: return false } return true } // Prints the AO Unit Tests Menu and executes the user's choice async function testsMenu() { console.log(`\n${headerStyle('AO Unit Tests')}`) let testChoices = Object.entries(tests).map(([menuTitle, testFunction]) => { return menuTitle }) testChoices.push('Back to Main Menu') const answer = await inquirer.prompt({ name: 'tests_menu', type: 'list', message: 'Please choose:', choices: testChoices }) if(answer.tests_menu === 'Back to Main Menu') { return false } const testFunction = tests[answer.tests_menu] if(testFunction) await testFunction() return true } // Prints the AO Admin Menu and executes the user's choice async function alchemyMenu() { console.log(`\n${headerStyle('Alchemy')}`) const alchemyChoices = [ 'Update software', 'Install AO prerequisites', 'Check bitcoin status', 'Back to Main Menu' ] const answer = await inquirer.prompt({ name: 'alchemy_menu', type: 'list', message: 'Please choose:', choices: alchemyChoices }) switch(answer.alchemy_menu) { case alchemyChoices[0]: updateSoftware() break case alchemyChoices[1]: installRequired() break case alchemyChoices[2]: let stdout = execSync('source ~/Alchemy/ingredients/lead && source ~/Alchemy/ingredients/gold && bitcoin_is_synced') console.log(`${stdout}`) break default: return false } return true } // Prints the Configure AO Features menu and executes the user's choice async function featuresMenu() { console.log(`\n${headerStyle('Configure AO Features')}`) const features = [ 'nginx host AO publicly over the world wide web', 'SSL/Certbot HTTPS for public web AO', 'Tor connect AOs p2p', 'Bitcoin payments', 'Lightning payments', 'Jitsi secure video chat', 'Signal notifications', 'File hosting file attachments on cards', 'youtube-dl cache web videos', 'Borg backup', 'Encryption serverside secret messages', 'Themes custom themes', 'Glossary custom glossary', 'Jubilee monthly points creation event', 'Back to Main Menu' ] const answer = await inquirer.prompt({ name: 'features_menu', type: 'list', message: 'Please choose:', choices: features, pageSize: features.length }) switch(answer.features_menu) { case 'Back to Main Menu': return false default: console.log("Not yet implemented") return true } return true } // Ask the user for their name and returns it async function askName() { const answer = await inquirer.prompt({ name: 'member_name', type: 'input', message: 'What username would you like?' }) return answer.member_name } // Prints the given todoItems (array of strings) and allows items to be checked and unchecked async function todoList(title, todoItems) { console.log(`\n${headerStyle(title)}`) const answer = await inquirer.prompt({ name: 'todo_list', type: 'checkbox', message: 'Check or uncheck items with Spacebar:', choices: todoItems }) } // Detects which version(s) of the AO are installed (ao-3, ao-react, or ao-v) function detectAoVersion() { } // Main entry point async function main() { // No root allowed (todo) exitIfRoot() // Loading screen, display some quick info during the fun animation distro = detectOS() if(checkAoEnvFile()) { console.log('AO .env file exists at', AO_ENV_FILE_PATH) } else { console.log('AO .env file does not exist at', AO_ENV_FILE_PATH) } await unicornPortal(650) // Main AO title screen and flavor text clearScreen() asciiArt() await welcome() // Main loop while(await mainMenu()) {} process.exit(0) } await main()