#!/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 } from './scripts/manual.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 } from './scripts/features.js'
import { startPublicBootstrap } from './scripts/bootstrap.js'
// These should become .env variables that are loaded intelligently
const MANUAL _PATH = process . env . HOME + '/.ao/manual'
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' :
await printManualPage ( MANUAL _PATH ) // Fencepost case - print overview page
let previousChoice = 0
do {
previousChoice = await manualFolderAsMenu ( 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 ( )