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.
308 lines
13 KiB
308 lines
13 KiB
// Functions related to intelligently installing the AO as a whole. Specific additional feature modules are each a file under ../features. |
|
import path from 'path' |
|
import { execSync } from 'child_process' |
|
import { aoEnv, setAoEnv } from '../ao-lib/settings.js' |
|
import { detectOS, updateSoftware, isInstalled, installIfNotInstalled } from './system.js' |
|
import { isFolder, isFile } from './files.js' |
|
import { aoIsInstalled } from '../features/ao-server.js' |
|
import features from '../features/index.js' |
|
import { asciiArt } from './console.js' |
|
import { headerStyle, heading2, styledStatus } from './styles.js' |
|
import { yesOrNo, promptMenu } from './welcome.js' |
|
import { isNpmPackageInstalled } from './system.js' |
|
|
|
const AO_PATH = path.join(process.env.HOME, '.ao') |
|
const AO_MEMES_PATH = path.join(AO_PATH, 'memes') |
|
|
|
const commonPackages = ['curl', 'wget', 'git', 'make', 'sqlite3', 'python', 'autoconf-archive'] |
|
const debianPackages = ['build-essential', 'zlib1g-dev', 'libtool-bin', 'autoconf', 'automake autotools-dev', 'libgmp-dev', 'libsqlite3-dev', 'python3', 'python3-mako', 'libsodium-dev', 'pkg-config', 'libev-dev', 'libcurl4-gnutls-dev', 'libssl-dev', 'fakeroot', 'devscripts'] |
|
const archPackages = ['gmp', 'pkgconf', 'libev', 'python-mako', 'python-pip', 'net-tools', 'zlib', 'libsodium', 'gettext', 'nginx'] // plus base-devel which is a group of packages |
|
const fedoraPackages = ['autoconf', 'automake', 'python3', 'python3-mako', 'pkg-config', 'fakeroot', 'devscripts'] |
|
|
|
const minimalFeatures = [ 'ao-cli', 'tor', 'alchemy' ] |
|
const standardFeatures = [ 'bitcoin', 'lightning', 'jitsi', 'files', 'youtube-dl', 'themes', 'glossary' ] |
|
const fullFeatures = Object.keys(features).filter(feature => ![...minimalFeatures, ...standardFeatures].includes(feature)) |
|
|
|
// Friendly interactive install wizard walks you through the entire process of installing and configuring a version of the AO and its features |
|
export default async function aoInstallWizard() { |
|
asciiArt('AO Installer') |
|
console.log('Welcome to the AO installer. The Coalition of Invisible Colleges is currently in licensing negotiations between the Autonomous Organization and Zen to acquire rights to display Zen\'s welcome text here, which is better than this irony.') |
|
const level = await chooseInstallLevel() |
|
if(!level) { |
|
console.log('Install canceled.') |
|
return |
|
} |
|
console.log('Proceeding with', level, 'installation.') |
|
const version = await chooseAoVersion() |
|
if(!version) { |
|
console.log('Install canceled.') |
|
return |
|
} |
|
// Ask them how they would like to host the AO (private on this computer only, public via tor only, public website via HTTPS/SSL) |
|
updateSoftware() |
|
createAoDirectories() |
|
installRequired() |
|
setNodeVersion() |
|
if(!aoIsInstalled(version)) { |
|
installAo(version) |
|
} |
|
//configureAO() // set required ENV variables (are any still required? make all optional?) |
|
if(level === 'standard' || level === 'full') { |
|
//if(!features.bitcoin.isInstalled()) features.bitcoin.install() |
|
//if(!features.lightning.isInstalled()) features.lightning.install() |
|
console.log('Skipping manual, tor, bitcoin, lightning, jitsi, and configuration of themes, glossary, jubilee (coming soon)') |
|
} |
|
|
|
if(level === 'full') { |
|
console.log('Skipping youtube-dl, Signal, borg (coming soon)') //maybe can just loop through all feature modules here |
|
} |
|
console.log('Skipping SSL/Certbot (coming soon)') // Ask them at the beginning but do it here |
|
console.log('The AO is installed.') |
|
} |
|
|
|
// Asks if the user wants to do a minimal, standard, or full install and returns their answer |
|
async function chooseInstallLevel() { |
|
return await promptMenu([ |
|
{ title: 'Minimal'.padEnd(11) + 'only core AO web server (or only ao-cli)', value: 'minimal', short: 'minimal install' }, |
|
{ title: 'Standard'.padEnd(11) + 'most AO features installed (recommended)', value: 'standard', short: 'standard install' }, |
|
{ title: 'Full'.padEnd(11) + 'all AO features installed', value: 'full', short: 'full install' }, |
|
{ title: 'Cancel', value: false } |
|
], 'What kind of installation?') |
|
} |
|
|
|
// Asks whether the user wants to install ao-svelte, ao-3, or ao-cli (only) and returns their choice |
|
export async function chooseAoVersion() { |
|
console.log('Active version:', aoEnv('AO_VERSION')) |
|
const answer = await promptMenu([ |
|
{ title: 'ao-svelte'.padEnd(12) + 'new and mobile-first, currently in prototype phase', value: 'ao-svelte', short: 'ao-svelte' }, |
|
{ title: 'ao-3'.padEnd(12) + 'the original, created in Vue 3, polished and bug-free', value: 'ao-3', short: 'ao-3' }, |
|
{ title: 'ao-cli only'.padEnd(12), value: 'ao-cli' }, |
|
{ title: 'Cancel', value: false } |
|
], 'Choose AO Version:') |
|
setAoEnv('AO_VERSION', answer.version_menu) |
|
// todo: If the version has changed, install it now |
|
console.log('AO version choice saved.') |
|
return answer |
|
} |
|
|
|
function checkAoDirectories() { |
|
return isFolder(AO_MEMES_PATH) |
|
} |
|
|
|
// Creates the directories to store the AO's database, memes, manual, and maybe other things, ~/.ao by standard |
|
export function createAoDirectories() { |
|
try { |
|
execSync('mkdir -p ' + AO_MEMES_PATH) |
|
} catch(error) { |
|
console.log('Error creating ~/.ao/memes directory. Maybe it already exists.') |
|
} |
|
} |
|
|
|
// Returns true if nvm is installed |
|
function checkNvm() { |
|
try { |
|
execSync('. ~/.nvm/nvm.sh && nvm -v') |
|
return true |
|
} catch(error) { |
|
console.log(error.stdout.toString()) |
|
return false |
|
} |
|
} |
|
|
|
function installNvm() { |
|
try { |
|
execSync('[ -z $NVM_DIR ]') |
|
execSync('source ~/Alchemy/ingredients/iron && install_nvm') |
|
console.log(`Installed nvm.`) |
|
return true |
|
} catch(err) { |
|
return false |
|
} |
|
} |
|
|
|
// Returns an object containing a list of requirement: installed (boolean) |
|
export function checkRequired() { |
|
const distro = detectOS() |
|
if(!distro) { |
|
console.log("Your OS was not recognized, so nothing was checked, sorry.") |
|
return false |
|
} |
|
let summary = {} |
|
console.log('Checking AO prerequisites for ' + distro.toTitleCase() + '...') |
|
// Check OS-specific requirements |
|
const checkEachPackage = (packages) => { |
|
packages.forEach(packageName => { |
|
summary[packageName] = isInstalled(packageName) |
|
}) |
|
} |
|
checkEachPackage(commonPackages) |
|
summary['nvm'] = checkNvm() |
|
switch(distro) { |
|
case 'debian': |
|
// Some of these might not be required |
|
checkEachPackage(debianPackages) |
|
break |
|
case 'arch': |
|
summary['base-devel'] = isInstalled('base-devel', true, true) |
|
checkEachPackage(archPackages) |
|
break |
|
case 'fedora': |
|
checkEachPackage(fedoraPackages) |
|
break |
|
} |
|
return summary |
|
} |
|
|
|
// Prints out a summary of the AO's installation status. Returns true if the AO is installed (Standard + ao-svelte or ao-3, not ao-cli-only) |
|
export async function checkAo() { |
|
const width = 19 |
|
const columnize = (status) => styledStatus(status, width) |
|
// Print status of each required package individually |
|
console.log(`\n${heading2('Required Software Packages')}`) |
|
const summary = Object.entries(checkRequired()) |
|
let installedRequirementCount = 0 |
|
summary.forEach(([packageName, isInstalled]) => { |
|
if(isInstalled) installedRequirementCount++ |
|
console.log(packageName.padEnd(width) + columnize(isInstalled ? 'Installed' : 'Missing')) |
|
}) |
|
const prerequisitesInstalled = summary.every(([packageName, isInstalled]) => isInstalled) |
|
|
|
// Check for existence of required directories |
|
console.log(`\n${heading2('Folders & Config File')}`) |
|
const requiredDirectoriesExist = checkAoDirectories() |
|
console.log('~/.ao'.padEnd(width) + (isFolder(AO_PATH) ? columnize('Created') : columnize('Missing'))) |
|
|
|
// Check for .env file |
|
const aoEnvFilePath = path.join(process.env.HOME, '.ao/.env') |
|
const hasEnvFile = isFile(aoEnvFilePath) |
|
console.log('~/.ao/.env'.padEnd(width) + columnize(hasEnvFile ? 'Initialized' : 'Blank')) |
|
|
|
console.log('~/.ao/memes'.padEnd(width) + columnize(requiredDirectoriesExist ? 'Created' : 'Missing')) |
|
|
|
// Check for node project folders and locally installed packages (npm i) |
|
console.log(`\n${heading2('AO Server + Client Version Installed')}`) |
|
const homePathsToCheck = ['ao-server', 'ao-svelte', 'ao-3'] |
|
let homePathsExist = [] |
|
let npmInstalled = [] |
|
homePathsToCheck.forEach(folderName => { |
|
const folderPath = path.join(process.env.HOME, folderName) |
|
const exists = isFolder(folderPath) |
|
let npmI = false |
|
if(exists) { |
|
homePathsExist.push(folderName) |
|
npmI = isNpmPackageInstalled(folderPath) |
|
if(npmI) { |
|
npmInstalled.push(folderName) |
|
} |
|
} |
|
console.log(folderName.padEnd(width) + columnize(exists ? npmI ? 'Installed' : 'Downloaded' : 'Not Installed')) |
|
}) |
|
const hasAnAo = (homePathsExist.includes('ao-server') && homePathsExist.includes('ao-svelte')) || homePathsExist.includes('ao-3') |
|
|
|
// Check which packages are installed, and based on three additive lists of requirements, determine Minimal, Standard, or Full install |
|
console.log(`\n${heading2('Optional Features')}`) |
|
let optionalInstalls = [] |
|
Object.entries(features).forEach(([shortname, feature]) => { |
|
const name = feature?.name || shortname |
|
const isInstalled = feature.hasOwnProperty('isInstalled') ? feature.isInstalled() : ['installed', 'enabled', 'running', 'synced'].includes(feature.status()) |
|
if(isInstalled) optionalInstalls.push(shortname) |
|
console.log(name.padEnd(width) + columnize(isInstalled ? 'Installed' : 'Not Installed')) |
|
}) |
|
|
|
console.log(`\n${heading2('Summary')}`) |
|
let installAttained = null |
|
const otherPrereqs = requiredDirectoriesExist && hasAnAo && hasEnvFile |
|
if(otherPrereqs && prerequisitesInstalled && minimalFeatures.every(shortname => optionalInstalls.includes(shortname))) { |
|
installAttained = 'Minimal' |
|
} |
|
if(installAttained && standardFeatures.every(shortname => optionalInstalls.includes(shortname))) { |
|
installAttained = 'Standard' |
|
} |
|
if(installAttained === 'Standard' && fullFeatures.every(shortname => optionalInstalls.includes(shortname))) { |
|
installAttained = 'Full' |
|
} |
|
console.log(installedRequirementCount + '/' + summary.length, 'required packages installed.') |
|
console.log(optionalInstalls.length + '/' + Object.keys(features).length, 'optional features installed.') |
|
if(!installAttained) { |
|
console.log("You have not installed the AO; the required packages were not detected.") |
|
} else { |
|
console.log('Recognized this set of packages as a', installAttained, 'install.') |
|
} |
|
console.log('Selected AO_VERSION is', aoEnv('AO_VERSION') + '\n') |
|
|
|
if(installAttained) console.log('The AO is installed.') |
|
else { |
|
console.log('The AO is not installed yet. Would you like to install it now?') |
|
const answer = await yesOrNo('Start AO install wizard?', true) |
|
if(answer) { |
|
await aoInstallWizard() |
|
} |
|
} |
|
} |
|
|
|
// Installs core dependencies required by Alchemy and the AO |
|
export function installRequired() { |
|
const distro = detectOS() |
|
if(!distro) { |
|
console.log("Your OS was not recognized, so nothing was installed, sorry.") |
|
return false |
|
} |
|
console.log('Installing Alchemy and AO installation process core dependencies (fast if already installed)...') |
|
console.log(`(You may need to input your ${chalk.blue.bold("'sudo' password")} here)`) |
|
|
|
// Install on every OS |
|
installIfNotInstalled(commonPackages) |
|
installNvm() |
|
|
|
// Install OS-specific requirements |
|
switch(distro) { |
|
case 'debian': |
|
// Some of these might not be required |
|
installIfNotInstalled(debianPackages) |
|
break |
|
case 'arch': |
|
installIfNotInstalled('base-devel', true, true) |
|
installIfNotInstalled(archPackages) |
|
break |
|
case 'fedora': |
|
installIfNotInstalled(fedoraPackages) |
|
break |
|
} |
|
return true |
|
} |
|
|
|
// Sets node to the current version used by the AO |
|
function setNodeVersion() { |
|
execSync('source ~/Alchemy/ingredients/lead && source ~/Alchemy/ingredients/iron && set_node_to v16.13.0') |
|
} |
|
|
|
// Checks if the AO server is running, and if not, offers to start it |
|
export async function checkAoServerInteractive() { |
|
const aoVersion = aoEnv('AO_VERSION') || 'ao-server' |
|
switch(aoVersion) { |
|
case 'ao-3': |
|
console.log('Check for running ao-3 server not yet implemented') |
|
return false |
|
case 'ao-server': |
|
case 'ao-svelte': |
|
case 'ao-cli': |
|
if(!features['ao-server'].submodules[0].isInstalled()) { |
|
const response = await yesOrNo('ao-server background service is not installed. Install it now?') |
|
if(response === true) { |
|
features['ao-server'].submodules[0].install() |
|
} else { |
|
return false |
|
} |
|
} |
|
if(features['ao-server'].status() === 'running') { |
|
return true |
|
} else { |
|
const response = await yesOrNo('ao-server is not running. Start it now?') |
|
if(response === true) { |
|
features['ao-server'].submodules[0].start() |
|
return true |
|
} |
|
return false |
|
} |
|
} |
|
}
|
|
|