install.fairie/home/dot_local/bin/executable_install-program-edit
2024-05-13 06:40:05 +00:00

1882 lines
77 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
let fittedLabel = label
while(fittedLabel.length < 14) {
fittedLabel = fittedLabel.length % 2 === 1 ? fittedLabel + ' ' : ' ' + fittedLabel
}
if (type === 'info') {
icon = `${chalk.cyanBright(figures.pointer)} ${chalk.bold.white.bgCyan(' ' + fittedLabel + ' ')}`
message = wrapMessage(msg)
} else if (type === 'star') {
icon = `${chalk.yellow(figures.star)} ${chalk.bold.black.bgYellow(' ' + fittedLabel + ' ')}`
message = wrapMessage(msg)
} else if (type === 'success') {
icon = `${chalk.greenBright(figures.play)} ${chalk.bold.white.bgGreenBright(' ' + fittedLabel + ' ')}`
message = wrapMessage(msg)
} else if (type === 'warn') {
icon = `${chalk.yellowBright(figures.lozenge)} ${chalk.bold.black.bgYellowBright(' WARNING ')}`
message = chalk.yellowBright(wrapMessage(msg))
} else if (type === 'error') {
icon = `${chalk.redBright(figures.cross)} ${chalk.black.bold.bgRedBright(' ERROR ')}`
message = chalk.redBright(wrapMessage(msg))
}
const now = new Date()
const outputMessage = `${icon} ${message} (${now})`
console.log(outputMessage)
}
function locations(substring,string){
var a=[],i=-1;
while((i=string.indexOf(substring,i+1)) >= 0) a.push(i);
return a;
}
function wrapMessage(msg) {
const indexes = locations('`', msg)
if (indexes.length > 3) {
return msg.substring(0, indexes[0]) + chalk.bold.black.bgGray(' ' + msg.substring(indexes[0] + 1, indexes[1] + 1 - indexes[0]) + ' ') + msg.substring(indexes[1] + 1 - indexes[0]) + ' '
} else {
return msg
}
}
function runCommand(spinnerTitle, command) {
// execSync(command.includes('sudo') ? `sudo "$(which gum)" spin --spinner dot --title "${spinnerTitle}" -- ${command}` : `gum spin --spinner dot --title "${spinnerTitle}" -- ${command}`, {
log('info', 'CMD', spinnerTitle)
console.log(command)
execSync(command, {
stdio: 'inherit',
shell: true,
// Timeout of 140m
timeout: 1000 * 60 * 140
})
}
async function runSilentCommand(command) {
execSync(`${command}`, { stdio: 'inherit', shell: true })
}
function fileExists(pathToFile) {
return fs.existsSync(pathToFile)
}
function dirExists(pathToDir) {
return fs.existsSync(pathToDir)
}
let installData
let installOrders = {}
let installMeta = {}
let binLinkRan = false
let chezmoiData = []
let installOrdersPre = []
let installOrdersPost = []
let installOrdersService = []
let installOrdersGroups = []
let installOrdersPorts = []
let installOrdersPlugins = []
let installOrdersBinLink = []
let brewUpdated, osType, osID, snapRefreshed
// Register the OS platform type
const osPlatformData = os.platform()
const osPlatform = osPlatformData === 'win32' ? 'windows' : osPlatformData
// Download the installation map
async function downloadInstallData() {
const response = await fetch('https://github.com/megabyte-labs/install.doctor/raw/master/software.yml')
if (response.ok) {
log('info', 'Catalog Download', `Received ok response from download`)
const text = await response.text()
log('info', 'Catalog Download', `Parsing software.yml`)
return YAML.parse(text, { maxAliasCount: -1 })
} 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()
log('info', 'Catalog Download', `Parsing local software.yml file`)
return YAML.parse(text, { maxAliasCount: -1 })
}
}
// Download the installation map
async function getChezmoiData() {
const text = fs.readFileSync(process.env.HOME + '/.local/share/chezmoi/home/.chezmoidata.yaml').toString()
return YAML.parse(text, { maxAliasCount: -1 })
}
// 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('Preference order:', 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`)
process.env.DEBUG === 'true' && console.log('softwarePackages:', softwarePackages)
console.log('pkg:', pkg)
continue
}
let comparesRemaining = preferenceOrder.length
for (let preference of preferenceOrder) {
comparesRemaining--
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 runSilentCommand(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
}
process.env.DEBUG === 'true' && log('info', 'Filter', `${pkg} is being skipped because of the _when:${pref} condition`)
processPluginOrders(pkg)
continue pkgFor
}
} else if (scopedPkgManager) {
try {
await runSilentCommand(scopedPkgManager)
} catch (e) {
const pref = preference
process.env.DEBUG === 'true' && log('info', 'Filter', `${pkg} is being skipped because of the _when:${pref} condition`)
processPluginOrders(pkg)
continue pkgFor
}
} else if (scopedSystem) {
try {
await runSilentCommand(scopedSystem)
} catch (e) {
let pref
if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID]) {
pref = osID
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType]) {
pref = osType
}
process.env.DEBUG === 'true' && log('info', 'Filter', `${pkg} is being skipped because of the _when:${pref} condition`)
processPluginOrders(pkg)
continue pkgFor
}
} else if (normalCheck) {
try {
await runSilentCommand(normalCheck)
} catch (e) {
process.env.DEBUG === 'true' && log('info', 'Filter', `${pkg} is being skipped because of the _when condition`)
processPluginOrders(pkg)
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
}
process.env.DEBUG === 'true' && log('info', 'Filter', `${bin} already in PATH (via _bin:${pref})`)
processPluginOrders(pkg)
continue pkgFor
} else {
if (preference === 'cask' || preference === 'flatpak') {
installOrdersBinLink.push({ package: packageKey, bin: doubleScoped, preference })
}
}
} 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
process.env.DEBUG === 'true' && log('info', 'Filter', `${bin} already in PATH (via _bin:${pref})`)
processPluginOrders(pkg)
continue pkgFor
} else {
if (preference === 'cask' || preference === 'flatpak') {
installOrdersBinLink.push({ package: packageKey, bin: scopedPkgManager, preference })
}
}
} 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
}
process.env.DEBUG === 'true' && log('info', 'Filter', `${bin} already in PATH (via _bin:${pref})`)
processPluginOrders(pkg)
continue pkgFor
} else {
if (preference === 'cask' || preference === 'flatpak') {
installOrdersBinLink.push({ package: packageKey, bin: scopedSystem, preference })
}
}
} 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) {
process.env.DEBUG === 'true' && log('info', 'Filter', `${bin} already in PATH (via _bin)`)
processPluginOrders(pkg)
continue pkgFor
} else {
if (preference === 'cask' || preference === 'flatpak') {
installOrdersBinLink.push({ package: packageKey, bin: normalCheck, preference })
}
}
}
// Handle the _app definition
const appName = softwarePackages[packageKey]['_app']
if (appName) {
if(fileExists(`/Applications/${appName}`) || fileExists(`${process.env.HOME}/Applications/${appName}`)) {
processPluginOrders(pkg)
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
}
} else {
if (!comparesRemaining) {
log('info', 'No Match', `There was no match found for ${pkg} - it may be an OS-specific package`)
}
}
}
}
if (generateInstallOrderCount === 1) {
return installOrders
} else {
generateInstallOrderCount--
}
}
function processPluginOrders(pkg) {
const pluginMap = chezmoiData && chezmoiData.softwarePlugins && chezmoiData.softwarePlugins[pkg]
if (pluginMap) {
if (pluginMap.cmd && pluginMap.plugins) {
installOrdersPlugins.push({ package: pkg, cmd: pluginMap.cmd, plugins: pluginMap.plugins })
}
}
}
// 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 = installOrdersPre.concat(typeof preHook === 'string' ? [preHook] : preHook)
}
const postHook = getHook(packages, 'post', scopedPreference, preference)
if (postHook) {
installOrdersPost = installOrdersPost.concat(typeof postHook === 'string' ? [postHook] : postHook)
}
const serviceHook = getHook(packages, 'service', scopedPreference, preference)
const serviceEnabledHook = getHook(packages, 'serviceEnabled', scopedPreference)
if (serviceHook && serviceEnabledHook) {
installOrdersService = installOrdersService.concat(typeof serviceHook === 'string' ? [serviceHook] : serviceHook)
}
const groupsHook = getHook(packages, 'groups', scopedPreference, preference)
if (groupsHook) {
installOrdersGroups = installOrdersGroups.concat(typeof groupsHook === 'string' ? [groupsHook] : groupsHook)
}
const portsHook = getHook(packages, 'ports', scopedPreference, preference)
if (portsHook) {
installOrdersPorts = installOrdersPorts.concat(typeof portsHook === 'string' ? [{
packageKey,
ports: portsHook
}] : {
packageKey,
ports: portsHook})
}
processPluginOrders(pkg)
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 (typeof newPackages === 'string') {
installMeta[newPackages] = {
preference,
packages,
scopedPreference,
pkg,
packageKey,
softwarePackages
}
} else {
for (const dataKey in newPackages) {
installMeta[newPackages] = {
preference,
packages,
scopedPreference,
pkg,
packageKey,
softwarePackages
}
}
}
if (preference === 'snap' && softwarePackages[pkg]['_snapClassic'] === true) {
if (!installOrders['snap-classic']) {
installOrders['snap-classic'] = []
}
installOrders['snap-classic'] = installOrders['snap-classic'].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 dirExists('/etc/ubuntu-advantage') ? 'ubuntu' : 'apt'
} else if (dnf || yum) {
return 'dnf'
} else if (pacman) {
return 'pacman'
} else if (zypper) {
return 'zypper'
} else if (freebsdPkg && freebsdVersion) {
return 'freebsd'
} else {
return dirExists('/Applications') && dirExists('/Library') ? 'darwin' : '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() {
return dirExists('/Applications') && dirExists('/Library') ? 'darwin' : (fileExists('/etc/os-release') ? 'linux' : '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') {
} 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') {
log('info', logStage, `Ensuring Homebrew cleanup is run`)
await $`brew cleanup`
} 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 --quiet ${dep}`
} else if (osType === 'windows') {
if (updateDone['choco'] !== true) {
// await beforeInstall('choco')
}
await `choco install -y ${dep}`
}
}
}
*/
// Pre-install hook
const updateDone = {}
/*
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`
if (osType === 'darwin') {
await $`pipx inject ansible PyObjC PyObjC-core`
}
await $`pipx inject ansible 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)"
sudo chmod -R g-w "$(brew --prefix)/share"
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="$?"
sudo chmod -R g-w "$(brew --prefix)/share"
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') {
const rustup = which.sync('rustup-init', { nothrow: true })
if (!rustup) {
await $`brew install --quiet rustup`
}
await $`rustup-init -y`
} 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`
}
log('info', logStage, `\`flatpak\` was installed. It may require a reboot to function correctly.`)
}
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!`)
}
} else if (packageManager === 'gem') {
const gem = which.sync('gem', { nothrow: true })
if (!gem) {
await ensureInstalled('gem', $`brew install --quiet ruby`)
}
} else if (packageManager === 'go') {
const go = which.sync('go', { nothrow: true })
if (!go) {
await ensureInstalled('go', $`brew install --quiet 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 --quiet volta`
}
await $`
export VOLTA_HOME="\${XDG_DATA_HOME:-$HOME/.local/share}/volta"
export PATH="$VOLTA_HOME/bin:$PATH"
volta setup
volta install node
`
}
log('info', logStage, 'Ensuring Volt has Node.js runtime available')
await $`export VOLTA_HOME="\${XDG_DATA_HOME:-$HOME/.local/share}/volta" && export PATH="$VOLTA_HOME/bin:$PATH" && 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 --quiet 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
runCommand('Enabling snapd service', `sudo systemctl enable snapd`)
runCommand('Starting snapd service', `sudo systemctl start snapd`)
} 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`
}
runCommand('Enabling snapd service', `sudo systemctl enable snapd`)
runCommand('Starting snapd service', `sudo systemctl start snapd`)
} 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('Check info for core snap package', `sudo snap info core`)
// Required: https://snapcraft.io/docs/troubleshooting#heading--early
runCommand('Ensuring snap is seeded', `sudo snap wait system seed.loaded`)
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 --quiet 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 zap', `zap install --select-first -q --from ${pkg}`)
} else if ((pkg.match(/\//g) || []).length === 1) {
log('info', 'AppImage Install', `Installing ${pkg} from a GitHub Release`)
runCommand('Installing ${pkg} via zap', `zap install --select-first -q --github --from ${pkg}`)
} else {
log('info', 'AppImage Install', `Installing ${pkg} using the AppImage Catalog`)
runCommand('Installing ${pkg} via zap', `zap install --select-first -q ${pkg}`)
}
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install', `There was an error using Zap to install ${pkg}`)
console.error(e)
}
}
log('warn', 'Install', 'Zap installs might fail - this is expected. Waiting on fixes to Zap upstream project')
} 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 === 'true' ? 'vv' : ''
if (osPlatform === 'darwin' || osPlatform === 'linux' || osPlatform === 'windows') {
const capitalOsPlatform = osPlatform.charAt(0).toUpperCase() + osPlatform.slice(1)
await $`ANSIBLE_CONFIG=${process.env.HOME}/.local/share/ansible/ansible.cfg ansible 127.0.0.1 -v${verboseMode} -e '{ ansible_connection: "local", ansible_become_user: "root", ansible_user: "${process.env.USER}", ansible_family: "${capitalOsPlatform}", install_homebrew: False }' -m include_role -a name=${pkg}`
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} else {
log('warn', 'Ansible', 'Unsupported platform - ' + osPlatform)
}
} catch (e) {
log('error', 'Install', `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', `There was an error installing ${pkg} with apk`)
console.error(e)
}
}
} else if (packageManager === 'apt') {
for (let pkg of packages) {
try {
if (pkg.startsWith('http') && pkg.endsWith('.deb')) {
runCommand(
`Downloading and installing ${pkg}`,
`TMP="$(mktemp)" && curl -sSL ${pkg} -o "$TMP" && sudo dpkg -i "$TMP"`
)
} else {
runCommand(
`Installing ${pkg} via ${packageManager}`,
`sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Options::=--force-confdef install -y ${pkg}`
)
}
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install', `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', `There was an error installing ${pkg} with basher`)
console.error(e)
}
}
} else if (packageManager === 'binary') {
for (let pkg of packages) {
try {
const bins = installData.softwarePackages.filter((x) => x.appimage === pkg)
if (bins && bins[0]) {
const binName = bins[0]['_bin']
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', `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 --quiet ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install', `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 --no-quarantine --quiet ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install', `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', `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', `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', `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', `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', `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}`)
} catch (e) {
log('error', 'Install', `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', `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', `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', `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('warn', 'Install', `There was an error installing ${pkg} with pipx, re-attempting using pip3`)
console.error(e)
try {
runCommand(`Installing ${pkg} via pip3`, `pip3 install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via pip3`)
} catch (e) {
log('error', 'Install', `There was an error installing ${pkg} with both pipx and pip3`)
console.error(e)
}
}
}
} else if (packageManager === 'pip') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `pip3 install ${pkg}`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('warn', 'Install', `There was an error installing ${pkg} with pip3`)
console.error(e)
}
}
} else if (packageManager === 'pkg-darwin') {
for (let pkg of packages) {
try {
runCommand(`Installing ${pkg} via ${packageManager}`, `TMP="$(mktemp)" && curl -sSL "${pkg}" > "$TMP" && sudo installer -pkg "$TMP" -target /`)
log('success', 'Install', `${pkg} successfully installed via ${packageManager}`)
} catch (e) {
log('error', 'Install', `There was an error installing ${pkg} with the system installer`)
console.error(e)
}
}
} 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', `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', `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', `There was an error installing ${pkg} with snap`)
console.error(e)
}
}
} else if (packageManager === 'script') {
for (let pkg of packages) {
try {
await $`bash -c ${pkg}`
} catch (e) {
log('error', 'Install', `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', `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', `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', `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', `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', `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 addUserGroup(group) {
const logStage = 'Users / Groups'
log('info', logStage, `Ensuring the ${group} group / user is added`)
runCommand(`Creating the ${group} user / group`, `sudo "${process.env.HOME}/.local/bin/add-usergroup" "${process.env.USER}" "${group}"`)
}
/**
* Adds the rules specified in the `_ports` key of each entry in the `software.yml` file.
*
* @param rule {packageKey: string, ports: any} Firewall rule in the form of 8888/tcp or 9999/udp. Can also be the XML file name stored in ~/.config/firewall/etc/firewalld/services.
*/
async function addFirewallRule(rule) {
try {
const logStage = 'Firewall'
const packageName = installData.softwarePackages[rule.packageKey] && installData.softwarePackages[rule.packageKey]._name
const packageDesc = installData.softwarePackages[rule.packageKey] && installData.softwarePackages[rule.packageKey]._desc
log('info', logStage, `Ensuring the ${rule.packageKey} rule is added since the _ports key is defined`)
if (osType === 'linux') {
const firewallCmd = which.sync('firewall-cmd', { nothrow: true })
// const ufw = which.sync('ufw', { nothrow: true })
if (firewallCmd) {
const serviceFile = `${process.env.HOME}/.config/firewall/etc/firewalld/services/${rule.packageKey}.xml`
if (fileExists(serviceFile)) {
log('info', logStage, `Service file found at ${serviceFile} - using it to apply firewall-cmd configuration`)
runCommand(`Copying over ${serviceFile} file to /etc/firewalld/services`, `sudo cp -f "${serviceFile}" "/etc/firewalld/services/${rule.packageKey}.xml"`)
runCommand(`Adding the ${rule.packageKey} firewall-cmd service`, `sudo firewall-cmd --add-service=${rule.packageKey} --permanent`)
} else {
if (typeof rule.ports === 'string') {
runCommand(`Adding the ${rule.packageKey} ${rule.ports} rule to the firewall configuration`, `sudo firewall-cmd --add-port=${rule.ports} --permanent`)
} else {
for (const port of rule.ports) {
if (typeof port === 'string') {
runCommand(`Adding the ${rule.packageKey} ${rule.ports} rule to the firewall configuration`, `sudo firewall-cmd --add-port=${rule.ports} --permanent`)
} else if (port.port && port.proto) {
runCommand(`Adding the ${rule.packageKey} ${port.port}/${port.proto} rule to the firewall configuration`, `sudo firewall-cmd --add-port=${port.port}/${port.proto} --permanent`)
} else {
log('error', logStage, `Unable to parse the firewall definition for ${rule.packageKey}`)
}
}
}
}
} else {
log('error', logStage, `The firewall-cmd executable is not present on the system so the firewall cannot be configured`)
}
} else if (osType === 'darwin') {
const socketFilterFw = '/usr/libexec/ApplicationFirewall/socketfilterfw'
const serviceFile = `${process.env.HOME}/.config/firewall/darwin/${rule.packageKey}.sh`
if (fileExists(serviceFile)) {
runCommand(`Executing the matching ${serviceFile} service file`, `sudo bash "${serviceFile}"`)
} else {
if (typeof rule.ports === 'string') {
log('error', logStage, `_ports rules that are equal to strings are not yet implemented on macOS (package: ${rule.packageKey})`)
} else {
for (const port of rule.ports) {
if (typeof port === 'string') {
log('error', logStage, `_ports rules that are equal to strings are not yet implemented on macOS (package: ${rule.packageKey})`)
} else if (port.port && port.proto) {
if (packageDesc) {
runCommand(`Adding new service with description populated for ${rule.packageKey}`, `${socketFilterFw} --add --service "${packageName ? packageName : rule.packageKey}" --setglobaldescription "${packageDesc} --getglobalstate"`)
} else {
runCommand(`Adding new service for ${rule.packageKey}`, `${socketFilterFw} --add --service "${packageName ? packageName : rule.packageKey}" --getglobalstate`)
}
runCommand(`Adding firewall rule for ${rule.packageKey}`, `${socketFilterFw} --add --service "${packageName ? packageName : rule.packageKey}" --port ${port.port} --protocol ${port.proto}`)
} else {
log('error', logStage, `Unable to parse the firewall definition for ${rule.packageKey}`)
}
}
}
}
} else if (osType === 'windows') {
log('warn', logStage, `Windows support not yet added`)
} else {
log('warn', logStage, `Unknown operating system type`)
}
} catch (e) {
console.log(e)
log('error', 'Bin', `Error configuring firewall settings for ${rule.packageKey}`)
}
}
async function updateService(service) {
const logStage = 'Service Service'
if (osType === 'linux') {
const systemctl = which.sync('systemctl', { nothrow: true })
const brew = which.sync('brew', { nothrow: true })
if (systemctl) {
try {
runCommand(`Starting / enabling ${service} with systemctl`, `sudo systemctl enable --now ${service}`)
log('success', logStage, `Started / enabled the ${service} service`)
} catch (e) {
log('info', logStage, `There was an error starting / enabling the ${service} service with systemd`)
try {
if (brew) {
if (typeof service === 'array') {
service.forEach(x => {
runCommand(`Starting / enabling object array ${x.name} with Homebrew`, `${x.sudo ? 'sudo brew' : 'brew'} services restart ${x.name}`)
log('success', logStage, `Started / enabled the ${x.name} service with Homebrew`)
})
} else if (typeof service === 'object') {
runCommand(`Starting / enabling object ${service.name} with Homebrew`, `${service.sudo ? 'sudo brew' : 'brew'} services restart ${service.name}`)
log('success', logStage, `Started / enabled the ${service.name} service with Homebrew`)
} else {
runCommand(`Starting / enabling ${service} with Homebrew`, `brew services restart ${service}`)
log('success', logStage, `Started / enabled the ${service} service with Homebrew`)
}
} else {
log('error', logStage, `Unable to start service with systemd and Homebrew is not available`)
}
} catch (err) {
log('error', logStage, `Unable to start service with both systemd and Homebrew`)
log('info', logStage, `systemd error`)
console.error(e)
log('info', logStage, `brew services error`)
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 {
if (typeof service === 'array') {
service.forEach(x => {
runCommand(`Starting / enabling object array ${x.name} with Homebrew`, `${x.sudo ? 'sudo brew' : 'brew'} services restart ${x.name}`)
log('success', logStage, `Started / enabled the ${x.name} service with Homebrew`)
})
} else if (typeof service === 'object') {
runCommand(`Starting / enabling object ${service.name} with Homebrew`, `${service.sudo ? 'sudo brew' : 'brew'} services restart ${service.name}`)
log('success', logStage, `Started / enabled the ${service.name} service with Homebrew`)
} else {
runCommand(`Starting / enabling ${service} with Homebrew`, `brew services restart ${service}`)
log('success', logStage, `Started / enabled the ${service} service with Homebrew`)
}
} 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('List to be filtered:', newOrders[pkgManager])
if (pkgManager === 'appimage') {
newOrders[pkgManager] = await asyncFilter(newOrders[pkgManager], async (pkg) => {
try {
await runSilentCommand(`zap list | grep "${pkg}" > /dev/null`)
return false
} catch (e) {
return true
}
})
} else 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]
const pkgTmp = '/tmp/brew-list-install-doctor'
runCommand(`Populating temporary file with list of Homebrew packages`, `brew list > ${pkgTmp}`)
for (const pkg of newOrders[pkgManager]) {
try {
await $`cat ${pkgTmp} | grep '^${pkg}$'`
log('info', 'Filter', 'Filtering list')
newVal = newVal.filter((x) => x !== pkg)
console.log('New list', newVal)
} 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`
const flatpakDir = flatpakInstallation.stdout.replace('\n', '')
newOrders[pkgManager] = await asyncFilter(newOrders[pkgManager], async (pkg) => {
try {
await runSilentCommand(`test -d ${flatpakDir}/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('Filtered list:', newOrders[pkgManager])
}
return newOrders
}
*/
async function installPlugins(pluginData) {
if (pluginData.cmd && pluginData.plugins && pluginData.plugins.length) {
const pluginWhen = pluginData.when
console.log('Plugin when condition:', pluginWhen)
try {
if (pluginWhen) {
runCommand(`Checking when condition for ${pluginData.package} plugin - ${plugin}`, pluginWhen)
}
for (const plugin of pluginData.plugins) {
try {
const pluginCmd = pluginData.cmd.replace(/{PLUGIN}/g, plugin)
try {
try {
runCommand(`Installing ${pluginData.package} plugin - ${plugin}`, pluginCmd)
log('success', 'Plugin', `Successfully installed ${pluginData.package} plugin - ${plugin}`)
} catch (e) {
log('info', 'Plugin', `Failed to install ${pluginData.package} plugin - ${plugin}`)
console.error(e)
}
} catch (e) {
log('info', 'Plugin', `Skipping ${pluginData.package} plugin installs due to failed when condition - ${pluginWhen}`)
break
}
} catch (e) {
log('error', 'Plugin', `Failed to install ${pluginData.package} plugin due to an unknown reason - ${plugin}`)
console.error(e)
}
}
} catch (e) {
log('info', 'Plugin', 'Failed to pass when condition')
}
}
if (pluginData.update) {
const pluginDataPackage = pluginData.package
if (pluginDataPackage) {
runCommand('Updating ' + pluginDataPackage + ' plugins', pluginData.update)
}
}
}
/*
async function linkBin(installOrdersBinLink) {
let flatpakInstallation, flatpakDir
const softwarePackages = installData.softwarePackages
const flatpak = which.sync('flatpak', { nothrow: true })
if (flatpak) {
flatpakInstallation = await $`flatpak --installations`
flatpakDir = flatpakInstallation.stdout.replace('\n', '')
}
for (const binLink of installOrdersBinLink) {
const pkg = softwarePackages[binLink.package][binLink.preference]
if (typeof pkg === 'string') {
if (binLink.bin !== false) {
if (!which.sync(binLink.bin, { nothrow: true })) {
if (binLink.preference === 'flatpak' && flatpak) {
try {
runCommand(
`Adding bin link for ${pkg} (${binLink.bin})`,
`bash -c 'test -d ${flatpakDir}/app/${pkg} && mkdir -p "${process.env.HOME}/.local/bin/flatpak" && echo "flatpak run ${pkg} \\\$*" > "${process.env.HOME}/.local/bin/flatpak/${binLink.bin}" && chmod +x "${process.env.HOME}/.local/bin/flatpak/${binLink.bin}"'`
)
log('success', 'Bin', `Linked ~/.local/bin/flatpak/${binLink.bin} to the ${pkg} Flatpak`)
} catch (e) {
log('warn', 'Bin', `Expected flatpak directory not available - ${flatpakDir}/app/${pkg}`)
}
} else if (softwarePackages[binLink.package]['_app']) {
try {
const appName = softwarePackages[binLink.package]['_app']
log('info', 'Bin', `Checking for existence of ${appName} application in /Applications and ~/Applications`)
if (fileExists(`/Applications/${appName}`)) {
runCommand(
`Adding shortcut bin link for ${binLink.package}`,
`bash -c 'mkdir -p "${process.env.HOME}/.local/bin/cask" && echo "open \"/Applications/${appName}\" \\\$*" > "${process.env.HOME}/.local/bin/cask/${binLink.bin}" && chmod +x "${process.env.HOME}/.local/bin/cask/${binLink.bin}"'`
)
} else if(fileExists(`${process.env.HOME}/Applications/${appName}`)) {
runCommand(
`Adding shortcut bin link for ${binLink.package}`,
`bash -c 'mkdir -p "${process.env.HOME}/.local/bin/cask" && echo "open \"${process.env.HOME}/Applications/${appName}\" \\\$*" > "${process.env.HOME}/.local/bin/cask/${binLink.bin}" && chmod +x "${process.env.HOME}/.local/bin/cask/${binLink.bin}"'`
)
} else {
log('warn', 'Bin', `Expected Homebrew cask directory not found - ${pkg}`)
}
} catch (e) {
console.log(e)
log('warn', 'Bin', `Error creating bin shortcut link for ${pkg}`)
}
}
} else {
log('info', 'Bin', `Link already exists for ${binLink.package}`)
}
} else {
log('info', 'Bin', `Skipping ${binLink.package} because the _bin is equal to false`)
}
} else {
log('info', 'Bin', `Skipping ${binLink.package} because there was more than one _bin value`)
}
}
}
*/
// 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()
chezmoiData = await getChezmoiData()
log('info', 'Filter', `Calculating the install orders`)
await generateInstallOrders(pkgsToInstall ? pkgsToInstall : process.argv.slice(3))
const packageManagers = Object.keys(installOrders)
packageManagers.length && log('info', 'Pre-Reqs', `Ensuring any package managers that will be used are installed / configured`)
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)
delete installOrders._deps
console.log('Install orders:', installOrders)
packageManagers.length && log('info', 'Pre-Reqs', `Running package manager pre-installation steps`)
for (const packageManager of packageManagers) {
// await beforeInstall(packageManager)
}
installOrdersPre.length && log('info', 'Pre-Install', `Running package-specific pre-installation steps`)
for (const script of installOrdersPre) {
const timeoutAvail = which.sync('timeout', { nothrow: true })
if (timeoutAvail) {
await $`${['timeout', '1000', 'bash', '-c', script]}`
} else {
await $`${['bash', '-c', script]}`
}
}
installOrdersGroups.length && log('info', 'Users / Groups', `Adding groups / users`)
for (const group of installOrdersGroups) {
await addUserGroup(group)
}
packageManagers.length && log('info', 'Install', `Installing the packages`)
for (const packageManager of packageManagers) {
const asyncOrders = []
//asyncOrders.push(installPackageList(packageManager, installOrders[packageManager]))
await Promise.all(asyncOrders)
}
installOrdersPorts.length && log('info', 'Firewall', 'Configuring firewall exceptions')
for (const firewallRule of installOrdersPorts) {
await addFirewallRule(firewallRule)
}
installOrdersService.length && log('info', 'Post-Install', `Running package-specific post-installation steps`)
for (const service of installOrdersService) {
await updateService(service)
}
if (!binLinkRan) {
binLinkRan = true
log('info', 'Bin', 'Linking bin aliases to their installed packages')
// await linkBin(installOrdersBinLink)
}
for (const script of installOrdersPost) {
try {
log('info', 'Post Hook', script)
const timeoutAvail = which.sync('timeout', { nothrow: true })
if (timeoutAvail) {
await $`${['timeout', '1000', 'bash', '-c', script]}`
} else {
await $`${['bash', '-c', script]}`
}
} catch(e) {
log('info', 'Post-Install Hook', 'Encountered error while running post-install hook')
}
}
installOrdersPlugins.length && log('info', 'Plugin', 'Installing package-specific plugins')
for (const plugin of installOrdersPlugins) {
await installPlugins(plugin)
}
packageManagers.length && 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)