install.fairie/home/dot_local/bin/executable_install-program
Brian Zalewski 2cd05c9a73 Update 3 files
- /home/.chezmoidata.yaml
- /home/dot_local/bin/executable_install-program
- /software.yml
2023-01-09 05:20:41 +00:00

1566 lines
61 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env zx
const execSync = require('child_process').execSync
// Log symbols
const figuresDefault = {
bullet: '●',
circle: '◯',
cross: '✖',
lozenge: '◆',
play: '▶',
pointer: '',
square: '◼',
star: '★',
tick: '✔'
}
const figuresFallback = {
bullet: '■',
circle: '□',
cross: '×',
lozenge: '♦',
play: '►',
pointer: '>',
square: '■',
star: '✶',
tick: '√'
}
function isUnicodeSupported() {
if (process.platform !== 'win32') {
// Linux console (kernel)
return process.env.TERM !== 'linux'
}
return (
Boolean(process.env.CI) ||
// Windows Terminal
Boolean(process.env.WT_SESSION) ||
// ConEmu and cmder
process.env.ConEmuTask === '{cmd::Cmder}' ||
process.env.TERM_PROGRAM === 'vscode' ||
process.env.TERM === 'xterm-256color' ||
process.env.TERM === 'alacritty'
)
}
const figures = isUnicodeSupported() ? figuresDefault : figuresFallback
function log(type, label, msg) {
let icon, message
if (type === 'info') {
icon = chalk.cyanBright(figures.pointer)
message = chalk.gray.bold(msg)
} else if (type === 'star') {
icon = chalk.yellowBright(figures.star)
message = chalk.bold(msg)
} else if (type === 'success') {
icon = chalk.greenBright(figures.play)
message = chalk.bold(msg)
} else if (type === 'warn') {
icon = `${chalk.yellowBright(figures.lozenge)} ${chalk.bold.black.bgYellowBright(' WARNING ')}`
message = chalk.yellowBright(msg)
} else if (type === 'error') {
icon = `${chalk.redBright(figures.cross)} ${chalk.black.bold.bgRedBright(' ERROR ')}`
message = chalk.redBright(msg)
}
const outputMessage = `${icon} ${chalk.bold(label)} ${message}`
console.log(outputMessage)
}
function runCommand(spinnerTitle, command) {
execSync(`gum spin --spinner dot --title "${spinnerTitle}" -- ${command}`, {
stdio: 'inherit',
shell: true
})
}
async function runSilentCommand(command) {
execSync(`${command}`, { stdio: 'inherit', shell: true })
}
function fileExists(pathToFile) {
return fs.existsSync(pathToFile)
}
let installData
let installOrders = {}
const installOrdersPre = []
const installOrdersPost = []
const installOrdersService = []
const installOrdersPlugins = []
let brewUpdated, osType, osID, snapRefreshed
// Download the installation map
async function downloadInstallData() {
const response = await fetch('https://gitlab.com/megabyte-labs/hiawatha-dotfiles/-/raw/master/software.yml')
if (response.ok) {
const text = await response.text()
return YAML.parse(text)
} else {
log('error', 'Catalog Download', `Failed to download the installation map`)
log('info', 'Catalog Download', `Falling back to local version of software.yml`)
const text = fs.readFileSync(process.env.HOME + '/.local/share/chezmoi/software.yml').toString()
return YAML.parse(text)
}
}
// Creates the installOrders object which maps package managers to arrays of packages to install
let generateInstallOrderCount = 0
async function generateInstallOrders(pkgsToInstall) {
const installerPreference = await OSTypeInstallerKey()
const preferenceOrder = installData.installerPreference[installerPreference]
const logStage = 'Install Orders'
const packagesToInstall = pkgsToInstall
const softwarePackages = installData.softwarePackages
if (generateInstallOrderCount === 0) {
log('info', logStage, `Installer preference category detected as ${installerPreference}`)
log('info', logStage, `Preference order acquired:`)
console.log(preferenceOrder)
}
generateInstallOrderCount++
log('info', logStage, `New packages discovered for processing: ${pkgsToInstall} (${pkgsToInstall.length} items)`)
pkgFor: for (let pkg of packagesToInstall) {
let packageKey
if (softwarePackages[pkg + ':' + osID]) {
packageKey = pkg + ':' + osID
} else if (softwarePackages[pkg + ':' + osType]) {
packageKey = pkg + ':' + osType
} else if (softwarePackages[pkg]) {
packageKey = pkg
} else {
log('warn', logStage, `The package \`${pkg}\` was not found in the installation map`)
continue
}
for (let preference of preferenceOrder) {
let currentSelector, doubleScoped, scopedPkgManager, scopedSystem, normalCheck
if (
softwarePackages[packageKey][preference + ':' + osID] ||
softwarePackages[packageKey][preference + ':' + osType] ||
softwarePackages[packageKey][preference]
) {
// Handle the _when attribute
currentSelector = 'when'
doubleScoped =
softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osID] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osID + ':' + preference] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osType] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osType + ':' + preference]
scopedPkgManager = softwarePackages[packageKey]['_' + currentSelector + ':' + preference]
scopedSystem =
softwarePackages[packageKey]['_' + currentSelector + ':' + osID] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osType]
normalCheck = softwarePackages[packageKey]['_' + currentSelector]
if (doubleScoped) {
try {
await $doubleScoped
} catch (e) {
let pref
if (softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osID]) {
pref = preference + ':' + osID
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osType]) {
pref = preference + ':' + osType
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID + ':' + preference]) {
pref = osID + ':' + preference
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType + ':' + preference]) {
pref = osType + ':' + preference
}
log('info', 'Filter', `${pkg} is being skipped because of the _when:${pref} condition`)
continue pkgFor
}
} else if (scopedPkgManager) {
try {
await $scopedPkgManager
} catch (e) {
const pref = preference
log('info', 'Filter', `${pkg} is being skipped because of the _when:${pref} condition`)
continue pkgFor
}
} else if (scopedSystem) {
try {
await $scopedSystem
} catch (e) {
let pref
if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID]) {
pref = osID
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType]) {
pref = osType
}
log('info', 'Filter', `${pkg} is being skipped because of the _when:${pref} condition`)
continue pkgFor
}
} else if (normalCheck) {
try {
await $(normalCheck)
} catch (e) {
log('info', 'Filter', `${pkg} is being skipped because of the _when condition`)
continue pkgFor
}
}
// Handle the _bin attribute
currentSelector = 'bin'
doubleScoped =
softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osID] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osID + ':' + preference] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osType] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osType + ':' + preference]
scopedPkgManager = softwarePackages[packageKey]['_' + currentSelector + ':' + preference]
scopedSystem =
softwarePackages[packageKey]['_' + currentSelector + ':' + osID] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osType]
normalCheck = softwarePackages[packageKey]['_' + currentSelector]
if (doubleScoped) {
const bin =
typeof doubleScoped === 'string'
? which.sync(doubleScoped, { nothrow: true })
: doubleScoped.map((x) => which.sync(x, { nothrow: true })).every((y) => !!y)
if (bin) {
let pref
if (softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osID]) {
pref = preference + ':' + osID
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osType]) {
pref = preference + ':' + osType
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID + ':' + preference]) {
pref = osID + ':' + preference
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType + ':' + preference]) {
pref = osType + ':' + preference
}
log('info', 'Filter', `${bin} already in PATH (via _bin:${pref})`)
continue pkgFor
}
} else if (scopedPkgManager) {
const bin =
typeof scopedPkgManager === 'string'
? which.sync(scopedPkgManager, { nothrow: true })
: scopedPkgManager.map((x) => which.sync(x, { nothrow: true })).every((y) => !!y)
if (bin) {
const pref = preference
log('info', 'Filter', `${bin} already in PATH (via _bin:${pref})`)
continue pkgFor
}
} else if (scopedSystem) {
const bin =
typeof scopedSystem === 'string'
? which.sync(scopedSystem, { nothrow: true })
: scopedSystem.map((x) => which.sync(x, { nothrow: true })).every((y) => !!y)
if (bin) {
let pref
if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID]) {
pref = osID
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType]) {
pref = osType
}
log('info', 'Filter', `${bin} already in PATH (via _bin:${pref})`)
continue pkgFor
}
} else if (normalCheck) {
const bin =
typeof normalCheck === 'string'
? which.sync(normalCheck, { nothrow: true })
: normalCheck.map((x) => which.sync(x, { nothrow: true })).every((y) => !!y)
if (bin) {
log('info', 'Filter', `${bin} already in PATH (via _bin)`)
continue pkgFor
}
}
// Handle the _deps attribute
currentSelector = 'deps'
doubleScoped =
softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osID] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osID + ':' + preference] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osType] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osType + ':' + preference]
scopedPkgManager = softwarePackages[packageKey]['_' + currentSelector + ':' + preference]
scopedSystem =
softwarePackages[packageKey]['_' + currentSelector + ':' + osID] ||
softwarePackages[packageKey]['_' + currentSelector + ':' + osType]
normalCheck = softwarePackages[packageKey]['_' + currentSelector]
const dependenciesTag = 'Dependencies'
if (doubleScoped) {
let pref
if (softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osID]) {
pref = '_deps:' + preference + ':' + osID
log('info', dependenciesTag, `Installing dependencies for ${packageKey}.${pref}`)
await generateInstallOrders(softwarePackages[packageKey][pref])
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osType]) {
pref = '_deps:' + preference + ':' + osType
log('info', dependenciesTag, `Installing dependencies for ${packageKey}.${pref}`)
await generateInstallOrders(softwarePackages[packageKey][pref])
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID + ':' + preference]) {
pref = '_deps:' + osID + ':' + preference
log('info', dependenciesTag, `Installing dependencies for ${packageKey}.${pref}`)
await generateInstallOrders(softwarePackages[packageKey][pref])
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType + ':' + preference]) {
pref = '_deps:' + osType + ':' + preference
log('info', dependenciesTag, `Installing dependencies for ${packageKey}.${pref}`)
await generateInstallOrders(softwarePackages[packageKey][pref])
}
} else if (scopedPkgManager) {
const pref = '_deps:' + preference
log('info', dependenciesTag, `Installing dependencies for ${packageKey}.${pref}`)
await generateInstallOrders(softwarePackages[packageKey][pref])
} else if (scopedSystem) {
let pref
if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID]) {
pref = '_deps:' + osID
log('info', dependenciesTag, `Installing dependencies for ${packageKey}.${pref}`)
await generateInstallOrders(softwarePackages[packageKey][pref])
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType]) {
pref = '_deps:' + osType
log('info', dependenciesTag, `Installing dependencies for ${packageKey}.${pref}`)
await generateInstallOrders(softwarePackages[packageKey][pref])
}
} else if (normalCheck) {
log('info', dependenciesTag, `Installing dependencies for ${packageKey}.deps`)
await generateInstallOrders(softwarePackages[packageKey]['_deps'])
}
if (softwarePackages[packageKey][preference + ':' + osID]) {
await updateInstallMaps(
preference,
softwarePackages[packageKey],
preference + ':' + osID,
pkg,
packageKey,
softwarePackages
)
break
} else if (softwarePackages[packageKey][preference + ':' + osType]) {
await updateInstallMaps(
preference,
softwarePackages[packageKey],
preference + ':' + osType,
pkg,
packageKey,
softwarePackages
)
break
} else if (softwarePackages[packageKey][preference]) {
await updateInstallMaps(
preference,
softwarePackages[packageKey],
preference,
pkg,
packageKey,
softwarePackages
)
break
}
}
}
}
if (generateInstallOrderCount === 1) {
return installOrders
} else {
generateInstallOrderCount--
}
}
// Update install, pre-hook, and post-hook objects
async function updateInstallMaps(preference, packages, scopedPreference, pkg, packageKey, softwarePackages) {
const preHook = getHook(packages, 'pre', scopedPreference, preference)
if (preHook) {
installOrdersPre.concat(typeof preHook === 'string' ? [preHook] : preHook)
}
const postHook = getHook(packages, 'post', scopedPreference, preference)
if (postHook) {
installOrdersPost.concat(typeof postHook === 'string' ? [postHook] : postHook)
}
const serviceHook = getHook(packages, 'service', scopedPreference, preference)
if (serviceHook) {
installOrdersService.concat(typeof serviceHook === 'string' ? [serviceHook] : serviceHook)
}
const pluginMap = installData && installData.softwarePlugins && installData.softwarePlugins[pkg]
if (pluginMap) {
if (pluginMap.cmd && pluginMap.plugins) {
installOrdersPlugins.push({ package: pkg, cmd: pluginMap.cmd, plugins: pluginMap.plugins })
}
}
if (!installOrders[preference]) {
installOrders[preference] = []
}
log('info', 'Match', `Found a match for the package \`${pkg}\` (${packageKey} via ${scopedPreference})`)
const newPackages = packages[scopedPreference]
const newPkgs = typeof newPackages === 'string' ? [newPackages] : newPackages
if (preference === 'snap' && softwarePackages[pkg]['_snapClassic'] === true) {
if (!installOrders[preference + '-classic']) {
installOrders[preference + '-classic'] = []
}
installOrders[preference + '-classic'] = installOrders[preference].concat(newPkgs)
} else {
installOrders[preference] = installOrders[preference].concat(newPkgs)
}
}
// Get pre / post install hooks
function getHook(packages, hook, scopedPreference, preference) {
const hookLabel = '_' + hook + ':'
if (packages[hookLabel + scopedPreference]) {
return packages[hookLabel + scopedPreference]
} else if (packages[hookLabel + preference]) {
return packages[hookLabel + preference]
} else if (packages[hookLabel + osID]) {
return packages
} else if (packages[hookLabel + osType]) {
return packages[hookLabel + osType]
} else if (packages['_' + hook]) {
return packages['_' + hook]
}
}
// Acquire OS type installer key (for the installerPreference data key)
async function OSTypeInstallerKey() {
try {
const apt = which.sync('apt-get', { nothrow: true })
const dnf = which.sync('dnf', { nothrow: true })
const freebsdPkg = which.sync('pkg', { nothrow: true })
const freebsdVersion = which.sync('freebsd-version', { nothrow: true })
const pacman = which.sync('pacman', { nothrow: true })
const yum = which.sync('yum', { nothrow: true })
const zypper = which.sync('zypper', { nothrow: true })
if (apt) {
return 'apt'
} else if (dnf || yum) {
return 'dnf'
} else if (pacman) {
return 'pacman'
} else if (zypper) {
return 'zypper'
} else if (freebsdPkg && freebsdVersion) {
return 'freebsd'
} else {
try {
await $`test -d /Applications && test -d /Library`
return 'darwin'
} catch (e) {
return 'windows'
}
}
} catch (e) {
log('error', 'OS Detection', 'There was an error determining the type of operating system')
console.error(e)
}
}
// Acquire OS type
async function OSType() {
try {
await $`test -d /Applications && test -d /Library`
return 'darwin'
} catch (e) {
try {
await $`test -f /etc/os-release`
return 'linux'
} catch (e) {
return 'windows'
}
}
}
// Acquire release ID (for Linux)
async function releaseID() {
const ID = await $`
if [ -f /etc/os-release ]; then
. /etc/os-release
echo -n $ID
fi
`
return ID.stdout
}
// Post-install hook
async function afterInstall(packageManager) {
const logStage = 'Post-Install Package Manager'
if (packageManager === 'appimage') {
} else if (packageManager === 'ansible') {
log('info', logStage, `Ensuring temporary passwordless sudo privileges used by Ansible are removed`)
const gsed = which.sync('gsed', { nothrow: true })
if (gsed) {
await $`sudo gsed -i '/# TEMPORARY FOR ANSIBLE INSTALL/d' /etc/sudoers`
} else {
await $`sudo sed -i '/# TEMPORARY FOR ANSIBLE INSTALL/d' /etc/sudoers`
}
} else if (packageManager === 'apk') {
} else if (packageManager === 'apt') {
try {
runCommand('Running apt-get autoclean', `sudo apt-get autoclean`)
runCommand('Running apt-get autoremove', `sudo apt-get -y autoremove`)
} catch (e) {
log('error', logStage, 'Error cleaning up apt-get')
}
} else if (packageManager === 'basher') {
} else if (packageManager === 'binary') {
} else if (packageManager === 'brew' || packageManager === 'cask') {
} else if (packageManager === 'cargo') {
} else if (packageManager === 'choco') {
} else if (packageManager === 'crew') {
} else if (packageManager === 'dnf') {
} else if (packageManager === 'flatpak') {
} else if (packageManager === 'gem') {
} else if (packageManager === 'go') {
} else if (packageManager === 'nix') {
} else if (packageManager === 'npm') {
} else if (packageManager === 'pacman') {
} else if (packageManager === 'pipx') {
} else if (packageManager === 'pkg') {
} else if (packageManager === 'port') {
} else if (packageManager === 'scoop') {
} else if (packageManager === 'script') {
} else if (packageManager === 'snap') {
} else if (packageManager === 'whalebrew') {
} else if (packageManager === 'winget') {
} else if (packageManager === 'yay') {
} else if (packageManager === 'zypper') {
}
}
async function ensurePackage(dep) {
const target = which.sync(dep, { nothrow: true })
if (!target) {
if (osType === 'linux') {
const apk = which.sync('apk', { nothrow: true })
const apt = which.sync('apt-get', { nothrow: true })
const dnf = which.sync('dnf', { nothrow: true })
const pkg = which.sync('pkg', { nothrow: true })
const yum = which.sync('yum', { nothrow: true })
const pacman = which.sync('pacman', { nothrow: true })
const zypper = which.sync('zypper', { nothrow: true })
if (apk) {
await $`sudo apk add ${dep}`
} else if (apt) {
if (updateDone['apt-get'] !== true) {
await beforeInstall('apt-get')
}
try {
log('info', 'apt-get Installation', `Checking if ${dep} is already installed`)
runCommand(`Checking if ${dep} is already installed via apt-get`, `dpkg -l ${dep} | grep -E '^ii' > /dev/null`)
log('info', 'Filter', `${pkg} already installed via apt-get`)
} catch (e) {
runCommand(`Installing ${dep} via apt-get`, `sudo apt-get -o DPkg::Options::=--force-confdef install -y ${dep}`)
log('success', 'Install', `Successfully installed ${pkg} via apt-get`)
}
} else if (dnf) {
if (updateDone['dnf'] !== true) {
await beforeInstall('dnf')
}
try {
log('info', 'dnf Installation', `Checking if ${dep} is already installed`)
await $`rpm -qa | grep ${dep} > /dev/null`
} catch (e) {
log('info', 'dnf Installation', `Installing ${dep} since it is not already present on the system`)
await $`sudo dnf install -y ${dep}`
}
} else if (yum) {
if (updateDone['yum'] !== true) {
await beforeInstall('yum')
}
try {
log('info', 'YUM Installation', `Checking if ${dep} is already installed`)
await $`rpm -qa | grep ${dep} > /dev/null`
} catch (e) {
log('info', 'YUM Installation', `Installing ${dep} since it is not already present on the system`)
await $`sudo yum install -y ${dep}`
}
} else if (pacman) {
if (updateDone['pacman'] !== true) {
await beforeInstall('pacman')
}
try {
log('info', 'Pacman Installation', `Checking if ${dep} is already installed`)
await $`pacman -Qs ${dep}`
} catch (e) {
log('info', 'Pacman Installation', `Installing ${dep} since it is not already present on the system`)
await $`sudo pacman -Sy ${dep}`
}
} else if (zypper) {
if (updateDone['zypper'] !== true) {
await beforeInstall('zypper')
}
try {
log('info', 'Zypper Installation', `Checking if ${dep} is already installed`)
await $`rpm -qa | grep ${dep} > /dev/null`
} catch (e) {
log('info', 'Zypper Installation', `Installing ${dep} since it is not already present on the system`)
await $`sudo zypper install -y ${dep}`
}
} else if (pkg) {
if (updateDone['pkg'] !== true) {
await beforeInstall('pkg')
}
try {
log('info', 'pkg Installation', `Checking if ${dep} is already installed`)
await $`pkg info -Ix ${dep} > /dev/null`
} catch (e) {
log('info', 'pkg Installation', `Installing ${dep} since it is not already present on the system`)
await $`sudo pkg install -y ${dep}`
}
}
} else if (osType === 'darwin') {
if (updateDone['brew'] !== true) {
await beforeInstall('brew')
}
await $`brew install ${dep}`
} else if (osType === 'windows') {
if (updateDone['choco'] !== true) {
await beforeInstall('choco')
}
await `choco install -y ${dep}`
}
}
}
// Pre-install hook
const updateDone = {}
async function beforeInstall(packageManager) {
updateDone[packageManager] = true
const logStage = 'Pre-Install Package Manager'
if (packageManager === 'appimage') {
} else if (packageManager === 'ansible') {
log('info', logStage, `Temporarily enabling passwordless sudo for Ansible role installations`)
await $`echo "$(whoami) ALL=(ALL:ALL) NOPASSWD: ALL # TEMPORARY FOR ANSIBLE INSTALL" | sudo tee -a /etc/sudoers`
log('info', logStage, 'Running Ansible setup task so facts are cached')
const unbuffer = which.sync('unbuffer', { nothrow: true })
let unbufferPrefix = ''
if (unbuffer) {
unbufferPrefix = 'unbuffer'
}
await $`${unbufferPrefix} ansible 127.0.0.1 -vv -e '{ ansible_connection: "local", ansible_become_user: "${process.env.USER}", ansible_user: "${process.env.USER}", install_homebrew: False }' -m setup`
} else if (packageManager === 'apk') {
await $`sudo apk update`
} else if (packageManager === 'apt') {
runCommand('Running apt-get update / upgrade', `sudo apt-get update && sudo apt-get upgrade -y`)
} else if (packageManager === 'basher') {
} else if (packageManager === 'binary') {
} else if (packageManager === 'brew' || packageManager === 'cask') {
if (!brewUpdated) {
brewUpdated = true
runCommand('Running brew update', `brew update`)
}
} else if (packageManager === 'cargo') {
} else if (packageManager === 'choco') {
} else if (packageManager === 'crew') {
await $`crew update`
} else if (packageManager === 'dnf') {
const dnf = which.sync('dnf', { nothrow: true })
const yum = which.sync('yum', { nothrow: true })
if (dnf) {
runCommand('Running dnf update', `sudo dnf update -y`)
} else if (yum) {
runCommand('Running yum update', `sudo yum update -y`)
}
} else if (packageManager === 'flatpak') {
runCommand('Running flatpak update', `sudo flatpak update -y`)
} else if (packageManager === 'gem') {
} else if (packageManager === 'go') {
} else if (packageManager === 'nix') {
runCommand('Running nix-channel --update', `nix-channel --update`)
} else if (packageManager === 'npm') {
} else if (packageManager === 'pacman') {
runCommand('Running pacman update', `sudo pacman -Syu`)
} else if (packageManager === 'pipx') {
} else if (packageManager === 'pkg') {
await $`sudo pkg upgrade`
} else if (packageManager === 'port') {
const port = which.sync('port', { nothrow: true })
if (port) {
runCommand('Running port sync', `sudo port sync`)
} else {
log('error', 'Port Not Installed', 'Skipping sudo port sync step because port is not installed')
}
} else if (packageManager === 'scoop') {
runCommand('Running scoop update', `scoop update`)
} else if (packageManager === 'snap' || packageManager === 'snap-classic') {
if (!snapRefreshed) {
snapRefreshed = true
runCommand('Ensuring snap is refreshed', `sudo snap refresh`)
}
} else if (packageManager === 'whalebrew') {
if (osType === 'darwin') {
const docker = which.sync('docker', { nothrow: true })
if (!docker) {
await $`brew install --cask docker`
}
try {
await $`docker run --rm hello-world`
} catch (e) {
log('warn', logStage, `The command \`docker run --rm hello-world\` failed`)
try {
log(
'info',
logStage,
'Attempting to open `/Applications/Docker.app` (Docker Desktop for macOS). This should take about 30 seconds.'
)
const promises = [$`test -d /Applications/Docker.app`, $`open /Applications/Docker.app`]
await Promise.all(promises)
const gum = which.sync('gum', { nothrow: true })
if (gum) {
execSync('gum spin --spinner dot --title "Waiting for Docker Desktop to start up.." -- sleep 30', {
stdio: 'inherit',
shell: true
})
} else {
await $`sleep 30`
}
} catch (e) {
log('warn', logStage, `Docker Desktop appears to not be installed!`)
}
}
}
} else if (packageManager === 'winget') {
runCommand('Running winget source update', `winget source update`)
} else if (packageManager === 'yay') {
} else if (packageManager === 'zypper') {
runCommand('Running zypper update', `sudo zypper update`)
}
}
async function ensureInstalled(bin, callback) {
const logStage = 'Package Manager Install'
const installed = which.sync(bin, { nothrow: true })
if (installed) {
log('info', logStage, `\`${bin}\` is available`)
} else {
log('warn', logStage, `\`${bin}\` is not installed!`)
if (callback) {
await callback
} else {
log('error', logStage, `There does not appear to be an installation method available for \`${bin}\``)
}
}
}
async function ensurePackageManagerAnsible() {
await $`pipx install ansible-core`
if (osType === 'darwin') {
await $`pipx inject ansible-core PyObjC PyObjC-core`
}
await $`pipx inject ansible-core docker lxml netaddr pexpect python-vagrant pywinrm requests-credssp watchdog`
await $`mkdir -p "$HOME/.cache/megabyte-labs"`
await $`touch "$HOME/.cache/megabyte-labs/ansible-installed"`
log('info', 'Package Manager Install', `Ansible and its supporting packages are now installed via pipx`)
}
// Ensure the package manager is available
let packageManagerInstalled = {}
async function ensurePackageManager(packageManager) {
const logStage = 'Pre-Reqs'
log('info', logStage, `Ensuring \`${packageManager}\` is set up`)
if (packageManagerInstalled[packageManager]) {
return
} else {
packageManagerInstalled[packageManager] = true
}
if (packageManager === 'ansible') {
await ensurePackageManager('pipx')
}
if (
packageManager === 'gem' ||
packageManager === 'go' ||
packageManager === 'npm' ||
packageManager === 'pipx' ||
packageManager === 'whalebrew'
) {
await ensurePackageManager('brew')
}
if (packageManager === 'appimage') {
const zap = which.sync('zap', { nothrow: true })
if (!zap) {
log('info', 'Zap Installation', 'Installing Zap to handle AppImage installation')
await ensurePackage('curl')
await $`sudo curl -sSL --output /usr/local/bin/zap https://github.com/srevinsaju/zap/releases/download/continuous/zap-amd64`
await $`sudo chmod +x /usr/local/bin/zap`
}
} else if (packageManager === 'ansible') {
try {
await $`test -f "$HOME/.cache/megabyte-labs/ansible-installed"`
const ansible = which.sync('ansible', { nothrow: true })
if (ansible) {
log('info', logStage, `\`ansible\` and its supporting packages appear to be installed`)
} else {
await ensurePackageManagerAnsible()
}
} catch (e) {
await ensurePackageManagerAnsible()
}
} else if (packageManager === 'apk') {
await ensureInstalled('apk', false)
} else if (packageManager === 'apt') {
await ensureInstalled('apt', false)
} else if (packageManager === 'basher') {
await ensureInstalled(
'basher',
$`
# TODO
echo "Bash script that installs basher here"
`
)
} else if (packageManager === 'binary') {
await ensurePackage('curl')
} else if (packageManager === 'bpkg') {
await ensureInstalled(
'bpkg',
$`
# TODO
echo "Bash script that installs bpkg here"
`
)
} else if (packageManager === 'brew' || packageManager === 'cask') {
const brew = which.sync('brew', { nothrow: true })
if (!brew) {
await ensureInstalled(
'brew',
$`
if command -v sudo > /dev/null && sudo -n true; then
echo | bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
else
log('info', logStage, 'Homebrew is not installed. Password may be required.')
bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || BREW_EXIT_CODE="$?"
if [ -n "$BREW_EXIT_CODE" ]; then
if command -v brew > /dev/null; then
log('warn', logStage, 'Homebrew was installed but part of the installation failed. Attempting to fix..')
BREW_DIRS="share/man share/doc share/zsh/site-functions etc/bash_completion.d"
for BREW_DIR in $BREW_DIRS; do
if [ -d "$(brew --prefix)/$BREW_DIR" ]; then
sudo chown -R "$(whoami)" "$(brew --prefix)/$BREW_DIR"
fi
done
brew update --force --quiet
fi
fi
fi
`
)
}
} else if (packageManager === 'cargo') {
const cargo = which.sync('cargo', { nothrow: true })
if (!cargo) {
if (osType === 'darwin') {
await $`brew install rustup`
await $`rustup-init`
} else if (osType === 'windows') {
} else {
await ensurePackage('cargo')
}
}
} else if (packageManager === 'choco') {
await ensureInstalled(
'choco',
$`
powershell "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))"
`
)
} else if (packageManager === 'crew') {
await ensureInstalled(
'crew',
$`
# TODO Bash script that installs crew here
# Source: https://github.com/chromebrew/chromebrew
curl -Ls git.io/vddgY | bash
`
)
} else if (packageManager === 'dnf') {
const dnf = which.sync('dnf', { nothrow: true })
const yum = which.sync('yum', { nothrow: true })
if (dnf) {
log('info', logStage, `\`dnf\` is available`)
} else if (yum) {
log('info', logStage, `\`yum\` is available`)
} else {
log('error', logStage, `Both \`dnf\` and \`yum\` are not available`)
}
} else if (packageManager === 'flatpak') {
const flatpak = which.sync('flatpak', { nothrow: true })
if (flatpak) {
log('info', logStage, `\`flatpak\` is available`)
} else {
const apk = which.sync('apk', { nothrow: true })
const apt = which.sync('apt-get', { nothrow: true })
const dnf = which.sync('dnf', { nothrow: true })
const yum = which.sync('yum', { nothrow: true })
const pacman = which.sync('pacman', { nothrow: true })
const zypper = which.sync('zypper', { nothrow: true })
if (apk) {
runCommand('Installing flatpak via apk', 'sudo apk add flatpak')
} else if (apt) {
runCommand('Installing flatpak via apt-get', 'sudo apt-get install -y flatpak')
if (fileExists('/usr/bin/gnome-shell')) {
runCommand('Installing gnome-software-plugin-flatpak via apt-get', 'sudo apt-get install -y gnome-software-plugin-flatpak')
}
if (fileExists('/usr/bin/plasmashell')) {
runCommand('Installing plasmashell via apt-get', 'sudo apt-get install -y plasmashell')
}
} else if (dnf) {
await $`sudo dnf install -y flatpak`
} else if (yum) {
await $`sudo yum install -y flatpak`
} else if (pacman) {
await $`sudo pacman -Sy flatpak`
} else if (zypper) {
await $`sudo zypper install -y flatpak`
}
const flatpakPost = which.sync('flatpak', { nothrow: true })
if (flatpakPost) {
await $`sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo`
} else {
log('error', logStage, `\`flatpak\` failed to install!`)
}
log('info', logStage, `\`flatpak\` was installed. It may require a reboot to function correctly.`)
}
} else if (packageManager === 'gem') {
const gem = which.sync('gem', { nothrow: true })
if (!gem) {
await ensureInstalled('gem', $`brew install ruby`)
}
} else if (packageManager === 'go') {
const go = which.sync('go', { nothrow: true })
if (!go) {
await ensureInstalled('go', $`brew install go`)
}
} else if (packageManager === 'nix') {
await ensureInstalled(
'nix',
$`
if [ -d /Applications ] && [ -d /Library ]; then
sh <(curl -L https://nixos.org/nix/install)
else
sh <(curl -L https://nixos.org/nix/install) --daemon
fi
`
)
} else if (packageManager === 'npm') {
const npm = which.sync('npm', { nothrow: true })
const node = which.sync('node', { nothrow: true })
const volta = which.sync('volta', { nothrow: true })
if (npm && node && volta) {
log('info', logStage, `\`npm\`, \`node\`, and \`volta\` are available`)
} else {
if (!volta) {
await $`brew install volta`
}
await $`
if [ -z "$VOLTA_HOME" ]; then
volta setup
fi
export PATH="$VOLTA_HOME/bin:$PATH"
volta install node
`
}
log('info', logStage, 'Ensuring Volt has Node.js runtime available')
await $`if ! volta list 2>1 | grep 'runtime node' > /dev/null; then volta install node; fi`
} else if (packageManager === 'pacman') {
await ensureInstalled('pacman', false)
} else if (packageManager === 'pipx') {
const pipx = which.sync('pipx', { nothrow: true })
if (!pipx) {
await ensureInstalled('pipx', $`brew install pipx`)
await $`pipx ensurepath`
}
} else if (packageManager === 'pkg') {
await ensureInstalled('pkg', false)
} else if (packageManager === 'port') {
const port = which.sync('port', { nothrow: true })
if (!port) {
log('info', logStage, `Installing ${packageManager}`)
await ensureInstalled(
'port',
$`
sudo mkdir -p /opt/mports
cd /opt/mports
sudo rm -rf macports-base
sudo git clone https://github.com/macports/macports-base.git
cd macports-base
sudo git checkout v2.8.0
sudo bash --noprofile --norc -c './configure --enable-readline && make && make install && make distclean'
sudo port selfupdate
`
)
log('info', logStage, `${packageManager} is now installed`)
} else {
log('info', logStage, `\`port\` is available`)
}
} else if (packageManager === 'scoop') {
await ensureInstalled(
'scoop',
$`
powershell 'Set-ExecutionPolicy RemoteSigned -Scope CurrentUser'
powershell 'irm get.scoop.sh | iex
`
)
} else if (packageManager === 'snap') {
const apk = which.sync('apk', { nothrow: true })
const apt = which.sync('apt-get', { nothrow: true })
const dnf = which.sync('dnf', { nothrow: true })
const yum = which.sync('yum', { nothrow: true })
const pacman = which.sync('pacman', { nothrow: true })
const zypper = which.sync('zypper', { nothrow: true })
if (apt) {
if (fileExists('/etc/apt/preferences.d/nosnap.pref')) {
$`sudo mv /etc/apt/preferences.d/nosnap.pref /etc/apt/nosnap.pref.bak`
}
runCommand('Ensuring snapd is installed', `sudo apt-get install -y snapd`)
// TODO Following may be required on Kali -> https://snapcraft.io/docs/installing-snap-on-kali
// systemctl enable --now snapd apparmor
} else if (dnf) {
runCommand('Ensuring snapd is installed', `sudo dnf install -y snapd`)
if (!fileExists('/snap')) {
await $`sudo ln -s /var/lib/snapd/snap /snap`
}
} else if (yum) {
runCommand('Ensuring snapd is installed', 'sudo yum install -y snapd')
await $`sudo systemctl enable --now snapd.socket`
if (!fileExists('/snap')) {
$`sudo ln -s /var/lib/snapd/snap /snap`
}
} else if (pacman) {
$`if [ -f /etc/arch-release ]; then sudo git clone https://aur.archlinux.org/snapd.git /usr/local/src/snapd && cd /usr/local/src/snapd && sudo makepkg -si; else sudo pacman -S snapd && sudo systemctl enable --now snapd.socket && if [ ! -d /snap ]; then sudo ln -s /var/lib/snapd/snap /snap; fi; fi`
} else if (zypper) {
// TODO See https://snapcraft.io/docs/installing-snap-on-opensuse
await $`
echo "TODO - Bash script that installs snap w/ zypper"
`
}
const snap = which.sync('snap', { nothrow: true })
if (snap) {
runCommand('Ensuring snap core is installed', `sudo snap install core`)
} else {
log('warn', logStage, 'Snap installation sequence completed but the snap bin is still not available')
}
} else if (packageManager === 'script') {
} else if (packageManager === 'whalebrew') {
await ensureInstalled('whalebrew', $`brew install whalebrew`)
} else if (packageManager === 'winget') {
await ensureInstalled(
'winget',
$`
echo "TODO - Script that installs winget here"
`
)
} else if (packageManager === 'yay') {
const yay = which.sync('yay', { nothrow: true })
await $`sudo pacman -S --needed base-devel git`
await $`
if [ -d /usr/local/src ]; then
git clone https://aur.archlinux.org/yay.git /usr/local/src/yay
cd /usr/local/src/yay
makepkg -si
fi
`
} else if (packageManager === 'zypper') {
await ensureInstalled('zypper', false)
}
}
// Installs a list of packages via the specified package manager
async function installPackageList(packageManager, packages) {
const logStage = 'Package Install'
try {
if (packageManager === 'appimage') {
for (let pkg of packages) {
try {
if (pkg.substring(0, 4) === 'http') {
log('info', 'AppImage Install', `Installing ${pkg} from its URL`)
runCommand('Installing ${pkg} via apk', `sudo zap install --from ${pkg}`)
} else if ((pkg.match(/\\/g) || []).length === 1) {
log('info', 'AppImage Install', `Installing ${pkg} from a GitHub Release`)
runCommand('Installing ${pkg} via apk', `sudo zap install --github --from ${pkg}`)
} else {
log('info', 'AppImage Install', `Installing ${pkg} using the AppImage Catalog`)
runCommand('Installing ${pkg} via apk', `sudo zap install ${pkg}`)
}
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error using Zap to install ${pkg}`)
console.error(e)
}
}
} else if (packageManager === 'ansible') {
for (let pkg of packages) {
try {
const unbuffer = which.sync('unbuffer', { nothrow: true })
let unbufferPrefix = ''
if (unbuffer) {
unbufferPrefix = 'unbuffer'
}
const verboseMode = process.env.DEBUG_MODE === 'on' ? 'vv' : ''
await $`ansible 127.0.0.1 -v${verboseMode} -e '{ ansible_connection: "local", ansible_become_user: "root", ansible_user: "${process.env.USER}", install_homebrew: False }' -m include_role -a name=${pkg}`
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with Ansible`)
}
}
} else if (packageManager === 'apk') {
for (let pkg of packages) {
try {
runCommand('Installing ${pkg} via apk', `sudo apk add ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with apk`)
console.log(e)
}
}
} else if (packageManager === 'apt') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo apt-get -o DPkg::Options::=--force-confdef install -y ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with apt-get`)
console.error(e)
}
}
} else if (packageManager === 'basher') {
for (let pkg of packages) {
try {
await $`basher install ${pkg}`
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with basher`)
console.error(e)
}
}
} else if (packageManager === 'binary') {
for (let pkg of packages) {
try {
await $`TMP="$(mktemp)" && curl -sSL ${pkg} > "$TMP" && sudo mv "$TMP" /usr/local/src/${binName} && chmod +x /usr/local/src/${binName}`
} catch (e) {
log('error', 'Install Failure', `There was an error installing the binary release for ${pkg}`)
console.error(e)
}
}
} else if (packageManager === 'brew') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `brew install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with brew`)
console.error(e)
}
}
} else if (packageManager === 'cask') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `brew install --cask ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with Homebrew Cask`)
console.error(e)
}
}
} else if (packageManager === 'cargo') {
for (const pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `cargo install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with Cargo`)
console.error(e)
}
}
} else if (packageManager === 'choco') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `choco install -y ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with Chocolatey`)
console.error(e)
}
}
} else if (packageManager === 'crew') {
} else if (packageManager === 'dnf') {
const dnf = which.sync('dnf', { nothrow: true })
const yum = which.sync('yum', { nothrow: true })
for (let pkg of packages) {
if (dnf) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo dnf install -y ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with dnf`)
console.error(e)
}
} else if (yum) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo yum install -y ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with yum`)
console.error(e)
}
}
}
} else if (packageManager === 'flatpak') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo flatpak install -y flathub ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with flatpak`)
console.error(e)
}
}
} else if (packageManager === 'gem') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `gem install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
await $`gem install ${pkg}`
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with gem`)
console.error(e)
}
}
} else if (packageManager === 'go') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `go install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with go`)
console.error(e)
}
}
} else if (packageManager === 'nix') {
} else if (packageManager === 'npm') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `volta install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with volta`)
console.error(e)
}
}
} else if (packageManager === 'pacman') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo pacman -Sy --noconfirm --needed ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with pacman`)
console.error(e)
}
}
} else if (packageManager === 'pipx') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `pipx install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with pipx`)
console.error(e)
}
}
} else if (packageManager === 'pkg') {
} else if (packageManager === 'port') {
const port = which.sync('port', { nothrow: true })
if (port) {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo port install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with port`)
console.error(e)
}
}
} else {
log(
'error',
'Port Not Installed',
`Unable to install with port because it is not installed. Skipping installation of ${packages}`
)
}
} else if (packageManager === 'scoop') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `scoop install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with scoop`)
console.error(e)
}
}
} else if (packageManager === 'snap') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo snap install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with snap`)
console.error(e)
}
}
} else if (packageManager === 'script') {
for (let pkg of packages) {
try {
await $`${pkg}`
} catch (e) {
log('error', 'Install Failure', `There was an error running the script installation method for ${pkg}`)
console.error(e)
}
}
} else if (packageManager === 'snap-classic') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo snap install --classic ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with snap in classic mode`)
console.error(e)
}
}
} else if (packageManager === 'whalebrew') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `whalebrew install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with whalebrew`)
console.error(e)
}
}
} else if (packageManager === 'winget') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `winget install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with winget`)
console.error(e)
}
}
} else if (packageManager === 'yay') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `yay -Sy --noconfirm --needed ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with yay`)
console.error(e)
}
}
} else if (packageManager === 'zypper') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `sudo zypper install -y ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install Failure', `There was an error installing ${pkg} with zypper`)
console.error(e)
}
}
}
} catch (e) {
log('error', logStage, `Possibly encountered an error while installing via \`${packageManager}\``)
log('info', logStage, `Proceeding with the installation..`)
}
}
async function updateService(service) {
const logStage = 'Service Service'
if (osType === 'linux') {
const systemctl = which.sync('systemctl', { nothrow: true })
if (systemctl) {
try {
log('info', logStage, `Starting / enabling the ${service} service`)
await $`sudo systemctl enable --now ${service}`
} catch (e) {
log('error', logStage, `There was an error starting / enabling the ${service} service`)
console.error(e)
}
} else {
log(
'warn',
logStage,
`The systemctl command is not available so applications with services cannot be started / enabled`
)
}
} else if (osType === 'darwin') {
const brew = which.sync('brew', { nothrow: true })
if (brew) {
try {
log('info', logStage, `Starting / enabling the ${service} service with Homebrew`)
await $`brew services start ${service}`
} catch (e) {
log('error', logStage, `There was an error starting / enabling the ${service} Homebrew service`)
console.error(e)
}
} else {
log('warn', logStage, `Homebrew is not available - skipping service start command`)
}
}
}
/**
* Filter that resolves when all asynchronous filter actions are done
*/
const asyncFilter = async (arr, predicate) => {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_v, index) => results[index]);
}
async function pruneInstallOrders(installOrders) {
const newOrders = Object.assign({}, installOrders)
log('info', 'Filter', 'Removing packages from installOrders that are already installed')
for (const pkgManager in installOrders) {
log('info', 'Filter', `Filtering the ${pkgManager} installOrders`)
console.log(newOrders[pkgManager])
if (pkgManager === 'apt') {
newOrders[pkgManager] = await asyncFilter(newOrders[pkgManager], async (pkg) => {
try {
await runSilentCommand(`dpkg -l ${pkg} | grep -E '^ii' > /dev/null`)
return false
} catch (e) {
return true
}
})
} else if (pkgManager === 'brew') {
let newVal = newOrders[pkgManager]
for (const pkg of newOrders[pkgManager]) {
try {
runCommand(`Ensuring Homebrew package is not already installed - ${pkg}`, `brew list ${pkg}`)
newVal = newVal.filter(x => x === pkg)
} catch (e) {
// Do nothing
}
}
newOrders[pkgManager] = newVal
} else if (pkgManager === 'dnf') {
const dnf = which.sync('dnf', { nothrow: true })
newOrders[pkgManager] = await asyncFilter(newOrders[pkgManager], async (pkg) => {
try {
if (dnf) {
await runSilentCommand(`rpm -qa | grep ${pkg} > /dev/null`)
} else {
await runSilentCommand(`rpm -qa | grep ${pkg} > /dev/null`)
}
return false
} catch (e) {
return true
}
})
} else if (pkgManager === 'flatpak') {
const flatpakInstallation = await $`flatpak --installations`
newOrders[pkgManager] = await asyncFilter(newOrders[pkgManager], async (pkg) => {
try {
await runSilentCommand(`test -d ${flatpakInstallation}/app/${pkg}`)
return false
} catch (e) {
return true
}
})
newOrders[pkgManager] = await asyncFilter(newOrders[pkgManager], async (pkg) => {
try {
await runSilentCommand(`flatpak info ${pkg} > /dev/null`)
return false
} catch (e) {
return true
}
})
} else if (pkgManager === 'pacman') {
newOrders[pkgManager] = await asyncFilter(newOrders[pkgManager], async (pkg) => {
try {
await runSilentCommand(`pacman -Qs ${pkg} > /dev/null`)
return false
} catch (e) {
return true
}
})
} else if (pkgManager === 'snap' || pkgManager === 'snap-classic') {
newOrders[pkgManager] = await asyncFilter(newOrders[pkgManager], async (pkg) => {
try {
await runSilentCommand(`snap list ${pkg} | grep ${pkg} > /dev/null`)
return false
} catch (e) {
return true
}
})
}
log('info', 'Filter', `Finished filtering ${pkgManager}`)
console.log(newOrders[pkgManager])
}
return newOrders
}
async function installPlugins(pluginData) {
for (const plugin of pluginData.plugin) {
try {
const pluginCmd = plugin.cmd.replace(/{PLUGIN}/g, plugin)
runCommand(`Installing ${pluginData.package} plugin - ${plugin}`, `{pluginCmd}`)
log('success', 'Plugin', `Successfully installed ${pluginData.package} plugin - ${plugin}`)
} catch (e) {
log('error', 'Plugin', `Failed to install ${pluginData.package} plugin - ${plugin}`)
console.error(e)
}
}
}
// main process
async function installSoftware(pkgsToInstall) {
osType = await OSType()
osID = osType
if (osType === 'linux') {
osID = await releaseID()
}
log('info', 'Catalog Download', `Fetching the latest version of the installation map`)
installData = await downloadInstallData()
log('info', 'Filter', `Calculating the install orders`)
await generateInstallOrders(pkgsToInstall ? pkgsToInstall : process.argv.slice(3))
log(
'info',
'Pre-Reqs',
`Ensuring any package managers that will be used are installed / configured`
)
const packageManagers = Object.keys(installOrders)
for (const packageManager of packageManagers) {
await ensurePackageManager(packageManager)
}
try {
for (const key in installOrders) {
installOrders[key] = [...new Set(installOrders[key])]
}
log('info', 'Install', `The install orders were generated:`)
} catch (e) {
log('error', 'Filter', `There was an error reducing the duplicates in the install orders`)
console.error(e)
}
installOrders = await pruneInstallOrders(installOrders)
console.log(installOrders)
log('info', 'Pre-Reqs', `Running package manager pre-installation steps`)
for (const packageManager of packageManagers) {
await beforeInstall(packageManager)
}
log('info', 'Pre-Install', `Running package-specific pre-installation steps`)
for (const script of installOrdersPre) {
await $`${script}`
}
log('info', 'Install', `Installing the packages`)
for (const packageManager of packageManagers) {
const asyncOrders = []
asyncOrders.push(installPackageList(packageManager, installOrders[packageManager]))
await Promise.all(asyncOrders)
}
log('info', 'Post-Install', `Running package-specific post-installation steps`)
for (const service of installOrdersService) {
await updateService(service)
}
log('info', 'Plugin', 'Installing package-specific plugins')
for (const plugin of installOrdersPlugins) {
await installPlugins(plugin)
}
for (const script of installOrdersPost) {
await $`${script}`
}
log('info', 'Post-Install', `Running package manager post-installation steps`)
for (const packageManager of packageManagers) {
await afterInstall(packageManager)
}
log('success', 'Complete', `Done!`)
}
// Start the main process
await installSoftware(false)