install.fairie/.local/bin/install-program
2022-12-02 18:31:20 +00:00

685 lines
24 KiB
Text

#!/usr/bin/env zx
const execSync = require('child_process').execSync
$.log = (entry) => {
if (entry.kind === 'cmd' && entry.cmd.substring(0, 4) === 'logg') {
//execSync(entry.cmd, {stdio: 'inherit', shell: true})
} else {
log(entry)
}
}
let installData
const installOrders = {};
const installOrdersPre = [];
const installOrdersPost = [];
const osType = await OSType();
let osID = osType
if(osType === 'linux') {
osID = await realeaseID()
}
// Download the installation map
async function downloadInstallData() {
const response = await fetch(
"https://gitlab.com/megabyte-labs/misc/dotfiles/-/raw/master/.local/share/chezmoi/software.yml"
);
if (response.ok) {
const text = await response.text()
return YAML.parse(text)
} else {
await $`logg error 'Failed to download the installation map'`
}
}
// Creates the installOrders object which maps package managers to arrays of packages to install
async function generateInstallOrders() {
const packagesToInstall = process.argv.slice(3);
const installerPreference = await OSTypeInstallerKey()
await $`logg info 'Installer preference category detected as ${installerPreference}'`
const preferenceOrder = installData.installerPreference[installerPreference];
await $`logg info 'Preference order acquired:'`
console.log(preferenceOrder)
const softwarePackages = installData.softwarePackages;
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 {
await $`logg warn 'The package \`${pkg}\` was not found in the installation map'`
continue;
}
for (let preference of preferenceOrder) {
if (softwarePackages[packageKey][preference + ":" + osID]) {
await updateInstallMaps(
preference,
softwarePackages[packageKey],
preference + ":" + osID,
pkg,
packageKey
);
break
} else if (softwarePackages[packageKey][preference + ":" + osType]) {
await updateInstallMaps(
preference,
softwarePackages[packageKey],
preference + ":" + osType,
pkg,
packageKey
);
break
} else if (softwarePackages[packageKey][preference]) {
await updateInstallMaps(preference, softwarePackages[packageKey], preference, pkg, packageKey);
break
}
}
}
return installOrders;
}
// Update install, pre-hook, and post-hook objects
async function updateInstallMaps(preference, packages, scopedPreference, pkg, packageKey) {
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
);
}
if (!installOrders[preference]) {
installOrders[preference] = [];
}
await $`logg info 'Found a match for the package \`${pkg}\` (${packageKey} via ${scopedPreference})'`
const newPackages = packages[scopedPreference];
installOrders[preference] = installOrders[preference].concat(
typeof newPackages === "string" ? [newPackages] : newPackages
);
}
// 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() {
const apt = which.sync('apt-get', { nothrow: true })
const dnf = which.sync('dnf', { nothrow: true })
const freebsd = which.sync('pkg', { 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 (freebsd) {
return 'freebsd'
} else {
try {
await $`test -d /Applications && test -d /Library`
return 'darwin'
} catch (e) {
return 'windows'
}
}
}
// 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) {
if (packageManager === 'appimage') {
} else if (packageManager === 'ansible') {
await $`logg info 'Ensuring temporary passwordless sudo privileges used by Ansible are removed'`
await $`sudo sed -i '/# TEMPORARY FOR ANSIBLE INSTALL/d' /etc/sudoers`
} else if (packageManager === 'apk') {
} else if (packageManager === 'apt') {
} 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 === 'crew') {
} else if (packageManager === 'dnf') {
} else if (packageManager === 'flatpak') {
} else if (packageManager === 'snap') {
} else if (packageManager === 'whalebrew') {
} else if (packageManager === 'winget') {
} else if (packageManager === 'yay') {
} else if (packageManager === 'zypper') {
}
}
// Pre-install hook
async function beforeInstall(packageManager) {
if (packageManager === 'appimage') {
} else if (packageManager === 'ansible') {
await $`logg info 'Temporarily enabling passwordless sudo for Ansible role installations'`
await $`sudo echo "$(whoami) ALL=(ALL:ALL) NOPASSWD: ALL # TEMPORARY FOR ANSIBLE INSTALL" > /etc/sudoers`
} else if (packageManager === 'apk') {
} else if (packageManager === 'apt') {
await $`sudo apt-get update`
} 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') {
await $`sudo pacman -Syu`
} else if (packageManager === 'pipx') {
} else if (packageManager === 'pkg') {
} else if (packageManager === 'port') {
} else if (packageManager === 'scoop') {
} else if (packageManager === 'crew') {
} else if (packageManager === 'dnf') {
} else if (packageManager === 'flatpak') {
} else if (packageManager === 'snap') {
} 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) {
await $`logg warn 'The command \`docker run --rm hello-world\` failed'`
try {
const promises = [$`test -d /Applications/Docker.app`, $`logg info 'Attempting to open \`/Applications/Docker.app\` (Docker Desktop for macOS). This should take about 30 seconds.'`, $`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) {
await $`logg warn 'Docker Desktop appears to not be installed!'`
}
}
}
} else if (packageManager === 'winget') {
} else if (packageManager === 'yay') {
} else if (packageManager === 'zypper') {
}
}
async function ensureInstalled(bin, callback) {
const installed = which.sync(bin, { nothrow: true })
if (installed) {
await $`logg info '\`${bin}\` is available'`
} else {
await $`logg warn '\`${bin}\` is not installed!'`
if (callback) {
await callback
} else {
await $`logg error 'There does not appear to be an installation method available for \`${bin}\`'`
}
}
}
async function ensurePackageManagerAnsible() {
await $`pipx install ansible`
await $`pipx inject ansible PyObjC PyObjC-core docker lxml netaddr pexpect python-vagrant pywinrm requests-credssp watchdog`
await $`touch "$HOME/.cache/megabytelabs/ansible-installed"`
await $`logg info 'Ansible and its supporting packages are now installed via pipx'`
}
// Ensure the package manager is available
let packageManagerInstalled = {};
async function ensurePackageManager(packageManager) {
await $`logg info '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') {
} else if (packageManager === 'ansible') {
try {
await $`test -f "$HOME/.cache/megabytelabs/ansible-installed"`
const ansible = which.sync('ansible', { nothrow: true })
if (ansible) {
await $`logg info '\`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') {
} 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
logg info '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
logg warn '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') {
await ensureInstalled('cargo', $`
# TODO Bash script that installs 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) {
await $`logg info '\`dnf\` is available'`
} else if (yum) {
await $`logg info '\`yum\` is available'`
} else {
await $`logg error 'Both \`dnf\` and \`yum\` are not available'`
}
} else if (packageManager === 'flatpak') {
const flatpak = which.sync('flatpak', { nothrow: true })
if (flatpak) {
await $`logg info '\`flatpak\` is available'`
} else {
const apk = which.sync('apk', { nothrow: true })
const apt = which.sync('apt', { 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) {
$`sudo apk add flatpak`
} else if(apt) {
$`
sudo apt install -y flatpak
if [ -f /usr/bin/gnome-shell ]; then
sudo apt install -y gnome-software-plugin-flatpak
fi
if [ -f /usr/bin/plasmashell ]; then
sudo apt 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 {
await $`logg error '\`flatpak\` failed to install!'`
}
await $`logg info '\`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('npm', { nothrow: true })
const node = which('node', { nothrow: true })
const volta = which('volta', { nothrow: true })
if (npm && node && volta) {
await $`logg info '\`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
`
}
} else if (packageManager === 'pacman') {
await ensureInstalled('pacman', false)
} else if (packageManager === 'pipx') {
await ensureInstalled('pipx', $`brew install pipx && pipx ensurepath`)
} else if (packageManager === 'pkg') {
await ensureInstalled('pkg', false)
} else if (packageManager === 'port') {
await ensureInstalled('port', $`
echo -n "TODO - script that installs port on macOS here"
`)
} 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 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) {
$`sudo snap install core`
}
} 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) {
let pkg = packages
try {
if (packageManager === 'appimage') {
} else if (packageManager === 'ansible') {
for (const role of packages) {
await $`ansible localhost -m include_role -a name=${role}`
}
} else if (packageManager === 'apk') {
await $`sudo apk add ${packages}`
} else if (packageManager === 'apt') {
await $`sudo apt-get install -y ${packages}`
} else if (packageManager === 'basher') {
} else if (packageManager === 'binary') {
} else if (packageManager === 'brew') {
await $`brew install ${packages}`
} else if (packageManager === 'cask') {
await $`brew install --cask ${packages}`
} else if (packageManager === 'cargo') {
for (const pkg of packages) {
await $`cargo install ${pkg}`
}
} else if (packageManager === 'choco') {
await $`choco install -y ${packages}`
} else if (packageManager === 'crew') {
} else if (packageManager === 'dnf') {
const dnf = which.sync('dnf', { nothrow: true })
const yum = which.sync('yum', { nothrow: true })
if (dnf) {
await $`dnf install -y ${packages}`
} else if (yum) {
await $`yum install -y ${packages}`
}
} else if (packageManager === 'flatpak') {
for (let pkg of packages) {
await $`sudo flatpak install flathub ${pkg}`
}
} else if (packageManager === 'gem') {
for (let pkg of packages) {
await $`gem install ${pkg}`
}
} else if (packageManager === 'go') {
for (let pkg of packages) {
await $`go install ${pkg}`
}
} else if (packageManager === 'nix') {
} else if (packageManager === 'npm') {
for (let pkg of packages) {
await $`volta install ${pkg}`
}
} else if (packageManager === 'pacman') {
await $`sudo pacman -Sy --noconfirm --needed ${packages}`
} else if (packageManager === 'pipx') {
for (let pkg of packages) {
await $`pipx install ${pkg}`
}
} else if (packageManager === 'pkg') {
} else if (packageManager === 'port') {
for (let pkg of packages) {
await $`sudo port install ${pkg}`
}
} else if (packageManager === 'scoop') {
for (let pkg of packages) {
await $`scoop install ${pkg}`
}
} else if (packageManager === 'snap') {
for (let pkg of packages) {
await $`sudo snap install -y ${pkg}`
}
} else if (packageManager === 'whalebrew') {
for (let pkg of packages) {
await $`whalebrew install ${pkg}`
}
} else if (packageManager === 'winget') {
} else if (packageManager === 'yay') {
for (let pkg of packages) {
await $`yay -Sy --noconfirm --needed ${pkg}`
}
} else if (packageManager === 'zypper') {
await $`sudo zypper install -y ${packages}`
}
} catch (e) {
await $`logg error 'Possibly encountered an error while installing via \`${packageManager}\`'`
await $`logg info 'Error was encountered while installing: ${pkg}'`
await $`logg info 'Proceeding with the installation..'`
}
}
// main process
async function main() {
await $`logg info 'Fetching the latest version of the installation map'`
installData = await downloadInstallData();
await $`logg info 'Calculating the install orders'`
await generateInstallOrders();
await $`logg info 'Ensuring any package managers that will be used are installed / configured'`
const packageManagers = Object.keys(installOrders);
for (const packageManager of packageManagers) {
await ensurePackageManager(packageManager);
}
await $`logg info 'The install orders were generated:'`
console.log(installOrders)
await $`logg info 'Running package manager pre-installation steps'`
for (const packageManager of packageManagers) {
await beforeInstall(packageManager);
}
await $`logg info 'Running package-specific pre-installation steps'`
for (const script of installOrdersPre) {
await $`${script}`;
}
await $`logg info 'Installing the packages'`
for (const packageManager of packageManagers) {
const asyncOrders = [];
asyncOrders.push(
Promise.resolve(
installPackageList(packageManager, installOrders[packageManager])
)
);
await Promise.all(asyncOrders);
}
await $`logg info 'Running package-specific post-installation steps'`
for (const script of installOrdersPost) {
await $`${script}`;
}
await $`logg info 'Running package manager post-installation steps'`
for (const packageManager of packageManagers) {
await afterInstall(packageManager);
}
await $`logg success 'Done!'`
}
// Start the main process
await main();