From 7bf69285681c2f1ecee692751a748e54032001ad Mon Sep 17 00:00:00 2001 From: Brian Zalewski <59970525+ProfessorManhattan@users.noreply.github.com> Date: Tue, 16 Jan 2024 04:43:57 +0000 Subject: [PATCH] Latest --- docs/TODO.md | 3 + home/.chezmoidata.yaml | 8 + home/dot_local/bin/executable_installx | 266 +++++++++++++++++-------- software.yml | 67 +++++-- 4 files changed, 242 insertions(+), 102 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 6c5e1323..e3be6658 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,15 +1,18 @@ https://github.com/harababurel/gcsf +[text](https://github.com/gitbito/CLI) * Move age decryption higher * Add ~/.local/share/sounds was symlink to {{ .host.home }}/.local/share/betelgeuse/share/sounds xattr -d com.apple.quarantine rclone Create issue about setting up completions - https://github.com/rsteube/lazycomplete pw="$(osascript -e 'Tell application "System Events" to display dialog "Password:" default answer "" with hidden answer' -e 'text returned of result' 2>/dev/null)" && echo "$pw" https://github.com/Shougo/ddc.vim +[text](https://instill.tech/chill/models) https://github.com/harababurel/gcsf https://github.com/awslabs/mountpoint-s3 / https://github.com/s3fs-fuse/s3fs-fuse https://github.com/superfly/litefs https://github.com/Qihoo360/QConf https://github.com/ossec/ossec-hids +[text](https://github.com/invoke-ai/InvokeAI) https://github.com/search?q=system&type=repositories&s=stars&o=desc&p=59 - https://github.com/nats-io/nats-server - [Title](https://github.com/albfan/miraclecast) diff --git a/home/.chezmoidata.yaml b/home/.chezmoidata.yaml index 3e7bd99c..74baac73 100644 --- a/home/.chezmoidata.yaml +++ b/home/.chezmoidata.yaml @@ -167,6 +167,12 @@ removeLinuxShortcuts: - scvim.desktop theme: Betelgeuse softwareGroups: + AI: &AI + - aider + - ai-shell + - aifiles + - talksheet + - tgpt AI-Desktop: &AI-Desktop - chatgpt-nofwl - chatgpt-menubar @@ -347,6 +353,7 @@ softwareGroups: - zenity CLI-Extras: &CLI-Extras - ack + - airdrop-cli - axel - bin - bitly @@ -1355,6 +1362,7 @@ softwareGroups: - warp _Full: &_Full - *_Standard + - *AI - *Android - *Ansible - *Backup diff --git a/home/dot_local/bin/executable_installx b/home/dot_local/bin/executable_installx index ef5f98d6..f15d8adb 100644 --- a/home/dot_local/bin/executable_installx +++ b/home/dot_local/bin/executable_installx @@ -6,13 +6,29 @@ import osInfo from 'linux-os-info' let installOrder, osArch, osId, osType, pkgs, sysType const cacheDir = os.homedir() + '/.cache/installx' +function log(message) { + console.log(`${chalk.cyanBright('instx->')} ${message}`) +} + async function getOsInfo() { return osInfo({ mode: 'sync' }) } +function execPromise(command) { + return new Promise(function (resolve, reject) { + require('child_process').exec(command, (error, stdout, stderr) => { + if (error) { + reject(error) + return + } + resolve(stdout.trim()) + }) + }) +} + async function runSilentCommand(command) { require('child_process').execSync(`${command}`, { stdio: 'inherit', shell: true }) - } +} async function runScript(key, script) { fs.writeFileSync(`${cacheDir}/${key}`, script) @@ -112,16 +128,55 @@ function expandDeps(keys) { return [...keys] } +async function createCaskLinks() { + const caskApps = pkgMap(pkgs) + .filter(x => { + // Filter out macOS apps that already have a _app installed + if (x.installType === 'cask' || (osId === 'darwin' && x._app)) { + const appField = getPkgData('_app', x, x.installType) + const binField = getPkgData('_bin', x, x.installType) + const sysDir = fs.existsSync(`/Applications/${x[appField]}`) + const homeDir = fs.existsSync(`${os.homedir()}/Applications/${x[appField]}`) + const binFile = fs.existsSync(`${os.homedir()}/.local/bin/cask/${x[binField]}`) + return (sysDir || homeDir) && !binFile + } + return false + }) + caskApps.length && await $`mkdir -p "$HOME/.local/bin/cask"` + 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]}" $*`) + } else { + log(`Unable to create bin link to ${x[appField]}`) + } + } + caskApps.length && log(`Finished creating Homebrew cask links in ~/.local/bin/cask`) +} + async function bundleInstall(brews, casks) { - const lines = [] - for (const cask of casks) { - lines.push(`cask "${cask.cask}"`) + try { + const lines = [] + log(`Adding following casks to Brewfile for installation: ${casks.join(' ')}`) + for (const cask of casks) { + lines.push(`cask "${cask}"`) + } + log(`Adding following brews to Brewfile for installation: ${casks.join(' ')}`) + for (const brew of brews) { + lines.push(`brew "${brew}"`) + } + log(`Creating Brewfile to install from`) + fs.writeFileSync('Brewfile', lines.join('\n')) + log(`Installing packages via brew bundle`) + await $`brew bundle --file Brewfile` + log(`Finished installing via Brewfile`) + await createCaskLinks() + } catch (e) { + log(`Error occurred while installing via Brewfile`) } - for (const brew of brews) { - lines.push(`brew "${brew.brew}"`) - } - fs.writeFileSync('Brewfile', lines.join('\n')) - await $`brew bundle --file Brewfile` } async function forEachSeries(iterable) { @@ -133,17 +188,21 @@ async function forEachSeries(iterable) { async function installPackages(pkgInstructions) { const combined = {} const promises = [] + log(`Populating install order lists`) for (const option of installOrder[sysType]) { + console.log(installOrder[sysType]) const instructions = pkgInstructions.filter(x => x.installType === option) if (instructions.length) { combined[option] = instructions } } + log(`Running Homebrew installation via Brewfile`) if ((combined.brew && combined.brew.length) || (combined.cask && combined.cask.length)) { - promises.push(bundleInstall(combined.brew ? combined.brew : [], combined.cask ? combined.cask : [])) + promises.push(bundleInstall(combined.brew ? combined.brew.flatMap(x => x.installList.flatMap(i => i)) : [], combined.cask ? combined.cask.flatMap(x => x.installList.flatMap(i => i)) : [])) } for (const key of Object.keys(combined)) { - switch(key) { + log(`Install orders for ${key}: ${combined[key].flatMap(i => i.installList).join(' ')}`) + 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}`)))) case 'apk': @@ -172,67 +231,69 @@ async function installPackages(pkgInstructions) { case 'scoop': // Maybe needs forEachSeries case 'winget': // Maybe needs forEachSeries promises.push(...combined[key].flatMap(x => x.installList.flatMap(i => $`${key} install ${i}`))) - break; + break 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}`))) case 'brew': case 'cask': // Handled above - break; + break case 'choco': - promises.push($`${key} install -y ${combined[key].flatMap(x => x.installList).split(' ')}`) + promises.push($`${key} install -y ${combined[key].flatMap(x => x.installList).join(' ')}`) case 'dnf': case 'yum': case 'zypper': - promises.push($`sudo ${key} install -y ${combined[key].flatMap(x => x.installList).split(' ')}`) - break; + promises.push($`sudo ${key} install -y ${combined[key].flatMap(x => x.installList).join(' ')}`) + break case 'emerge': case 'pkg_add': - promises.push($`sudo ${key} ${combined[key].flatMap(x => x.installList).split(' ')}`) - break; + promises.push($`sudo ${key} ${combined[key].flatMap(x => x.installList).join(' ')}`) + break case 'eopkg': case 'pkg-freebsd': case 'pkg-termux': case 'pkgin': case 'port': case 'snap': // TODO - snap testing.. combine with snap-classic and add appropriate logic - promises.push($`sudo ${key === 'pkg-freebsd' || key === 'pkg-termux' ? 'pkg' : key} install ${combined[key].flatMap(x => x.installList).split(' ')}`) - break; + promises.push($`sudo ${key === 'pkg-freebsd' || key === 'pkg-termux' ? 'pkg' : key} install ${combined[key].flatMap(x => x.installList).join(' ')}`) + break case 'flatpak': promises.push(forEachSeries(combined[key].flatMap(x => x.installList.flatMap(i => $`sudo ${key} install -y flathub ${i}`)))) - break; + break case 'github': // TODO - break; + break case 'nix-env': // TODO case 'nix-pkg': // TODO case 'nix-shell': // TODO - break; + break case 'pacman': - promises.push($`sudo ${key} -Sy --noconfirm --needed ${combined[key].flatMap(x => x.installList).split(' ')}`) - break; + promises.push($`sudo ${key} -Sy --noconfirm --needed ${combined[key].flatMap(x => x.installList).join(' ')}`) + break case 'pkg-darwin': - break; + break case 'sbopkg': // TODO - break; + break case 'script': - promises.push(...combined[key].flatMap(x => x.installList.map(i => $`${i}`))) - break; + promises.push(...combined[key].flatMap(x => x.installList.join(i => $`${i}`))) + break case 'snap-classic': - promises.push($`sudo snap install --classic ${combined[key].flatMap(x => x.installList).split(' ')}`) - break; + promises.push($`sudo snap install --classic ${combined[key].flatMap(x => x.installList).join(' ')}`) + break case 'whalebrew': // TODO - break; + break case 'xbps': - promises.push($`sudo xbps-install -S ${combined[key].flatMap(x => x.installList).split(' ')}`) - break; + promises.push($`sudo xbps-install -S ${combined[key].flatMap(x => x.installList).join(' ')}`) + break case 'yay': - promises.push($`yay -Sy --noconfirm --needed ${combined[key].flatMap(x => x.installList).split(' ')}`) - break; + promises.push($`yay -Sy --noconfirm --needed ${combined[key].flatMap(x => x.installList).join(' ')}`) + break default: console.log(`Unable to find install key instructions for ${key}`) } } - await Promise.all(promises) + const installs = await Promise.allSettled(promises) + log(`All of the installations have finished`) + console.log('Installs:', installs) } async function acquireManagerList(type, command) { @@ -246,39 +307,8 @@ async function acquireManagerList(type, command) { return fs.readFileSync(`${cacheDir}/${type}`).toString().split('\n') } - -async function main() { - await $`mkdir -p ${cacheDir}` - const initData = await Promise.all([ - getOsInfo(), - getSoftwareDefinitions(), - getSystemType() - ]) - osArch = initData[0].arch - osId = process.platform === 'win32' ? 'win32' : (process.platform === 'linux' ? initData[0].id : process.platform) - osType = process.platform === 'win32' ? 'windows' : process.platform - pkgs = initData[1].softwarePackages - sysType = initData[2] - installOrder = initData[1].installerPreference - const lists = [ - acquireManagerList('brew', `brew list -1`), - acquireManagerList('cargo', `cargo install --list | awk '/^[[:alnum:]]/ {print $1}'`), - acquireManagerList('gem', `gem list | awk '{print $1}'`), - acquireManagerList('npm', `volta list --format plain | awk '{print $2}' | sed 's/@.*//'`), - acquireManagerList('pip3', `pip3 list | awk '{print $1}'`), - acquireManagerList('pipx', `pipx list --short | awk '{print $1}'`) - ] - const managerLists = { - brew: lists[0], - cargo: lists[1], - gem: lists[2], - npm: lists[3], - pip3: lists[4], - pipx: lists[5] - } - const installKeys = Object.keys(pkgs) - .filter(i => expandDeps(argv._).includes(i)) - const installData = installKeys +function pkgMap(pkgDefs) { + return pkgDefs .map(i => { for (const pref of installOrder[sysType]) { const installKey = getPkgData(pref, pkgs[i], false) @@ -301,40 +331,100 @@ async function main() { } }) .filter(x => x.installKey) +} + + +async function main() { + await $`mkdir -p ${cacheDir}` + log(`Acquiring software definitions and system information`) + const initData = await Promise.all([ + getOsInfo(), + getSoftwareDefinitions(), + getSystemType() + ]) + osArch = initData[0].arch + osId = process.platform === 'win32' ? 'win32' : (process.platform === 'linux' ? initData[0].id : process.platform) + osType = process.platform === 'win32' ? 'windows' : process.platform + pkgs = initData[1].softwarePackages + sysType = initData[2] + installOrder = initData[1].installerPreference + log(`Populating lists of pre-installed packages`) + const lists = [ + acquireManagerList('brew', `brew list -1`), + acquireManagerList('cargo', `cargo install --list | awk '/^[[:alnum:]]/ {print $1}'`), + acquireManagerList('gem', `gem list | awk '{print $1}'`), + acquireManagerList('npm', `volta list --format plain | awk '{print $2}' | sed 's/@.*//'`), + acquireManagerList('pip3', `pip3 list | awk '{print $1}'`), + acquireManagerList('pipx', `pipx list --short | awk '{print $1}'`) + ] + const managerLists = { + brew: lists[0], + cargo: lists[1], + cask: lists[0], + gem: lists[2], + npm: lists[3], + pip3: lists[4], + pipx: lists[5] + } + log(`Acquiring installation keys`) + const installKeys = Object.keys(pkgs) + .filter(i => expandDeps(argv._).includes(i)) + log(`Constructing installation data`) + const installData = pkgMap(installKeys) + log(`Filtering install instructions`) const installInstructions = installData .filter(x => { // Filter out packages already installed by by package managers - return Object.keys(managerLists).includes(x.installType) + return !Object.keys(managerLists).includes(x.installType) }) .filter(x => { // Filter out macOS apps that already have a _app installed - if (x.installType === 'cask') { + if (x.installType === 'cask' || (osId === 'darwin' && x._app)) { const appField = getPkgData('_app', x, x.installType) - if (fs.existsSync(`/Applications/${x[appField]}`) || fs.existsSync(`${os.homedir()}/Applications/${x[appField]}`)) { - return false + return !(fs.existsSync(`/Applications/${x[appField]}`) || fs.existsSync(`${os.homedir()}/Applications/${x[appField]}`)) + } + return true + }) + .filter(x => { + // Filter out packages that already have a bin in the PATH + const binField = getPkgData('_bin', x, x.installType) + const isArray = Array.isArray(x[binField]) + if (typeof x[binField] === 'string' || isArray) { + if (isArray) { + log(`_bin field for ${x.listKey} is an array so the first entry will be used to check`) + } + return !(which.sync(typeof x[binField] === 'string' ? x[binField] : x[binField][0], { nothrow: true })) + } + log(`Ignoring _bin check because the _bin field for ${x.listKey} is not a string or array`) + return true + }) + .filter(x => { + // Filter out packages that do not pass _when check + const whenField = getPkgData('_when', x, x.installType) + if (x[whenField]) { + if (typeof x[whenField] === 'string') { + try { + execSync(`${x[whenField]}`) + return false + } catch (e) { + return true + } + } else { + log(`typeof _when for ${x.listKey} must be a string`) } } return true }) - .filter(async x => { - // Filter out packages that already have a bin in the PATH - const binField = getPkgData('_bin', x, x.installType) - const binCheck = x[binField] && await which(x[binField], { nothrow: true }) - return binField ? binCheck : true - }) - .filter(async x => { - // Filter out packages that do not pass _when check - const whenField = getPkgData('_when', x, x.installType) - const whenCheck = x[whenField] && await $`${x[whenField]}`.exitCode == 0 - return whenField ? whenCheck : true - }) + console.log(installInstructions) + log(`Running installation routine`) await installPackages(installInstructions) - const postScripts = installData + log(`Running post-install scripts`) + /*const postScripts = installData .flatMap(x => { const postField = getPkgData('_post', x, x.installType) return (postField && runScript(x.listKey, x[postField])) || Promise.resolve() }) - await Promise.all(postScripts) + await Promise.all(postScripts)*/ } main() diff --git a/software.yml b/software.yml index e5e764c2..69a34de3 100644 --- a/software.yml +++ b/software.yml @@ -100,7 +100,6 @@ installerPreference: - script - ansible - binary - - _deps darwin: - whalebrew - cask @@ -117,7 +116,6 @@ installerPreference: - script - ansible - binary - - _deps dnf: - flatpak - snap @@ -134,7 +132,6 @@ installerPreference: - script - ansible - binary - - _deps freebsd: - pkg - go @@ -142,7 +139,6 @@ installerPreference: - npm - gem - script - - _deps pacman: - flatpak - snap @@ -160,7 +156,6 @@ installerPreference: - script - ansible - binary - - _deps ubuntu: - snap - flatpak @@ -177,7 +172,6 @@ installerPreference: - script - ansible - binary - - _deps windows: - choco - scoop @@ -190,7 +184,6 @@ installerPreference: - script - ansible - binary - - _deps zypper: - flatpak - snap @@ -206,7 +199,6 @@ installerPreference: - script - ansible - binary - - _deps softwarePackages: _kde: _deps: @@ -7583,7 +7575,7 @@ softwarePackages: _github: https://github.com/hschmidt/EnvPane _home: https://github.com/hschmidt/EnvPane _name: EnvPane - _when:script: '! test -d "$HOME/Library/PreferencePanes/EnvPane.prefPane" && ! test -d "~$HOME/Library/PreferencePanes/EnvPane.prefPane"' + _when:script: '! test -d "$HOME/Library/PreferencePanes/EnvPane.prefPane"' script:darwin: '(cd ~/Library/PreferencePanes && rm -rf EnvPane.prefPane && curl -sL https://github.com/hschmidt/EnvPane/releases/download/releases%2F0.8/EnvPane-0.8.tar.bz2 | tar -xjf -)' skhd: _bin: skhd @@ -12803,13 +12795,60 @@ softwarePackages: ansible:windows: professormanhattan.visualstudio cask: visual-studio choco: visualstudio2022community + aider: + _bin: aider + _github: https://github.com/paul-gauthier/aider + _name: Aider AI Chat + pipx: aider-chat + poppler: + brew: poppler + csvkit: + brew: csvkit + airdrop-cli: + _bin: airdrop + _github: https://github.com/vldmrkl/airdrop-cli + _name: Airdrop CLI + brew:darwin: vldmrkl/formulae/airdrop-cli + rusty: + _bin: rusty + _github: https://github.com/zahidkhawaja/rusty + _name: Rusty AI CLI + _todo: Get cargo crate link once this is resolved https://github.com/zahidkhawaja/rusty/issues/7 + aifiles: + _deps: + - pandoc + - exiftool + - poppler + - csvkit + _bin: aifiles + _github: https://github.com/jjuliano/aifiles + _name: AI Files + _todo: Implement this when XDG spec is supported so we can store configs in ~/.config + npm: aifiles + talksheet: + _bin: talksheet + _github: https://github.com/danthelion/talksheet + _name: Talksheet + pipx: talksheet + tgpt: + _bin: tgpt + _github: https://github.com/aandrew-me/tgpt + _name: Terminal GPT + yay: tgpt-bin + go: github.com/aandrew-me/tgpt/v2@latest + scoop: https://raw.githubusercontent.com/aandrew-me/tgpt/main/tgpt.json + ai-shell: + _bin: ai + _github: https://github.com/BuilderIO/ai-shell + _name: AI Shell + npm: '@builder.io/ai-shell' github-runner: _desc: '[GitHub Runner](https://docs.github.com/en/actions/hosting-your-own-runners) is a system that you deploy and manage to execute jobs from GitHub Actions on GitHub.com.' _docs: https://docs.github.com/en/actions/hosting-your-own-runners _github: https://github.com/actions/runner _home: https://docs.github.com/en/actions/hosting-your-own-runners _name: GitHub Runner - _when: '! test -d "$HOME/.local/github-runner"' + _when: '! test -d "${XDG_DATA_HOME:-$HOME/.local/share}/github-runner"' _post: | #!/usr/bin/env bash # @file GitHub Runner Registration @@ -12830,7 +12869,7 @@ softwarePackages: # # * [Secrets / Environment variables documentation](https://install.doctor/docs/customization/secrets) - GH_RUNNER_PATH="$HOME/.local/github-runner" + GH_RUNNER_PATH="${XDG_DATA_HOME:-$HOME/.local/share}/github-runner" ### Check if GitHub runner is installed if [ -f "$GH_RUNNER_PATH/config.sh" ]; then @@ -12890,9 +12929,9 @@ softwarePackages: ARCHITECTURE="$(uname -m | sed 's/86_//' | sed 's/v7l//')" OS_FAMILY="$(test -d /Applications && echo osx || echo linux)" curl -sSL "https://github.com/actions/runner/releases/download/$LATEST_VERSION/actions-runner-${OS_FAMILY}-${ARCHITECTURE}-${LATEST_VERSION:1}.tar.gz" > "/tmp/actions-runner-${OS_FAMILY}-${ARCHITECTURE}-${LATEST_VERSION:1}.tar.gz" - mkdir -p "$HOME/.local/github-runner" - tar xzf "/tmp/actions-runner-${OS_FAMILY}-${ARCHITECTURE}-${LATEST_VERSION:1}.tar.gz" -C "$HOME/.local/github-runner" - chown -Rf "$USER" "$HOME/.local/github-runner" + mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}/github-runner" + tar xzf "/tmp/actions-runner-${OS_FAMILY}-${ARCHITECTURE}-${LATEST_VERSION:1}.tar.gz" -C "${XDG_DATA_HOME:-$HOME/.local/share}/github-runner" + chown -Rf "$USER" "${XDG_DATA_HOME:-$HOME/.local/share}/github-runner" rm -f "/tmp/actions-runner-${OS_FAMILY}-${ARCHITECTURE}-${LATEST_VERSION:1}.tar.gz" vscodium: _bin: codium