An interactive command-line interface (CLI) tool to help you install, use, and administer an AO instance.
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.
 
 
 

345 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 } 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()