From 1a8fdfb96302a1b695ce9be1a339e1e423752f0f Mon Sep 17 00:00:00 2001 From: Brian Zalewski <59970525+ProfessorManhattan@users.noreply.github.com> Date: Mon, 29 Jan 2024 05:13:53 +0000 Subject: [PATCH] Latest --- docs/scripts/profile/profile.sh.tmpl.md | 15 +- home/.chezmoidata.yaml | 3 +- .../universal/run_after_24-cleanup.sh.tmpl | 19 + .../run_before_04-requirements.sh.tmpl | 8 + .../universal/run_before_05-system.sh.tmpl | 114 +- .../debian/common-dependencies | 2 + .../ubuntu/common-dependencies | 2 + .../universal/common-dependencies | 2 + .../group.com.docker/settings.json.tmpl | 2 +- home/dot_bashrc | 15 +- home/dot_config/shell/exports.sh.tmpl | 3 +- home/dot_local/bin/executable_install-program | 7 - .../bin/executable_install-program-edit | 1882 +++++++++++++++++ home/dot_local/bin/executable_installx | 95 +- home/dot_local/bin/executable_update-system | 101 + home/dot_zshrc | 15 +- scripts/src/provision.sh.tmpl | 2 +- software.yml | 5 - system/etc/bash.bashrc | 4 + system/etc/bashrc | 15 + system/etc/zshrc | 78 + 21 files changed, 2331 insertions(+), 58 deletions(-) create mode 100644 home/dot_local/bin/executable_install-program-edit create mode 100644 home/dot_local/bin/executable_update-system create mode 100644 system/etc/bash.bashrc create mode 100644 system/etc/bashrc create mode 100644 system/etc/zshrc diff --git a/docs/scripts/profile/profile.sh.tmpl.md b/docs/scripts/profile/profile.sh.tmpl.md index 803afd44..ce377d0d 100644 --- a/docs/scripts/profile/profile.sh.tmpl.md +++ b/docs/scripts/profile/profile.sh.tmpl.md @@ -155,13 +155,14 @@ if [ -d /Applications ] && [ -d /System ]; then fi ### SDKMan -if command -v brew > /dev/null && command -v sdkman-cli > /dev/null; then - export SDKMAN_DIR="$(brew --prefix sdkman-cli)/libexec" - . "$SDKMAN_DIR/bin/sdkman-init.sh" -elif [ -f "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then - export SDKMAN_DIR="$XDG_DATA_HOME/sdkman" - . "$SDKMAN_DIR/bin/sdkman-init.sh" -fi +### Deprecated in favor of mise +# if command -v brew > /dev/null && command -v sdkman-cli > /dev/null; then +# export SDKMAN_DIR="$(brew --prefix sdkman-cli)/libexec" +# . "$SDKMAN_DIR/bin/sdkman-init.sh" +# elif [ -f "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then +# export SDKMAN_DIR="$XDG_DATA_HOME/sdkman" +# . "$SDKMAN_DIR/bin/sdkman-init.sh" +# fi ### VIM export GVIMINIT='let $MYGVIMRC="$XDG_CONFIG_HOME/vim/gvimrc" | source $MYGVIMRC' diff --git a/home/.chezmoidata.yaml b/home/.chezmoidata.yaml index fbfc34eb..e62fbb44 100644 --- a/home/.chezmoidata.yaml +++ b/home/.chezmoidata.yaml @@ -866,7 +866,6 @@ softwareGroups: - proxyman - pushpin - rust - - sdkman-cli - snapcraft - solidity - swimat @@ -1591,6 +1590,8 @@ softwareGroups: note: Deprecated because browser extensions do a better job of unifying bookmarks. Namely, the extension called Floccus allows cross-browser bookmark syncing. The Raindrop package also requires a paid subscription for advanced features. - pkg: rvm note: Deprecated in favor of using ASDF. + - pkg: sdkman-cli + note: Deprecated in favor of mise - pkg: standard-notes note: Deprecated in favor of Obsidian / Notion - pkg: stubby diff --git a/home/.chezmoiscripts/universal/run_after_24-cleanup.sh.tmpl b/home/.chezmoiscripts/universal/run_after_24-cleanup.sh.tmpl index 4bf14018..64f8355e 100644 --- a/home/.chezmoiscripts/universal/run_after_24-cleanup.sh.tmpl +++ b/home/.chezmoiscripts/universal/run_after_24-cleanup.sh.tmpl @@ -8,6 +8,19 @@ {{ includeTemplate "universal/profile" }} {{ includeTemplate "universal/logg" }} +cleanAptGet() { + if command -v apt-get > /dev/null; then + logg info 'Running sudo apt-get autoclean' && sudo apt-get autoclean + logg info 'Running sudo apt-get -y autoremove' && sudo apt-get -y autoremove + fi +} + +cleanupBrew() { + if command -v brew > /dev/null; then + logg info 'Running brew cleanup' && brew cleanup + fi +} + ### Remove meta sudo file if [ -f "$HOME/.sudo_as_admin_successful" ]; then rm -f "$HOME/.sudo_as_admin_successful" @@ -43,3 +56,9 @@ if [ -d /Applications ] && [ -d /System ]; then logg info 'Emptying trash' && m trash clean fi fi + +cleanAptGet & +cleanupBrew & +wait + +logg success 'Finished cleanup process' diff --git a/home/.chezmoiscripts/universal/run_before_04-requirements.sh.tmpl b/home/.chezmoiscripts/universal/run_before_04-requirements.sh.tmpl index c6af0d9e..82b544d2 100644 --- a/home/.chezmoiscripts/universal/run_before_04-requirements.sh.tmpl +++ b/home/.chezmoiscripts/universal/run_before_04-requirements.sh.tmpl @@ -123,6 +123,10 @@ else fi done elif [ '{{ .host.distro.id }}' = 'debian' ]; then + if command -v apt-get > /dev/null && [ -f /etc/apt/preferences.d/nosnap.pref ]; then + logg info 'Moving /etc/apt/preferences.d/nosnap.pref to /etc/apt/nosnap.pref.bak' && sudo mv -f /etc/apt/preferences.d/nosnap.pref /etc/apt/nosnap.pref.bak + fi + ### Print dependency list logg 'Installing common dependencies using apt-get' logg info 'Dependencies: {{ $packages | sortAlpha | uniq | join " " -}}' @@ -239,6 +243,10 @@ else fi done elif [ '{{ .host.distro.id }}' = 'ubuntu' ]; then + if command -v apt-get > /dev/null && [ -f /etc/apt/preferences.d/nosnap.pref ]; then + logg info 'Moving /etc/apt/preferences.d/nosnap.pref to /etc/apt/nosnap.pref.bak' && sudo mv -f /etc/apt/preferences.d/nosnap.pref /etc/apt/nosnap.pref.bak + fi + ### Print dependency list logg 'Installing common dependencies using apt-get' logg info 'Dependencies: {{ $packages | sortAlpha | uniq | join " " -}}' diff --git a/home/.chezmoiscripts/universal/run_before_05-system.sh.tmpl b/home/.chezmoiscripts/universal/run_before_05-system.sh.tmpl index 304b5633..1fb544e6 100644 --- a/home/.chezmoiscripts/universal/run_before_05-system.sh.tmpl +++ b/home/.chezmoiscripts/universal/run_before_05-system.sh.tmpl @@ -640,18 +640,42 @@ showNotificationCenter() { fi } +installAnsible() { + if command -v pipx > /dev/null; then + if [ ! -f "${XDG_CACHE_HOME:-$HOME/.cache}/install.doctor/ansible-installed" ]; then + logg info 'Running pipx install ansible-core' && pipx install ansible-core + if [ -d /Applications ] && [ -d /System ]; then + logg info 'Injecting ansible-core pipx with ansible-core PyObjC PyObjC-core because system is macOS' && pipx inject ansible-core PyObjC PyObjC-core + fi + logg info 'Running pipx inject ansible-core docker lxml netaddr pexpect python-vagrant pywinrm requests-credssp watchdog' && pipx inject ansible-core docker lxml netaddr pexpect python-vagrant pywinrm requests-credssp watchdog + mkdir -p "${XDG_CACHE_HOME:-$HOME/.cache}/install.doctor" + touch "${XDG_CACHE_HOME:-$HOME/.cache}/install.doctor/ansible-installed" + else + logg info 'Ansible installation routine appears to have already been run' + fi + else + logg warn 'pipx is unavailable to use for installing Ansible' + fi +} + installBrewPackages() { ensureNodeInstalled ensureDeltaInstalled - ensureBrewPackageInstalled "gh" - ensureBrewPackageInstalled "go" - ensureBrewPackageInstalled "zx" ensureBrewPackageInstalled "volta" volta install node@latest & volta install yarn@latest & npm install -g npm@latest & + ensureBrewPackageInstalled "pipx" + pipx ensurepath & + ensureBrewPackageInstalled "gh" + ensureBrewPackageInstalled "go" + ensureBrewPackageInstalled "ruby" + ensureBrewPackageInstalled "rustup" + ensureBrewPackageInstalled "zx" + ensureBrewPackageInstalled "whalebrew" wait logg success 'Finished installing auxilary Homebrew packages' + logg info 'Ensuring Ansible is installed (with plugins)' && installAnsible } ensureMacportsInstalled() { @@ -666,8 +690,83 @@ ensureMacportsInstalled() { fi } +setupSnap() { + if [ ! -d /Applications ] && [ ! -d /System ] && command -v snap > /dev/null; then + logg info 'Enabling snapd' && sudo systemctl enable snapd + logg info 'Starting snapd' && sudo systemctl start snapd + if [ -d /snap ]; then + logg info 'Linking /var/lib/snapd/snap to /snap' && sudo ln -s /var/lib/snapd/snap /snap + fi + logg info 'Running sudo snap info core' && sudo snap info core + logg info 'Running sudo snap wait system seed.loaded' && sudo snap wait system seed.loaded + logg info 'Running sudo snap install core' && sudo snap install core + fi +} + +installYay() { + if [ -f /etc/arch-release ] && ! command -v yay > /dev/null + sudo rm -rf /usr/local/src/yay + sudo git clone https://aur.archlinux.org/yay.git /usr/local/src/yay + cd /usr/local/src/yay + sudo makepkg -si + fi +} + +installNix() { + if ! command -v nix-shell > /dev/null; then + if [ -d /Applications ] && [ -d /System ]; then + ### macOS + logg info 'Installing nix for macOS' && sh <(curl -L https://nixos.org/nix/install) --yes + else + ### Linux + logg info 'Installing nix' && sh <(curl -L https://nixos.org/nix/install) --daemon --yes + fi + fi +} + +rustUpInit() { + if command -v rustup-init > /dev/null && ! command -v rustc > /dev/null; then + logg info 'Running rustup-init -y' && rustup-init -y + fi +} + +zapInstall() { + if ! command -v zap > /dev/null; then + ### Architecture + if [ -z ${ARCH+x} ]; then + MACHINE_ARCH="$(uname -m)" + if [ "$MACHINE_ARCH" = "amd64" ]; then + ARCH="amd64" + elif [ "$MACHINE_ARCH" = "x86_64" ]; then + ARCH="amd64" + elif [ "$MACHINE_ARCH" = "i386" ]; then + ARCH="386" + elif [ "$MACHINE_ARCH" = "i686" ]; then + ARCH="386" # both are 32bit, should be compatible + elif [ "$MACHINE_ARCH" = "aarch64" ]; then + ARCH="arm64" + elif [ "$MACHINE_ARCH" = "arm64" ]; then + ARCH="arm64" + elif [ "$MACHINE_ARCH" = "arm" ]; then + ARCH="arm" + fi + export ARCH + fi + logg info 'Downloading zap to /usr/local/bin/zap' && sudo curl -sSL --output /usr/local/bin/zap "https://github.com/srevinsaju/zap/releases/download/continuous/zap-${ARCH}" + logg info 'Making /usr/local/bin/zap executable' && sudo chmod +x /usr/local/bin/zap + fi +} + +addFlathub() { + if command -v flatpak > /dev/null; then + logg info 'Adding flatpak flathub repository' && sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + fi +} + +# TODO - Add install on macOS for macports if [ -n "$DEBUG" ] || [ -n "$DEBUG_MODE" ]; then logg info 'The DEBUG or DEBUG_MODE environment variable is set so preliminary system tweaks will be run synchronously' + addFlathub allocateSwap configureGPG disableDStoreFileCreation @@ -679,12 +778,17 @@ if [ -n "$DEBUG" ] || [ -n "$DEBUG_MODE" ]; then installDocker installJumpCloud installSystemPips + installYay removeLinuxBloatware + rustUpInit setHostname setNtpServer setTimezone + setupSnap showNotificationCenter + zapInstall else + addFlathub & allocateSwap & configureGPG & disableDStoreFileCreation & @@ -696,11 +800,15 @@ else installDocker & installJumpCloud & installSystemPips & + installYay & removeLinuxBloatware & + rustUpInit & setHostname & setNtpServer & setTimezone & + setupSnap & showNotificationCenter & + zapInstall & wait fi diff --git a/home/.chezmoitemplates/debian/common-dependencies b/home/.chezmoitemplates/debian/common-dependencies index b54d473f..f3191d89 100644 --- a/home/.chezmoitemplates/debian/common-dependencies +++ b/home/.chezmoitemplates/debian/common-dependencies @@ -2,6 +2,7 @@ "age/bullseye-backports" "build-essential" "gnome" + "gnome-software-plugin-flatpak" "gnupg-agent" "golang-go" "hopenpgp-tools" @@ -27,6 +28,7 @@ "npm" "pcscd" "pkg-config" + "plasmashell" "progress" "ruby-dev" "scdaemon" diff --git a/home/.chezmoitemplates/ubuntu/common-dependencies b/home/.chezmoitemplates/ubuntu/common-dependencies index 1a24dd86..76b3915e 100644 --- a/home/.chezmoitemplates/ubuntu/common-dependencies +++ b/home/.chezmoitemplates/ubuntu/common-dependencies @@ -3,6 +3,7 @@ "build-essential" "golang-go" "gnome" + "gnome-software-plugin-flatpak" "gnupg-agent" "hopenpgp-tools" "libaio1" @@ -27,6 +28,7 @@ "npm" "pcscd" "pkg-config" + "plasmashell" "progress" "ruby-dev" "scdaemon" diff --git a/home/.chezmoitemplates/universal/common-dependencies b/home/.chezmoitemplates/universal/common-dependencies index 3288562d..7fe83e88 100644 --- a/home/.chezmoitemplates/universal/common-dependencies +++ b/home/.chezmoitemplates/universal/common-dependencies @@ -1,10 +1,12 @@ {{- $packages := list "bash" "bison" + "cargo" "coreutils" "cryptsetup" "curl" "expect" + "flatpak" "git" "grep" "gnupg2" diff --git a/home/Library/Group Containers/group.com.docker/settings.json.tmpl b/home/Library/Group Containers/group.com.docker/settings.json.tmpl index 34665614..839de608 100644 --- a/home/Library/Group Containers/group.com.docker/settings.json.tmpl +++ b/home/Library/Group Containers/group.com.docker/settings.json.tmpl @@ -105,4 +105,4 @@ "wslEngineEnabled": false, "wslInstallMode": "installLatestWsl", "wslUpdateRequired": false -} \ No newline at end of file +} diff --git a/home/dot_bashrc b/home/dot_bashrc index 4c656f46..baf11898 100644 --- a/home/dot_bashrc +++ b/home/dot_bashrc @@ -170,13 +170,14 @@ if [ "$BASH_SUPPORT" = 'true' ] && [ -n "$BASH" ]; then ! command -v rtx > /dev/null || eval "$(rtx activate bash)" ### SDKMan - if command -v brew > /dev/null && command -v sdkman-cli > /dev/null; then - export SDKMAN_DIR="$(brew --prefix sdkman-cli)/libexec" - source "$SDKMAN_DIR/bin/sdkman-init.sh" - elif [ -f "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then - export SDKMAN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/sdkman" - source "$SDKMAN_DIR/bin/sdkman-init.sh" - fi + ### Using mise instead for Java handling + # if command -v brew > /dev/null && command -v sdkman-cli > /dev/null; then + # export SDKMAN_DIR="$(brew --prefix sdkman-cli)/libexec" + # source "$SDKMAN_DIR/bin/sdkman-init.sh" + # elif [ -f "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then + # export SDKMAN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/sdkman" + # source "$SDKMAN_DIR/bin/sdkman-init.sh" + # fi ### Sheldon export SHELDON_CONFIG_FILE="${SHELDON_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/sheldon}/plugins.bash.toml" diff --git a/home/dot_config/shell/exports.sh.tmpl b/home/dot_config/shell/exports.sh.tmpl index 821456aa..ba63032d 100644 --- a/home/dot_config/shell/exports.sh.tmpl +++ b/home/dot_config/shell/exports.sh.tmpl @@ -356,7 +356,8 @@ export NETRC="${XDG_CONFIG_HOME:-$HOME/.config}/netrc" export NAVI_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/navi/config.yaml" ### Nix -export NIX_SSL_CERT_FILE="$HOME/.local/etc/ssl/cloudflare/Cloudflare_CA.crt" +# TODO: Fix this +# export NIX_SSL_CERT_FILE="$HOME/.local/etc/ssl/cloudflare/Cloudflare_CA.crt" ### nnn if command -v nnn > /dev/null; then diff --git a/home/dot_local/bin/executable_install-program b/home/dot_local/bin/executable_install-program index ecbf9605..cc6a013f 100644 --- a/home/dot_local/bin/executable_install-program +++ b/home/dot_local/bin/executable_install-program @@ -588,13 +588,6 @@ 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 INSTALL DOCTOR ANSIBLE/d' /etc/sudoers` - } else { - await $`sudo sed -i '/# TEMPORARY FOR INSTALL DOCTOR ANSIBLE/d' /etc/sudoers` - } } else if (packageManager === 'apk') { } else if (packageManager === 'apt') { try { diff --git a/home/dot_local/bin/executable_install-program-edit b/home/dot_local/bin/executable_install-program-edit new file mode 100644 index 00000000..94388ab5 --- /dev/null +++ b/home/dot_local/bin/executable_install-program-edit @@ -0,0 +1,1882 @@ +#!/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-core` + if (osType === 'darwin') { + await $`pipx inject ansible-core PyObjC PyObjC-core` + } + await $`pipx inject ansible-core docker lxml netaddr pexpect python-vagrant pywinrm requests-credssp watchdog` + await $`mkdir -p "$HOME/.cache/megabyte-labs"` + await $`touch "$HOME/.cache/megabyte-labs/ansible-installed"` + log('info', 'Package Manager Install', `Ansible and its supporting packages are now installed via pipx`) +} +*/ +// Ensure the package manager is available +//let packageManagerInstalled = {} +/* +async function ensurePackageManager(packageManager) { + const logStage = 'Pre-Reqs' + log('info', logStage, `Ensuring \`${packageManager}\` is set up`) + if (packageManagerInstalled[packageManager]) { + return + } else { + packageManagerInstalled[packageManager] = true + } + if (packageManager === 'ansible') { + await ensurePackageManager('pipx') + } + if ( + packageManager === 'gem' || + packageManager === 'go' || + packageManager === 'npm' || + packageManager === 'pipx' || + packageManager === 'whalebrew' + ) { + await ensurePackageManager('brew') + } + if (packageManager === 'appimage') { + const zap = which.sync('zap', { nothrow: true }) + if (!zap) { + log('info', 'Zap Installation', 'Installing Zap to handle AppImage installation') + await ensurePackage('curl') + await $`sudo curl -sSL --output /usr/local/bin/zap https://github.com/srevinsaju/zap/releases/download/continuous/zap-amd64` + await $`sudo chmod +x /usr/local/bin/zap` + } + } else if (packageManager === 'ansible') { + try { + await $`test -f "$HOME/.cache/megabyte-labs/ansible-installed"` + const ansible = which.sync('ansible', { nothrow: true }) + if (ansible) { + log('info', logStage, `\`ansible\` and its supporting packages appear to be installed`) + } else { + //await ensurePackageManagerAnsible() + } + } catch (e) { + //await ensurePackageManagerAnsible() + } + } else if (packageManager === 'apk') { + await ensureInstalled('apk', false) + } else if (packageManager === 'apt') { + await ensureInstalled('apt', false) + } else if (packageManager === 'basher') { + await ensureInstalled( + 'basher', + $` + # TODO + echo "Bash script that installs basher here" + ` + ) + } else if (packageManager === 'binary') { + await ensurePackage('curl') + } else if (packageManager === 'bpkg') { + await ensureInstalled( + 'bpkg', + $` + # TODO + echo "Bash script that installs bpkg here" + ` + ) + } else if (packageManager === 'brew' || packageManager === 'cask') { + const brew = which.sync('brew', { nothrow: true }) + if (!brew) { + await ensureInstalled( + 'brew', + $` + if command -v sudo > /dev/null && sudo -n true; then + echo | bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + 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) diff --git a/home/dot_local/bin/executable_installx b/home/dot_local/bin/executable_installx index 4a291f12..9b0c801c 100644 --- a/home/dot_local/bin/executable_installx +++ b/home/dot_local/bin/executable_installx @@ -146,17 +146,44 @@ async function createCaskLinks() { for (const app of caskApps) { const appField = getPkgData('_app', app, app.installType) const binField = getPkgData('_bin', app, app.installType) - if (fs.existsSync(`${os.homedir()}/Applications/${x[appField]}`)) { - fs.writeFileSync(`${os.homedir()}/.local/bin/cask/${x[binField]}`, `open "$HOME/Applications/${x[appField]}" $*`) - } else if (fs.existsSync(`/Applications/${x[appField]}`)) { - fs.writeFileSync(`${os.homedir()}/.local/bin/cask/${x[binField]}`, `open "/Applications/${x[appField]}" $*`) + if (fs.existsSync(`${os.homedir()}/Applications/${app[appField]}`)) { + fs.writeFileSync(`${os.homedir()}/.local/bin/cask/${app[binField]}`, `#!/usr/bin/env bash\nopen "$HOME/Applications/${app[appField]}" $*`) + await $`chmod +x '${os.homedir()}/.local/bin/cask/${app[binField]}'` + } else if (fs.existsSync(`/Applications/${app[appField]}`)) { + fs.writeFileSync(`${os.homedir()}/.local/bin/cask/${app[binField]}`, `#!/usr/bin/env bash\nopen "/Applications/${app[appField]}" $*`) + await $`chmod +x '${os.homedir()}/.local/bin/cask/${app[binField]}'` } else { - log(`Unable to create bin link to ${x[appField]}`) + log(`Unable to create bin link to ${app[appField]}`) } } caskApps.length && log(`Finished creating Homebrew cask links in ~/.local/bin/cask`) } +async function createFlatpakLinks() { + const flatpakInstallations = await $`flatpak --installations` + const flatpakDir = flatpakInstallations.stdout.replace('\n', '') + const flatpakApps = pkgMap(pkgs) + .filter(x => { + if (x.installType === 'flatpak') { + const binField = getPkgData('_bin', x, x.installType) + const binFile = fs.existsSync(`${os.homedir()}/.local/bin/flatpak/${x[binField]}`) + return !binFile + } + return false + }) + flatpakApps.length && await $`mkdir -p "$HOME/.local/bin/flatpak"` + for (const app of flatpakApps) { + const binField = getPkgData('_bin', app, app.installType) + if (fs.existsSync(`${flatpakDir}/app/${app.installList[0]}`)) { + fs.writeFileSync(`${os.homedir()}/.local/bin/flatpak/${app[binField]}`, `#!/usr/bin/env bash\nflatpak run ${app.installList[0]} $*`) + await $`chmod +x '${os.homedir()}/.local/bin/flatpak/${app[binField]}'` + } else { + log(`Unable to create bin link to ${x.flatpak}`) + } + } + flatpakApps.length && log(`Finished creating Flatpak links in ~/.local/bin/flatpak`) +} + async function bundleInstall(brews, casks) { try { const lines = [] @@ -207,8 +234,10 @@ async function installPackages(pkgInstructions) { switch (key) { case 'ansible': promises.push(forEachSeries(combined[key].flatMap(x => x.installList.flatMap(i => $`${key} 127.0.0.1 -v${process.env.DEBUG && 'vv'} -e '{ ansible_connection: "local", ansible_become_user: "root", ansible_user: "${process.env.USER}", ansible_family: "${osId.charAt(0).toUpperCase() + osId.slice(1)}", install_homebrew: False }' -m include_role -a name=${i}`)))) + break case 'apk': promises.push($`sudo ${key} add ${combined[key].flatMap(x => x.installList).split(' ')}`) + break case 'appimage': promises.push(...combined[key].flatMap(x => x.installList.flatMap(i => { if (x.substring(0, 4) === 'http') { @@ -219,8 +248,10 @@ async function installPackages(pkgInstructions) { return $`zap install --select-first -q ${i}` } }))) + break case 'apt': promises.push($`DEBIAN_FRONTEND=noninteractive sudo apt-get -o DPkg::Options::=--force-confdef install -y ${combined[key].flatMap(x => x.installList).split(' ')}`) + break case 'basher': case 'baulk': case 'cargo': @@ -237,11 +268,13 @@ async function installPackages(pkgInstructions) { case 'binary': // TODO promises.push(...combined[key].flatMap(x => x.installList.flatMap(i => $`TMP="$(mktemp)" && curl -sSL ${i} > "$TMP" && sudo mv "$TMP" /usr/local/src/${x._bin} && chmod +x /usr/local/src/${x._bin}`))) + break case 'brew': case 'cask': // Handled above break case 'choco': promises.push($`${key} install -y ${combined[key].flatMap(x => x.installList).join(' ')}`) + break case 'dnf': case 'yum': case 'zypper': @@ -293,17 +326,30 @@ async function installPackages(pkgInstructions) { const installs = await Promise.allSettled(promises) log(`All of the installations have finished`) console.log('Installs:', installs) + await postInstall(combined) +} + +async function postInstall(combined) { + log(`Running post-install routine`) + const promises = [] + Object.keys(combined).includes('flatpak') && promises.push(createFlatpakLinks()) + const postInstalls = await Promise.allSettled(promises) + console.log('Post installs:', postInstalls) } async function acquireManagerList(type, command) { - if (fs.existsSync(`${cacheDir}/${type}`)) { - setTimeout(() => { + if (which.sync(type, { nothrow: true })) { + if (fs.existsSync(`${cacheDir}/${type}`)) { + setTimeout(() => { + require('child_process').execSync(`${command} > ${cacheDir}/${type}`) + }, 0) + } else { require('child_process').execSync(`${command} > ${cacheDir}/${type}`) - }, 0) + } + return fs.readFileSync(`${cacheDir}/${type}`).toString().split('\n') } else { - require('child_process').execSync(`${command} > ${cacheDir}/${type}`) + return [] } - return fs.readFileSync(`${cacheDir}/${type}`).toString().split('\n') } function pkgMap(pkgDefs) { @@ -349,21 +395,34 @@ async function main() { installOrder = initData[1].installerPreference log(`Populating lists of pre-installed packages`) const lists = [ + acquireManagerList('apt', `dpkg -l`), acquireManagerList('brew', `brew list -1`), acquireManagerList('cargo', `cargo install --list | awk '/^[[:alnum:]]/ {print $1}'`), + acquireManagerList('dnf', `rpm -qa`), + acquireManagerList('flatpak', `flatpak --columns=app list`), acquireManagerList('gem', `gem list | awk '{print $1}'`), acquireManagerList('npm', `volta list --format plain | awk '{print $2}' | sed 's/@.*//'`), + acquireManagerList('pacman', `pacman -Qs`), acquireManagerList('pip3', `pip3 list | awk '{print $1}'`), - acquireManagerList('pipx', `pipx list --short | awk '{print $1}'`) + acquireManagerList('pipx', `pipx list --short | awk '{print $1}'`), + acquireManagerList('snap', `snap list`), + acquireManagerList('zap', `zap list`) ] const managerLists = { - brew: lists[0], - cargo: lists[1], - cask: lists[0], - gem: lists[2], - npm: lists[3], - pip3: lists[4], - pipx: lists[5] + appimage: lists[6], + apt: lists[0], + brew: lists[1], + cargo: lists[2], + cask: lists[1], + dnf: lists[3], + flatpak: lists[4], + gem: lists[5], + npm: lists[6], + pacman: lists[7], + pip3: lists[8], + pipx: lists[9], + snap: lists[10], + zap: lists[11] } log(`Acquiring installation keys`) const installKeys = Object.keys(pkgs) diff --git a/home/dot_local/bin/executable_update-system b/home/dot_local/bin/executable_update-system new file mode 100644 index 00000000..54ea1a37 --- /dev/null +++ b/home/dot_local/bin/executable_update-system @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +updateApk() { + if command -v apk > /dev/null; then + logg info 'Running sudo apk update' && sudo apk update || logg error 'Failed to run sudo apk update' + fi +} + +updateAptGet() { + if command -v apt-get > /dev/null; then + logg info 'Running sudo apt-get update' && sudo apt-get update || logg error 'Failed to run sudo apt-get update' + logg info 'Running sudo apt-get upgrade -y' && sudo apt-get upgrade -y || logg error 'Failed to run sudo apt-get upgrade -y' + fi +} + +updateBrew() { + logg info 'Running brew update' && brew update || logg error 'Failed to run brew update' + logg info 'Running brew upgrade' && brew upgrade || logg error 'Failed to run brew upgrade' + if [ -d /Applications ] && [ -d /System ]; then + ### macOS + logg info 'Running brew upgrade --cask' && brew upgrade --cask || logg error 'Failed to run brew upgrade --cask' + fi +} + +updateCrew() { + if command -v crew > /dev/null; then + logg info 'Running crew update' && crew update || logg error 'Failed to run crew update' + fi +} + +updateDnf() { + if command -v dnf > /dev/null; then + logg info 'Running sudo dnf update -y' && sudo dnf update -y || logg error 'Failed to run sudo dnf update -y' + fi +} + +updateFlatpak() { + if command -v flatpak > /dev/null; then + logg info 'Running sudo flatpak update -y' && sudo flatpak update -y || logg error 'Failed to run sudo flatpak update -y' + fi +} + +updateNix() { + if command -v nix-channel > /dev/null; then + logg info 'Running nix-channel --update' && nix-channel --update || logg error 'Failed to run nix-channel --update' + fi +} + +updatePacman() { + if command -v pacman > /dev/null; then + logg info 'Running sudo pacman -Syu' && sudo pacman -Syu || logg error 'Failed to run sudo pacman -Syu' + fi +} + +updatePort() { + if command -v port > /dev/null; then + logg info 'Running sudo port sync' && sudo port sync || logg error 'Failed to run sudo port sync' + fi +} + +updateSnap() { + if command -v snap > /dev/null; then + logg info 'Running sudo snap refresh' && sudo snap refresh || logg error 'Failed to run sudo snap refresh' + fi +} + +updateZypper() { + if command -v zypper > /dev/null; then + logg info 'Running sudo zypper update' && sudo zypper update || logg error 'Failed to run sudo zypper update' + fi +} + +if [ -n "$DEBUG" ] || [ -n "$DEBUG_MODE" ]; then + logg info 'The DEBUG or DEBUG_MODE environment variable is set so updates will be run synchronously' + updateApk + updateAptGet + updateBrew + updateCrew + updateDnf + updateFlatpak + updateNix + updatePacman + updatePort + updateSnap + updateZypper +else + updateApk & + updateAptGet & + updateBrew & + updateCrew & + updateDnf & + updateFlatpak & + updateNix & + updatePacman & + updatePort & + updateSnap & + updateZypper & + wait +fi + +logg success 'Finished running update-system' diff --git a/home/dot_zshrc b/home/dot_zshrc index 11e7b0d2..4e170037 100644 --- a/home/dot_zshrc +++ b/home/dot_zshrc @@ -470,13 +470,14 @@ fi ! command -v rtx > /dev/null || eval "$(rtx activate zsh)" ### SDKMan -if command -v brew > /dev/null && command -v sdkman-cli > /dev/null; then - export SDKMAN_DIR="$(brew --prefix sdkman-cli)/libexec" - . "$SDKMAN_DIR/bin/sdkman-init.sh" -elif [ -f "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then - export SDKMAN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/sdkman" - . "$SDKMAN_DIR/bin/sdkman-init.sh" -fi +### Using mise instead for Java handling +# if command -v brew > /dev/null && command -v sdkman-cli > /dev/null; then +# export SDKMAN_DIR="$(brew --prefix sdkman-cli)/libexec" +# . "$SDKMAN_DIR/bin/sdkman-init.sh" +# elif [ -f "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then +# export SDKMAN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/sdkman" +# . "$SDKMAN_DIR/bin/sdkman-init.sh" +# fi ### Sheldon export SHELDON_CONFIG_FILE="${SHELDON_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/sheldon}/plugins.zsh.toml" diff --git a/scripts/src/provision.sh.tmpl b/scripts/src/provision.sh.tmpl index 37460e7c..00f8f654 100644 --- a/scripts/src/provision.sh.tmpl +++ b/scripts/src/provision.sh.tmpl @@ -433,8 +433,8 @@ provisionLogic() { logg info "Handling Qubes dom0 logic (if applicable)" && handleQubesDom0 logg info "Handling pre-provision logic" && initChezmoiAndPrompt logg info "Running the Chezmoi provisioning" && runChezmoi - logg info "Ensuring temporary passwordless sudo is removed" && removePasswordlessSudo logg info "Determing whether or not reboot" && handleRequiredReboot + logg info "Ensuring temporary passwordless sudo is removed" && removePasswordlessSudo logg info "Handling post-provision logic" && postProvision } provisionLogic diff --git a/software.yml b/software.yml index 12bc65c5..427ceda1 100644 --- a/software.yml +++ b/software.yml @@ -1174,11 +1174,6 @@ softwarePackages: _github: https://github.com/cupcakearmy/autorestic/ _home: https://autorestic.vercel.app/ _name: Autorestic - _post: | - #!/usr/bin/env bash - sudo mkdir -p /var/local/backups/apps - sudo mkdir -p /var/local/backups/home - sudo mkdir -p /var/local/backups/docker ansible: professormanhattan.autorestic brew: autorestic awscli: diff --git a/system/etc/bash.bashrc b/system/etc/bash.bashrc new file mode 100644 index 00000000..37ad97f5 --- /dev/null +++ b/system/etc/bash.bashrc @@ -0,0 +1,4 @@ +### Nix +if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then + . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' +fi diff --git a/system/etc/bashrc b/system/etc/bashrc new file mode 100644 index 00000000..08b16396 --- /dev/null +++ b/system/etc/bashrc @@ -0,0 +1,15 @@ +# System-wide .bashrc file for interactive bash(1) shells. +if [ -z "$PS1" ]; then + return +fi + +PS1='\h:\W \u\$ ' +# Make bash check its window size after a process completes +shopt -s checkwinsize + +[ -r "/etc/bashrc_$TERM_PROGRAM" ] && . "/etc/bashrc_$TERM_PROGRAM" + +### Nix +if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then + . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' +fi diff --git a/system/etc/zshrc b/system/etc/zshrc new file mode 100644 index 00000000..fe2e736b --- /dev/null +++ b/system/etc/zshrc @@ -0,0 +1,78 @@ +# System-wide profile for interactive zsh(1) shells. + +# Setup user specific overrides for this in ~/.zshrc. See zshbuiltins(1) +# and zshoptions(1) for more details. + +# Correctly display UTF-8 with combining characters. +if [[ "$(locale LC_CTYPE)" == "UTF-8" ]]; then + setopt COMBINING_CHARS +fi + +# Disable the log builtin, so we don't conflict with /usr/bin/log +disable log + +# Save command history +HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history +HISTSIZE=2000 +SAVEHIST=1000 + +# Beep on error +setopt BEEP + +# Use keycodes (generated via zkbd) if present, otherwise fallback on +# values from terminfo +if [[ -r ${ZDOTDIR:-$HOME}/.zkbd/${TERM}-${VENDOR} ]] ; then + source ${ZDOTDIR:-$HOME}/.zkbd/${TERM}-${VENDOR} +else + typeset -g -A key + + [[ -n "$terminfo[kf1]" ]] && key[F1]=$terminfo[kf1] + [[ -n "$terminfo[kf2]" ]] && key[F2]=$terminfo[kf2] + [[ -n "$terminfo[kf3]" ]] && key[F3]=$terminfo[kf3] + [[ -n "$terminfo[kf4]" ]] && key[F4]=$terminfo[kf4] + [[ -n "$terminfo[kf5]" ]] && key[F5]=$terminfo[kf5] + [[ -n "$terminfo[kf6]" ]] && key[F6]=$terminfo[kf6] + [[ -n "$terminfo[kf7]" ]] && key[F7]=$terminfo[kf7] + [[ -n "$terminfo[kf8]" ]] && key[F8]=$terminfo[kf8] + [[ -n "$terminfo[kf9]" ]] && key[F9]=$terminfo[kf9] + [[ -n "$terminfo[kf10]" ]] && key[F10]=$terminfo[kf10] + [[ -n "$terminfo[kf11]" ]] && key[F11]=$terminfo[kf11] + [[ -n "$terminfo[kf12]" ]] && key[F12]=$terminfo[kf12] + [[ -n "$terminfo[kf13]" ]] && key[F13]=$terminfo[kf13] + [[ -n "$terminfo[kf14]" ]] && key[F14]=$terminfo[kf14] + [[ -n "$terminfo[kf15]" ]] && key[F15]=$terminfo[kf15] + [[ -n "$terminfo[kf16]" ]] && key[F16]=$terminfo[kf16] + [[ -n "$terminfo[kf17]" ]] && key[F17]=$terminfo[kf17] + [[ -n "$terminfo[kf18]" ]] && key[F18]=$terminfo[kf18] + [[ -n "$terminfo[kf19]" ]] && key[F19]=$terminfo[kf19] + [[ -n "$terminfo[kf20]" ]] && key[F20]=$terminfo[kf20] + [[ -n "$terminfo[kbs]" ]] && key[Backspace]=$terminfo[kbs] + [[ -n "$terminfo[kich1]" ]] && key[Insert]=$terminfo[kich1] + [[ -n "$terminfo[kdch1]" ]] && key[Delete]=$terminfo[kdch1] + [[ -n "$terminfo[khome]" ]] && key[Home]=$terminfo[khome] + [[ -n "$terminfo[kend]" ]] && key[End]=$terminfo[kend] + [[ -n "$terminfo[kpp]" ]] && key[PageUp]=$terminfo[kpp] + [[ -n "$terminfo[knp]" ]] && key[PageDown]=$terminfo[knp] + [[ -n "$terminfo[kcuu1]" ]] && key[Up]=$terminfo[kcuu1] + [[ -n "$terminfo[kcub1]" ]] && key[Left]=$terminfo[kcub1] + [[ -n "$terminfo[kcud1]" ]] && key[Down]=$terminfo[kcud1] + [[ -n "$terminfo[kcuf1]" ]] && key[Right]=$terminfo[kcuf1] +fi + +# Default key bindings +[[ -n ${key[Delete]} ]] && bindkey "${key[Delete]}" delete-char +[[ -n ${key[Home]} ]] && bindkey "${key[Home]}" beginning-of-line +[[ -n ${key[End]} ]] && bindkey "${key[End]}" end-of-line +[[ -n ${key[Up]} ]] && bindkey "${key[Up]}" up-line-or-search +[[ -n ${key[Down]} ]] && bindkey "${key[Down]}" down-line-or-search + +# Default prompt +PS1="%n@%m %1~ %# " + +# Useful support for interacting with Terminal.app or other terminal programs +[ -r "/etc/zshrc_$TERM_PROGRAM" ] && . "/etc/zshrc_$TERM_PROGRAM" + +### Nix +if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then + . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' +fi