You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
11 KiB
370 lines
11 KiB
#!/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, loadJsonFile } 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, getAoCliVersion, selfUpdate, 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\'', |
|
'Update 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 'Install \'ao\' alias for \'ao-cli\'': |
|
installAoAlias() |
|
break |
|
case 'Update ao-cli': |
|
await selfUpdate() |
|
break |
|
case 'Check AO install': |
|
case 'Update AO': |
|
case 'Configure AO features': |
|
while(await featuresMenu()) {} |
|
break |
|
case 'Switch AO database': |
|
case 'Import/Export state/decks': |
|
case 'Check AO service': |
|
case 'Watch logs now': |
|
case 'Start/Stop AO service': |
|
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() { |
|
|
|
} |
|
|
|
// Returns false if a flag means the program should now terminate |
|
// -v Print version info |
|
async function handleArgs(args) { |
|
switch (args[0]) { |
|
case '--version': |
|
case '-v': |
|
console.log(await getAoCliVersion()) |
|
return false |
|
} |
|
return true |
|
} |
|
|
|
// Main entry point |
|
async function main() { |
|
// Print version info etc. no matter what |
|
const nodePath = process.argv[0] |
|
const aoCliPath = process.argv[1] |
|
const args = process.argv.slice(2) |
|
let shouldTerminate = !await handleArgs(args) |
|
if(shouldTerminate) { |
|
process.exit(0) |
|
} |
|
|
|
// 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()
|
|
|