On an Omarchy laptop bootstrap left two of almost everything: VS Code from both pacman and the tarball (two menu entries, one shared ~/.config/Code profile), and kubectl/helm/k9s/kubectx/yq/jq/node/ansible in ~/.local/bin behind mise and pacman copies that always won on PATH. The zsh helpers (op*, grmb, secrets.env) also never reached the bash login shell. - lib/fetch.sh: add `shadowed`, true when another copy of a tool comes earlier on PATH than ~/.local/bin. Always false where zshrc puts ~/.local/bin first, so VM installs are unchanged. - bin/, node/, ansible/: skip tools that are shadowed. - vscode/: defer to any system `code`, and remove a tarball copy this module installed earlier. - bash/: new module; bash port of the zshrc helpers, sourced from ~/.bashrc via a marked block. No-ops without ~/.bashrc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
285 lines
10 KiB
Bash
Executable File
285 lines
10 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
# bin/install.sh — install userland devops CLIs into ~/.local/bin.
|
|
#
|
|
# Every tool is a self-contained release binary downloaded from upstream and
|
|
# dropped into ~/.local/bin (no sudo, nothing outside $HOME), so it persists
|
|
# across Kasm redeploys. Idempotent: each tool records its version under
|
|
# ~/.local/bin/.versions and is skipped when the pinned version is already
|
|
# installed. To add a tool, pin a version, add an install_<name> function,
|
|
# and call it at the bottom.
|
|
#
|
|
# Ansible is not a single binary; it is handled separately in ansible/.
|
|
set -eu
|
|
|
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
REPO_ROOT="$(dirname -- "$SCRIPT_DIR")"
|
|
|
|
# shellcheck source=../lib/fetch.sh
|
|
. "$REPO_ROOT/lib/fetch.sh"
|
|
|
|
ARCH="$(fetch_arch)" # x86_64 / aarch64
|
|
DEB="$(fetch_arch_deb)" # amd64 / arm64
|
|
|
|
# docker_full_available — true only if a non-rootless (full) Docker daemon is
|
|
# up and reachable. kind needs full Docker: we could not get it working against
|
|
# rootless Docker on Kasm, so it is only worth installing where a rootful
|
|
# daemon answers. SecurityOptions lists name=rootless under rootless mode.
|
|
docker_full_available() {
|
|
command -v docker >/dev/null 2>&1 || return 1
|
|
_secopts="$(docker info -f '{{println .SecurityOptions}}' 2>/dev/null)" || return 1
|
|
[ -n "$_secopts" ] || return 1
|
|
! printf '%s' "$_secopts" | grep -q 'name=rootless'
|
|
}
|
|
|
|
# jq — command-line JSON processor (single static binary).
|
|
install_jq() {
|
|
ver=1.8.2
|
|
have_version jq "$ver" && { fsay "jq $ver already installed."; return 0; }
|
|
install_raw_bin \
|
|
"https://github.com/jqlang/jq/releases/download/jq-$ver/jq-linux-$DEB" jq
|
|
mark_version jq "$ver"
|
|
}
|
|
|
|
# kubectl — Kubernetes CLI (single static binary from dl.k8s.io).
|
|
install_kubectl() {
|
|
ver=v1.36.2
|
|
have_version kubectl "$ver" && { fsay "kubectl $ver already installed."; return 0; }
|
|
install_raw_bin \
|
|
"https://dl.k8s.io/release/$ver/bin/linux/$DEB/kubectl" kubectl
|
|
mark_version kubectl "$ver"
|
|
}
|
|
|
|
# kubelogin — kubectl OIDC plugin, so omnictl-generated kubeconfigs can
|
|
# authenticate (they exec `kubectl oidc-login get-token`). Installed as
|
|
# kubectl-oidc_login, the name kubectl's plugin lookup expects.
|
|
install_oidc_login() {
|
|
ver=v1.36.4
|
|
have_version kubectl-oidc_login "$ver" && { fsay "kubectl-oidc_login $ver already installed."; return 0; }
|
|
install_zip_bin \
|
|
"https://github.com/int128/kubelogin/releases/download/$ver/kubelogin_linux_$DEB.zip" \
|
|
kubelogin kubectl-oidc_login
|
|
mark_version kubectl-oidc_login "$ver"
|
|
}
|
|
|
|
# helm — Kubernetes package manager (from get.helm.sh).
|
|
install_helm() {
|
|
ver=v4.2.2
|
|
have_version helm "$ver" && { fsay "helm $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://get.helm.sh/helm-$ver-linux-$DEB.tar.gz" \
|
|
"linux-$DEB/helm" helm
|
|
mark_version helm "$ver"
|
|
}
|
|
|
|
# k9s — terminal UI for Kubernetes clusters. Asset uses capital "Linux".
|
|
install_k9s() {
|
|
ver=v0.51.0
|
|
have_version k9s "$ver" && { fsay "k9s $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://github.com/derailed/k9s/releases/download/$ver/k9s_Linux_$DEB.tar.gz" \
|
|
k9s k9s
|
|
mark_version k9s "$ver"
|
|
}
|
|
|
|
# kind — run local Kubernetes clusters in Docker (single static binary). Gated
|
|
# on full (non-rootless) Docker at the call site: kind could not be made to
|
|
# work against rootless Docker on Kasm, so it is skipped there.
|
|
install_kind() {
|
|
ver=v0.32.0
|
|
have_version kind "$ver" && { fsay "kind $ver already installed."; return 0; }
|
|
install_raw_bin \
|
|
"https://github.com/kubernetes-sigs/kind/releases/download/$ver/kind-linux-$DEB" kind
|
|
mark_version kind "$ver"
|
|
}
|
|
|
|
# kubectx / kubens — fast context and namespace switching. Uses x86_64 token.
|
|
install_kubectx() {
|
|
ver=v0.11.0
|
|
have_version kubectx "$ver" && { fsay "kubectx $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://github.com/ahmetb/kubectx/releases/download/$ver/kubectx_${ver}_linux_$ARCH.tar.gz" \
|
|
kubectx kubectx
|
|
mark_version kubectx "$ver"
|
|
}
|
|
install_kubens() {
|
|
ver=v0.11.0
|
|
have_version kubens "$ver" && { fsay "kubens $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://github.com/ahmetb/kubectx/releases/download/$ver/kubens_${ver}_linux_$ARCH.tar.gz" \
|
|
kubens kubens
|
|
mark_version kubens "$ver"
|
|
}
|
|
|
|
# stern — multi-pod log tailing. Release tag is vX but asset drops the v.
|
|
install_stern() {
|
|
ver=1.34.0
|
|
have_version stern "$ver" && { fsay "stern $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://github.com/stern/stern/releases/download/v$ver/stern_${ver}_linux_$DEB.tar.gz" \
|
|
stern stern
|
|
mark_version stern "$ver"
|
|
}
|
|
|
|
# yq — YAML/JSON/XML processor (single static binary).
|
|
install_yq() {
|
|
ver=v4.53.3
|
|
have_version yq "$ver" && { fsay "yq $ver already installed."; return 0; }
|
|
install_raw_bin \
|
|
"https://github.com/mikefarah/yq/releases/download/$ver/yq_linux_$DEB" yq
|
|
mark_version yq "$ver"
|
|
}
|
|
|
|
# OpenTofu — open-source Terraform. Asset drops the leading v.
|
|
install_tofu() {
|
|
ver=1.12.3
|
|
have_version tofu "$ver" && { fsay "tofu $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://github.com/opentofu/opentofu/releases/download/v$ver/tofu_${ver}_linux_$DEB.tar.gz" \
|
|
tofu tofu
|
|
mark_version tofu "$ver"
|
|
}
|
|
|
|
# op — 1Password CLI, used for secrets storage/retrieval (op read, op run,
|
|
# op inject). Single static binary shipped as a zip. Sign in with
|
|
# `op signin` or a service-account token (OP_SERVICE_ACCOUNT_TOKEN) —
|
|
# credentials are per-box and never live in this repo.
|
|
install_1p() {
|
|
ver=2.34.1
|
|
have_version op "$ver" && { fsay "op $ver already installed."; return 0; }
|
|
install_zip_bin \
|
|
"https://cache.agilebits.com/dist/1P/op2/pkg/v$ver/op_linux_${DEB}_v$ver.zip" \
|
|
op op
|
|
mark_version op "$ver"
|
|
}
|
|
|
|
# opencode — terminal AI coding agent. Points at the covert-gpt LiteLLM
|
|
# gateway via mcp-config/opencode.gateway.json (synced by mcp-config/).
|
|
# Release assets use x64/arm64 tokens rather than amd64/x86_64.
|
|
install_opencode() {
|
|
ver=v1.17.18
|
|
have_version opencode "$ver" && { fsay "opencode $ver already installed."; return 0; }
|
|
case "$DEB" in
|
|
amd64) _oc_arch=x64 ;;
|
|
*) _oc_arch=arm64 ;;
|
|
esac
|
|
install_tarball_bin \
|
|
"https://github.com/sst/opencode/releases/download/$ver/opencode-linux-$_oc_arch.tar.gz" \
|
|
opencode opencode
|
|
mark_version opencode "$ver"
|
|
}
|
|
|
|
# zarf — a DevSecOps tool for Offline and Airgapped Kubernetes (single static binary).
|
|
install_zarf() {
|
|
ver=v0.81.0
|
|
have_version zarf "$ver" && { fsay "zarf $ver already installed."; return 0; }
|
|
case "$DEB" in
|
|
amd64) _zarf_arch=amd64 ;;
|
|
*) _zarf_arch=arm64 ;;
|
|
esac
|
|
install_raw_bin \
|
|
"https://github.com/zarf-dev/zarf/releases/download/$ver/zarf_${ver}_Linux_$_zarf_arch" zarf
|
|
mark_version zarf "$ver"
|
|
}
|
|
|
|
# task — go-task, the Taskfile.yml task runner. Binary is at the archive root.
|
|
install_task() {
|
|
ver=v3.52.0
|
|
have_version task "$ver" && { fsay "task $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://github.com/go-task/task/releases/download/$ver/task_linux_$DEB.tar.gz" \
|
|
task task
|
|
mark_version task "$ver"
|
|
}
|
|
|
|
# packer — infrastructure as code build tool (single static binary).
|
|
install_packer() {
|
|
ver=1.15.4
|
|
have_version packer "$ver" && { fsay "packer $ver already installed."; return 0; }
|
|
case "$(fetch_arch)" in
|
|
x86_64) _packer_arch=amd64 ;;
|
|
aarch64) _packer_arch=arm64 ;;
|
|
*) fsay "packer: unsupported arch $(uname -m)"; return 1 ;;
|
|
esac
|
|
install_zip_bin \
|
|
"https://releases.hashicorp.com/packer/$ver/packer_${ver}_linux_$_packer_arch.zip" \
|
|
packer packer
|
|
mark_version packer "$ver"
|
|
}
|
|
|
|
# terragrunt — thin wrapper around tofu/terraform (single static binary).
|
|
install_terragrunt() {
|
|
ver=v1.1.0
|
|
have_version terragrunt "$ver" && { fsay "terragrunt $ver already installed."; return 0; }
|
|
install_raw_bin \
|
|
"https://github.com/gruntwork-io/terragrunt/releases/download/$ver/terragrunt_linux_$DEB" \
|
|
terragrunt
|
|
mark_version terragrunt "$ver"
|
|
}
|
|
|
|
# coder — CLI for Coder workspaces (coder login, coder ssh, coder config-ssh).
|
|
# Release tag is vX but asset drops the v; binary is at the archive root.
|
|
install_coder() {
|
|
ver=2.36.5
|
|
have_version coder "$ver" && { fsay "coder $ver already installed."; return 0; }
|
|
install_tarball_bin \
|
|
"https://github.com/coder/coder/releases/download/v$ver/coder_${ver}_linux_$DEB.tar.gz" \
|
|
coder coder
|
|
mark_version coder "$ver"
|
|
}
|
|
|
|
# try <install_fn> — run one tool's installer; on failure, warn and keep going.
|
|
#
|
|
# WHY: these installs used to be bare calls, so under `set -eu` the FIRST
|
|
# failure killed this script -- and its non-zero exit then aborted bootstrap.sh
|
|
# itself, so every later module (node/, ansible/) silently never ran. A single
|
|
# tool whose release asset moved could leave a box with no opencode, no npx and
|
|
# no python3. One optional CLI is not worth the whole bootstrap.
|
|
#
|
|
# The subshell re-enables `set -e` inside itself (it is disabled for a command
|
|
# being tested by `if`), so a failed download still aborts that tool's function
|
|
# before mark_version stamps a version that was never installed.
|
|
#
|
|
# try <install_fn> <binary> also skips the tool when another <binary> already
|
|
# wins on PATH (see `shadowed` in lib/fetch.sh): a copy here would never run.
|
|
FAILED=""
|
|
try() {
|
|
if _winner="$(shadowed "$2")"; then
|
|
fsay "$2: $_winner comes first on PATH; skipping (a copy in $BIN_DIR would never run)."
|
|
return 0
|
|
fi
|
|
if ( set -e; "$1" ); then return 0; fi
|
|
FAILED="$FAILED ${1#install_}"
|
|
fsay "WARNING: ${1#install_} failed to install; continuing."
|
|
}
|
|
|
|
try install_jq jq
|
|
try install_kubectl kubectl
|
|
try install_oidc_login kubectl-oidc_login
|
|
try install_helm helm
|
|
try install_k9s k9s
|
|
if docker_full_available; then
|
|
try install_kind kind
|
|
else
|
|
fsay "Skipping kind: full (non-rootless) Docker not available."
|
|
fi
|
|
try install_kubectx kubectx
|
|
try install_kubens kubens
|
|
try install_stern stern
|
|
try install_yq yq
|
|
try install_tofu tofu
|
|
try install_task task
|
|
try install_terragrunt terragrunt
|
|
try install_1p op
|
|
try install_opencode opencode
|
|
try install_zarf zarf
|
|
try install_packer packer
|
|
try install_coder coder
|
|
|
|
if [ -n "$FAILED" ]; then
|
|
fsay ""
|
|
fsay "WARNING: these tools did NOT install:$FAILED"
|
|
fsay "Everything else is in $BIN_DIR. Fix the cause and re-run bin/install.sh."
|
|
else
|
|
fsay "Done. Tools in $BIN_DIR (added to PATH by zsh/zshrc)."
|
|
fi
|