#!/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 , greenChalk } from './scripts/styles.js'
import './scripts/strings.js'
import { installAoAlias , getAoCliVersion , selfUpdate , downloadManual , updateManual } from './scripts/features.js'
import { startPublicBootstrap } from './scripts/bootstrap.js'
import { isLoggedIn , loginPrompt , logout } from './scripts/session.js'
import { AO _DEFAULT _HOSTNAME } from './scripts/api.js'
import { getTopPriorityText } from './scripts/priority.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 ` )
let mainMenuChoices = [
'AO' ,
'Features' ,
'Admin' ,
'Alchemy' ,
'Manual' ,
'Exit'
]
const answer = await inquirer . prompt ( {
name : 'main_menu' ,
type : 'list' ,
message : 'Please choose:' ,
choices : mainMenuChoices ,
pageSize : mainMenuChoices . length
} )
switch ( answer . main _menu ) {
case 'AO' :
while ( await useAoMenu ( ) ) { }
break
case 'Features' :
while ( await featuresMenu ( ) ) { }
break
case 'Admin' :
while ( await adminMenu ( ) ) { }
break
case 'Alchemy' :
while ( await alchemyMenu ( ) ) { }
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
do {
previousChoice = await manualFolderAsMenu ( AO _MANUAL _PATH , 'AO User Manual' , 'Back to Main Menu' , previousChoice + 1 )
} while ( previousChoice !== false )
break
case 'Exit' :
farewell ( )
await sleep ( 310 )
return false
}
return true
}
// Prints the Use AO Menu and executes the user's choice
async function useAoMenu ( ) {
const loggedIn = isLoggedIn ( )
console . log ( ` \n ${ headerStyle ( 'AO' ) } \n ` )
console . log ( 'Top priority:' , await getTopPriorityText ( ) )
let aoMenuChoices = [ ]
if ( loggedIn ) {
aoMenuChoices . push (
'Chat' ,
'Deck' ,
)
}
aoMenuChoices . push (
loggedIn ? 'Log Out' : 'Log In' ,
'Back to Main Menu'
)
const answer = await inquirer . prompt ( {
name : 'ao_menu' ,
type : 'list' ,
message : 'Please choose:' ,
choices : aoMenuChoices ,
pageSize : aoMenuChoices . length
} )
switch ( answer . ao _menu ) {
case 'Chat' :
while ( await chatMenu ( ) ) { }
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 'Log In' :
console . log ( '\nao-cli will use the AO API to log into the AO server at' , ( aoEnv ( 'AO_CLI_TARGET_HOSTNAME' ) || AO _DEFAULT _HOSTNAME ) + '.' )
await loginPrompt ( )
break
case 'Log Out' :
await logout ( )
//await spinnerWait('Logging out...')
break
case 'Back to Main Menu' :
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
// Maybe Alchemy menu should be installation and update, and admin menu should be more configuration & AO member admin
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' ,
'Switch AO target server' ,
'Switch AO database' ,
'Import/Export state/decks' ,
'Check AO service' ,
'Watch logs now' ,
'Start/Stop AO service' ,
'Tests' ,
'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 'Switch AO target server' :
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
case 'Tests' :
while ( await testsMenu ( ) ) { }
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 status = {
off : ' ' + chalk . grey ( 'Off' ) + ' ' ,
ins : chalk . yellow ( 'Installed' ) + ' ' ,
ena : ' ' + greenChalk ( 'Enabled' ) + ' ' ,
run : ' ' + greenChalk ( 'Running' ) + ' ' ,
err : ' ' + chalk . red ( 'Error' ) + ' '
}
let widest = 9
const features = [
` Tor ${ status . run } connect AOs p2p ` ,
` Bitcoin ${ status . run } payments ` ,
` Lightning ${ status . ins } payments ` ,
` nginx ${ status . ins } host AO publicly over the world wide web ` ,
` SSL/Certbot ${ status . ins } HTTPS for public web AO ` ,
` Jitsi ${ status . err } secure video chat ` ,
` Signal ${ status . off } notifications ` ,
` File hosting ${ status . err } file attachments on cards ` ,
` youtube-dl ${ status . ins } cache web videos ` ,
` Borg ${ status . ins } backup ` ,
` Encryption ${ status . err } serverside secret messages ` ,
` Themes ${ status . err } custom themes ` ,
` Glossary ${ status . err } custom glossary ` ,
` Jubilee ${ status . ena } 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
}
// 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 = aoEnv ( 'DISTRO' )
if ( ! distro ) {
distro = detectOS ( )
setAoEnv ( 'DISTRO' , distro )
}
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 ( )