1433 lines
54 KiB
Text
1433 lines
54 KiB
Text
#!/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)
|
||
}
|
||
|
||
let installData
|
||
let installOrders = {}
|
||
const installOrdersPre = []
|
||
const installOrdersPost = []
|
||
const installOrdersSystemd = []
|
||
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', 'Skipping Package', `${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', 'Skipping Package', `${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', 'Skipping Package', `${pkg} is being skipped because of the _when:${pref} condition`)
|
||
continue pkgFor
|
||
}
|
||
} else if (normalCheck) {
|
||
try {
|
||
await $(normalCheck)
|
||
} catch (e) {
|
||
log('info', 'Skipping Package', `${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', 'Skipping Package', `${pkg} is being skipped because of the _bin:${pref} condition`)
|
||
log('info', 'Skipping Package', `${bin} is already in the PATH`)
|
||
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', 'Skipping Package', `${pkg} is being skipped because of the _bin:${pref} condition`)
|
||
log('info', 'Skipping Package', `${bin} is already in the PATH`)
|
||
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', 'Skipping Package', `${pkg} is being skipped because of the _bin:${pref} condition`)
|
||
log('info', 'Skipping Package', `${bin} is already in the PATH`)
|
||
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', 'Skipping Package', `${pkg} is being skipped because of the _bin condition`)
|
||
log('info', 'Skipping Package', `${bin} is already in the PATH`)
|
||
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]
|
||
if (doubleScoped) {
|
||
let pref
|
||
if (softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osID]) {
|
||
pref = preference + ':' + osID
|
||
log('info', 'Installing Dependencies', `Installing dependencies for ${packageKey}.${pref}`)
|
||
await generateInstallOrders(softwarePackages[packageKey][pref])
|
||
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + preference + ':' + osType]) {
|
||
pref = preference + ':' + osType
|
||
log('info', 'Installing Dependencies', `Installing dependencies for ${packageKey}.${pref}`)
|
||
await generateInstallOrders(softwarePackages[packageKey][pref])
|
||
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID + ':' + preference]) {
|
||
pref = osID + ':' + preference
|
||
log('info', 'Installing Dependencies', `Installing dependencies for ${packageKey}.${pref}`)
|
||
await generateInstallOrders(softwarePackages[packageKey][pref])
|
||
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType + ':' + preference]) {
|
||
pref = osType + ':' + preference
|
||
log('info', 'Installing Dependencies', `Installing dependencies for ${packageKey}.${pref}`)
|
||
await generateInstallOrders(softwarePackages[packageKey][pref])
|
||
}
|
||
} else if (scopedPkgManager) {
|
||
const pref = preference
|
||
log('info', 'Installing Dependencies', `Installing dependencies for ${packageKey}.${pref}`)
|
||
await generateInstallOrders(softwarePackages[packageKey][pref])
|
||
} else if (scopedSystem) {
|
||
let pref
|
||
if (softwarePackages[packageKey]['_' + currentSelector + ':' + osID]) {
|
||
pref = osID
|
||
log('info', 'Installing Dependencies', `Installing dependencies for ${packageKey}.${pref}`)
|
||
await generateInstallOrders(softwarePackages[packageKey][pref])
|
||
} else if (softwarePackages[packageKey]['_' + currentSelector + ':' + osType]) {
|
||
pref = osType
|
||
log('info', 'Installing Dependencies', `Installing dependencies for ${packageKey}.${pref}`)
|
||
await generateInstallOrders(softwarePackages[packageKey][pref])
|
||
}
|
||
} else if (normalCheck) {
|
||
log('info', 'Installing Dependencies', `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 systemdHook = getHook(packages, 'systemd', scopedPreference, preference)
|
||
if (systemdHook) {
|
||
installOrdersSystemd.concat(typeof systemdHook === 'string' ? [systemdHook] : systemdHook)
|
||
}
|
||
if (!installOrders[preference]) {
|
||
installOrders[preference] = []
|
||
}
|
||
log('info', 'Install Orders', `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 {
|
||
await $`sudo apt-get autoclean`
|
||
await $`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`)
|
||
await $`dpkg -l ${dep} | grep -E '^ii' > /dev/null`
|
||
} catch (e) {
|
||
log('info', 'apt-get Installation', `Installing ${dep} since it is not already present on the system`)
|
||
await $`sudo apt-get install -y ${dep}`
|
||
}
|
||
} 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_user: "${process.env.USER}", install_homebrew: False }' -m setup`
|
||
} else if (packageManager === 'apk') {
|
||
await $`sudo apk update`
|
||
} else if (packageManager === 'apt') {
|
||
await $`sudo apt-get update`
|
||
} else if (packageManager === 'basher') {
|
||
} else if (packageManager === 'binary') {
|
||
} else if (packageManager === 'brew' || packageManager === 'cask') {
|
||
if (!brewUpdated) {
|
||
brewUpdated = true
|
||
await $`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) {
|
||
await $`sudo dnf update -y`
|
||
} else if (yum) {
|
||
await $`sudo yum update -y`
|
||
}
|
||
} else if (packageManager === 'flatpak') {
|
||
await $`sudo flatpak update -y`
|
||
} else if (packageManager === 'gem') {
|
||
} else if (packageManager === 'go') {
|
||
} else if (packageManager === 'nix') {
|
||
await $`nix-channel --update`
|
||
} else if (packageManager === 'npm') {
|
||
} else if (packageManager === 'pacman') {
|
||
await $`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) {
|
||
await $`sudo port sync`
|
||
} else {
|
||
log('error', 'Port Not Installed', 'Skipping sudo port sync step because port is not installed')
|
||
}
|
||
} else if (packageManager === 'scoop') {
|
||
await $`scoop update`
|
||
} else if (packageManager === 'snap' || packageManager === 'snap-classic') {
|
||
if (!snapRefreshed) {
|
||
snapRefreshed = true
|
||
await $`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') {
|
||
await $`winget source update`
|
||
} else if (packageManager === 'yay') {
|
||
} else if (packageManager === 'zypper') {
|
||
await $`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 = 'Package Manager Install'
|
||
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 https://github.com/srevinsaju/zap/releases/download/continuous/zap-amd64 > /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) {
|
||
await $`sudo apk add flatpak`
|
||
} else if (apt) {
|
||
await $`
|
||
sudo apt-get install -y flatpak
|
||
if [ -f /usr/bin/gnome-shell ]; then
|
||
sudo apt-get install -y gnome-software-plugin-flatpak
|
||
fi
|
||
if [ -f /usr/bin/plasmashell ]; then
|
||
sudo apt-get install -y plasmashell
|
||
fi
|
||
`
|
||
} 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 $`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') {
|
||
await ensureInstalled('gem', $`brew install ruby`)
|
||
} else if (packageManager === 'go') {
|
||
await ensureInstalled('gem', $`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) {
|
||
await $`if [ -f /etc/apt/preferences.d/nosnap.pref ]; then sudo mv /etc/apt/preferences.d/nosnap.pref /etc/apt/nosnap.pref.bak; fi && 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) {
|
||
await $`sudo dnf install -y snapd && if [ ! -d /snap ]; then sudo ln -s /var/lib/snapd/snap /snap; fi`
|
||
} else if (yum) {
|
||
await $`sudo yum install -y snapd && sudo systemctl enable --now snapd.socket && if [ ! -d /snap ]; then sudo ln -s /var/lib/snapd/snap /snap; fi`
|
||
} else if (pacman) {
|
||
await $`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) {
|
||
await $`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, 3) === 'http' && pkg.slice(-8) === 'AppImage') {
|
||
log('info', 'AppImage Install', `Installing ${pkg} from its URL`)
|
||
await $`zap install --from ${pkg}`
|
||
} else if (pkg.includes('/')) {
|
||
log('info', 'AppImage Install', `Installing ${pkg} from a GitHub Release`)
|
||
await $`zap install --github --from ${pkg}`
|
||
} else {
|
||
log('info', 'AppImage Install', `Installing ${pkg} using the AppImage Catalog`)
|
||
await $`zap install ${pkg}`
|
||
}
|
||
} catch (e) {
|
||
log('error', 'AppImage / Zap Failure', `There was an error using Zap to install ${pkg}`)
|
||
}
|
||
}
|
||
} 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 $`${unbufferPrefix} ansible 127.0.0.1 -v${verboseMode} -e '{ ansible_connection: "local", ansible_user: "${process.env.USER}", install_homebrew: False }' -m include_role -a name=${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Ansible Role Failure', `There was an error installing ${pkg} with Ansible`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'apk') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`sudo apk add ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'APK Install Failure', `There was an error installing ${pkg} with apk`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'apt') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`sudo apt-get install -y ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'apt-get Failure', `There was an error installing ${pkg} with apt-get`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'basher') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`basher install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Basher Failure', `There was an error installing ${pkg} with basher`)
|
||
}
|
||
}
|
||
} 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', 'Binary Release Install', `There was an error installing the binary release for ${pkg}`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'brew') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`brew install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Homebrew Failure', `There was an error installing ${pkg} with brew`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'cask') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`brew install --cask ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Homebrew Cask Failure', `There was an error installing ${pkg} with Homebrew Cask`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'cargo') {
|
||
for (const pkg of packages) {
|
||
try {
|
||
await $`cargo install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Cargo Failure', `There was an error installing ${pkg} with Cargo`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'choco') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`choco install -y ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Chocolatey Failure', `There was an error installing ${pkg} with Chocolatey`)
|
||
}
|
||
}
|
||
} 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 {
|
||
await $`sudo dnf install -y ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'dnf Failure', `There was an error installing ${pkg} with dnf`)
|
||
}
|
||
} else if (yum) {
|
||
try {
|
||
await $`sudo yum install -y ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'yum Failure', `There was an error installing ${pkg} with yum`)
|
||
}
|
||
}
|
||
}
|
||
} else if (packageManager === 'flatpak') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`sudo flatpak install -y flathub ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Flatpak Failure', `There was an error installing ${pkg} with flatpak`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'gem') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`gem install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Gem Failure', `There was an error installing ${pkg} with gem`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'go') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`go install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Go Failure', `There was an error installing ${pkg} with go`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'nix') {
|
||
} else if (packageManager === 'npm') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`volta install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Volta Failure', `There was an error installing ${pkg} with volta`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'pacman') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`sudo pacman -Sy --noconfirm --needed ${dep}`
|
||
} catch (e) {
|
||
log('error', 'Pacman Failure', `There was an error installing ${pkg} with pacman`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'pipx') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`pipx install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'PIPX Failure', `There was an error installing ${pkg} with pipx`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'pkg') {
|
||
} else if (packageManager === 'port') {
|
||
const port = which.sync('port', { nothrow: true })
|
||
if (port) {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`sudo port install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Port Failure', `There was an error installing ${pkg} with port`)
|
||
}
|
||
}
|
||
} 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 {
|
||
await $`scoop install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Scoop Failure', `There was an error installing ${pkg} with scoop`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'snap') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`sudo snap install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Snap Failure', `There was an error installing ${pkg} with snap`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'script') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Script Install Failure', `There was an error running the script installation method for ${pkg}`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'snap-classic') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`sudo snap install --classic ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Snap Failure', `There was an error installing ${pkg} with snap in classic mode`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'whalebrew') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`whalebrew install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Whalebrew Failure', `There was an error installing ${pkg} with whalebrew`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'winget') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`winget install ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Winget Failure', `There was an error installing ${pkg} with winget`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'yay') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`yay -Sy --noconfirm --needed ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Yay Failure', `There was an error installing ${pkg} with yay`)
|
||
}
|
||
}
|
||
} else if (packageManager === 'zypper') {
|
||
for (let pkg of packages) {
|
||
try {
|
||
await $`sudo zypper install -y ${pkg}`
|
||
} catch (e) {
|
||
log('error', 'Zypper Failure', `There was an error installing ${pkg} with zypper`)
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
log('error', logStage, `Possibly encountered an error while installing via \`${packageManager}\``)
|
||
log('info', logStage, `Proceeding with the installation..`)
|
||
}
|
||
}
|
||
|
||
async function updateSystemd(service) {
|
||
const logStage = 'Systemd Service'
|
||
if (osType === 'linux') {
|
||
const systemd = which.sync('systemctl', { nothrow: true })
|
||
if (systemd) {
|
||
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`)
|
||
}
|
||
}
|
||
}
|
||
|
||
async function pruneInstallOrders(installOrders) {
|
||
const newOrders = Object.assign({}, installOrders)
|
||
for (const pkgManager in installOrders) {
|
||
if (pkgManager === 'apt') {
|
||
newOrders[pkgManager] = newOrders[pkgManager].filter(async x => {
|
||
try {
|
||
await $`dpkg -l ${pkg} | grep -E '^ii' > /dev/null`
|
||
return true
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
})
|
||
} else if (pkgManager === 'brew') {
|
||
newOrders[pkgManager] = newOrders[pkgManager].filter(async x => {
|
||
try {
|
||
await $`brew list ${pkg}`
|
||
return true
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
})
|
||
} else if (pkgManager === 'dnf') {
|
||
const dnf = which.sync('dnf', { nothrow: true })
|
||
newOrders[pkgManager] = newOrders[pkgManager].filter(async x => {
|
||
try {
|
||
if (dnf) {
|
||
await $`rpm -qa | grep ${pkg} > /dev/null > /dev/null`
|
||
} else {
|
||
await $`rpm -qa | grep ${pkg} > /dev/null > /dev/null`
|
||
}
|
||
return true
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
})
|
||
} else if (pkgManager === 'flatpak') {
|
||
newOrders[pkgManager] = newOrders[pkgManager].filter(async x => {
|
||
try {
|
||
if (dnf) {
|
||
await $`rpm -qa | grep ${pkg} > /dev/null > /dev/null`
|
||
} else {
|
||
await $`rpm -qa | grep ${pkg} > /dev/null > /dev/null`
|
||
}
|
||
return true
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
})
|
||
} else if (pkgManager === 'pacman') {
|
||
newOrders[pkgManager] = newOrders[pkgManager].filter(async x => {
|
||
try {
|
||
await $`pacman -Qs ${pkg} > /dev/null > /dev/null`
|
||
return true
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
})
|
||
}
|
||
}
|
||
return newOrders
|
||
}
|
||
|
||
// 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', 'Install Orders', `Calculating the install orders`)
|
||
await generateInstallOrders(pkgsToInstall ? pkgsToInstall : process.argv.slice(3))
|
||
log(
|
||
'info',
|
||
'Ensure Package Manager Installed',
|
||
`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 Orders', `The install orders were generated:`)
|
||
} catch (e) {
|
||
log('error', 'Install Orders', `There was an error reducing the duplicates in the install orders`)
|
||
console.error(e)
|
||
}
|
||
installOrders = await pruneInstallOrders(installOrders)
|
||
console.log(installOrders)
|
||
log('info', 'Package Manager Pre-Install', `Running package manager pre-installation steps`)
|
||
for (const packageManager of packageManagers) {
|
||
await beforeInstall(packageManager)
|
||
}
|
||
log('info', 'Package Pre-Install', `Running package-specific pre-installation steps`)
|
||
for (const script of installOrdersPre) {
|
||
await $`${script}`
|
||
}
|
||
log('info', 'Package Install', `Installing the packages`)
|
||
for (const packageManager of packageManagers) {
|
||
const asyncOrders = []
|
||
asyncOrders.push(installPackageList(packageManager, installOrders[packageManager]))
|
||
await Promise.all(asyncOrders)
|
||
}
|
||
log('info', 'Package Post-Install', `Running package-specific post-installation steps`)
|
||
for (const service of installOrdersSystemd) {
|
||
await updateSystemd(service)
|
||
}
|
||
for (const script of installOrdersPost) {
|
||
await $`${script}`
|
||
}
|
||
log('info', 'Package Manager Post-Install', `Running package manager post-installation steps`)
|
||
for (const packageManager of packageManagers) {
|
||
await afterInstall(packageManager)
|
||
}
|
||
log('success', 'Installation Complete', `Done!`)
|
||
}
|
||
|
||
// Start the main process
|
||
await installSoftware(false)
|