638 lines
26 KiB
Bash
Executable File
638 lines
26 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Put kind, tilt and kubectl on a machine that has none of them.
|
|
#
|
|
# The single file companion to rigmini.sh, for the same reason: rig installs its
|
|
# toolchain from ctrl/deps.sh reading ctrl/versions.env, and neither of those is
|
|
# going to a fresh AWS WorkSpace. The pins live inline here instead.
|
|
#
|
|
# What it will not do, deliberately:
|
|
#
|
|
# * no sudo, no apt, no yum. It writes into $OUT_BIN (default ~/.local/bin)
|
|
# and, for compose only, a symlink under ~/.docker/cli-plugins — both in
|
|
# your own home. Everything needing root — installing Docker, joining the
|
|
# docker group, raising inotify limits — is REPORTED for you to decide on.
|
|
# That is what makes it safe to run on a machine that already works.
|
|
# * no unverified download. Every artifact is checked against a SHA256 taken
|
|
# from the publisher's own release list. A mismatch aborts.
|
|
# * no guessing at another architecture. See ARCHITECTURE below.
|
|
#
|
|
# Two tiers, because "install the toolchain" is not one decision:
|
|
#
|
|
# core kubectl, jq — talk to a cluster someone else runs. Nothing that
|
|
# creates one. The right answer on a managed or corporate machine.
|
|
# dev core plus kind, tilt and ctlptl — build clusters and hot-reload
|
|
# into them. The default, and what you want on a workspace of your own.
|
|
#
|
|
# Usage:
|
|
# rigdeps.sh detect report the host, change nothing
|
|
# rigdeps.sh list the pinned versions and where they come from
|
|
# rigdeps.sh install [core|dev] detect, download, verify, install, report
|
|
# rigdeps.sh fetch [core|dev] [--to DIR] download + verify only
|
|
# rigdeps.sh verify run what is installed and see if it works
|
|
set -euo pipefail
|
|
|
|
OUT_BIN="${OUT_BIN:-$HOME/.local/bin}"
|
|
|
|
# ── the pinned toolchain ───────────────────────────────────────────────────
|
|
#
|
|
# ARCHITECTURE. These checksums are the upstream-published SHA256 of the
|
|
# **linux/amd64** artifact and of nothing else. An arm64 WorkSpace bundle needs
|
|
# a different binary with a different checksum, and this script refuses rather
|
|
# than reusing these — a checksum that is merely plausible is worse than none,
|
|
# because it turns a verified download into a ceremony.
|
|
#
|
|
# To bump a version, or to add arm64: take the checksum from the release's own
|
|
# published list, never from a download you did.
|
|
#
|
|
# curl -sSL https://github.com/<org>/<repo>/releases/download/<tag>/checksums.txt
|
|
#
|
|
# kubectl publishes its own instead, at <KUBECTL_URL>.sha256.
|
|
|
|
KIND_VERSION=v0.32.0
|
|
KIND_SHA256=50030de23cf40a18505f20426f6a8506bedf13c6e509244bd1fa9463721b0f54
|
|
KIND_URL="https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64"
|
|
|
|
KUBECTL_VERSION=v1.36.3
|
|
KUBECTL_SHA256=ebbd080e7c2e275093b55915722043257eb24004363e20acb3c4d71919f88336
|
|
KUBECTL_URL="https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
|
|
|
|
TILT_VERSION=0.37.6
|
|
TILT_SHA256=e9672b8a18d43501f35dcfe98465969a7db0e436b36cf0c50c7e6f8d40de5fe6
|
|
TILT_URL="https://github.com/tilt-dev/tilt/releases/download/v${TILT_VERSION}/tilt.${TILT_VERSION}.linux.x86_64.tar.gz"
|
|
|
|
# ctlptl creates a kind cluster WITH a local registry wired in, which is what
|
|
# keeps images off docker.io — an unqualified image name resolves to
|
|
# docker.io/library/<name>, and there is nothing structural stopping a push there.
|
|
CTLPTL_VERSION=0.9.4
|
|
CTLPTL_SHA256=c63a1ec28e60bc3faf6becb76f53355c5cf5e0143dafdd27ad85db5584fa6b1e
|
|
CTLPTL_URL="https://github.com/tilt-dev/ctlptl/releases/download/v${CTLPTL_VERSION}/ctlptl.${CTLPTL_VERSION}.linux.x86_64.tar.gz"
|
|
|
|
# Upstream's static build. Debian's jq is linked against libjq/libonig, which is
|
|
# fine on Debian and not portable anywhere else.
|
|
JQ_VERSION=1.8.2
|
|
JQ_SHA256=b1c22172dd303f3be49e935aa56aa48a8b7a46e0bc838b4997d3bb451495870f
|
|
JQ_URL="https://github.com/jqlang/jq/releases/download/jq-${JQ_VERSION}/jq-linux-amd64"
|
|
|
|
# Distro docker packages ship the daemon and CLI but frequently not this, so
|
|
# `docker compose up` fails with "unknown command" on an otherwise working
|
|
# Docker. It is a CLI plugin: the binary is found by name in a plugin directory,
|
|
# which is why install_compose_plugin links it into ~/.docker/cli-plugins.
|
|
COMPOSE_VERSION=5.5.1
|
|
COMPOSE_SHA256=db1889184726840f75c4f9c001048430d4f25b3be3cb084d3ddd762bc0aed576
|
|
COMPOSE_URL="https://github.com/docker/compose/releases/download/v${COMPOSE_VERSION}/docker-compose-linux-x86_64"
|
|
|
|
CORE_TOOLS="kubectl jq"
|
|
DEV_TOOLS="kind tilt ctlptl docker-compose"
|
|
|
|
# No helm: every rig addon installs with `kubectl apply -f`, so nothing has ever
|
|
# invoked it. Add it the day something actually needs a chart.
|
|
|
|
# Collected as we go, printed by report_manual() at the very end. Anything that
|
|
# needs root or a decision lands here instead of being done.
|
|
MANUAL=()
|
|
|
|
# ── platform ───────────────────────────────────────────────────────────────
|
|
|
|
# Windows outside WSL — Git Bash, MSYS, Cygwin — looks close enough to work and
|
|
# then fails in a pile of confusing ways: no /proc, no docker socket, none of
|
|
# the tooling. Detectable, so name it instead.
|
|
require_linux() {
|
|
case "$(uname -s)" in
|
|
MINGW*|MSYS*|CYGWIN*)
|
|
cat >&2 <<'EOF'
|
|
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
|
|
|
If WSL is not installed yet, from an elevated PowerShell or Command Prompt:
|
|
|
|
wsl --install
|
|
|
|
That enables Windows features and needs a reboot, so it is not something this
|
|
script will do for you. Afterwards, open the Linux shell it installs and run
|
|
this from there.
|
|
EOF
|
|
exit 1 ;;
|
|
Linux) ;;
|
|
*) echo "$(uname -s) is not Linux. These are linux binaries; nothing here" >&2
|
|
echo "would run even if it downloaded." >&2
|
|
exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
arch() {
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) echo amd64 ;;
|
|
aarch64|arm64) echo arm64 ;;
|
|
*) uname -m ;;
|
|
esac
|
|
}
|
|
|
|
# The pins above are amd64. Rather than download something that cannot execute
|
|
# and let it fail as "cannot execute binary file: Exec format error", say so
|
|
# here and hand over the commands that produce the right checksums.
|
|
require_amd64() {
|
|
local a; a=$(arch)
|
|
[ "$a" = "amd64" ] && return 0
|
|
cat >&2 <<EOF
|
|
This machine is ${a} ($(uname -m)); every pin in this script is linux/amd64.
|
|
|
|
Nothing here would run, so it does not download. To make an ${a} version, the
|
|
URLs need the ${a} artifact and the checksums need to come from each project's
|
|
own published list — not from these values, and not from a download you did:
|
|
|
|
curl -sSL https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/checksums.txt
|
|
curl -sSL https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${a}/kubectl.sha256
|
|
curl -sSL https://github.com/tilt-dev/tilt/releases/download/v${TILT_VERSION}/checksums.txt
|
|
curl -sSL https://github.com/tilt-dev/ctlptl/releases/download/v${CTLPTL_VERSION}/checksums.txt
|
|
curl -sSL https://github.com/jqlang/jq/releases/download/jq-${JQ_VERSION}/sha256sum.txt
|
|
|
|
Edit the pinned block at the top of this file with what those print.
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
is_wsl() { grep -qi microsoft /proc/version 2>/dev/null; }
|
|
|
|
# ── the tools this script itself needs ─────────────────────────────────────
|
|
|
|
# A fresh minimal image may genuinely have neither curl nor wget. Find out once,
|
|
# up front, rather than half way through the first download.
|
|
DL=""
|
|
pick_downloader() {
|
|
if command -v curl >/dev/null 2>&1; then DL=curl
|
|
elif command -v wget >/dev/null 2>&1; then DL=wget
|
|
else
|
|
echo "neither curl nor wget is installed, so nothing can be downloaded." >&2
|
|
echo "Install one first: $(pkg_install_cmd curl)" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
download() {
|
|
local url="$1" out="$2"
|
|
case "$DL" in
|
|
curl) curl -fsSL --retry 3 -o "$out" "$url" ;;
|
|
wget) wget -q --tries=3 -O "$out" "$url" ;;
|
|
esac
|
|
}
|
|
|
|
# sha256sum is coreutils; shasum is the perl one that turns up on stripped
|
|
# images. Verification is not optional, so if neither exists that is fatal.
|
|
SHA=""
|
|
pick_sha() {
|
|
if command -v sha256sum >/dev/null 2>&1; then SHA=sha256sum
|
|
elif command -v shasum >/dev/null 2>&1; then SHA="shasum -a 256"
|
|
else
|
|
echo "no sha256sum and no shasum — downloads could not be verified." >&2
|
|
echo "Refusing to install unverified binaries." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ── package manager, for the instructions only ─────────────────────────────
|
|
# This never runs a package manager. It names one so the reported action is
|
|
# something you can paste, on the distro you are actually on — an apt line on
|
|
# Amazon Linux 2 is a wrong answer dressed up as help.
|
|
|
|
pkg_install_cmd() {
|
|
local pkg="$1"
|
|
if command -v apt-get >/dev/null 2>&1; then echo "sudo apt-get update && sudo apt-get install -y $pkg"
|
|
elif command -v dnf >/dev/null 2>&1; then echo "sudo dnf install -y $pkg"
|
|
elif command -v yum >/dev/null 2>&1; then echo "sudo yum install -y $pkg"
|
|
elif command -v zypper >/dev/null 2>&1; then echo "sudo zypper install -y $pkg"
|
|
elif command -v apk >/dev/null 2>&1; then echo "sudo apk add $pkg"
|
|
else echo "install '$pkg' with this system's package manager"
|
|
fi
|
|
}
|
|
|
|
docker_pkg() {
|
|
# Debian and Ubuntu call it docker.io; the RPM distros call it docker.
|
|
if command -v apt-get >/dev/null 2>&1; then echo docker.io; else echo docker; fi
|
|
}
|
|
|
|
# ── detect ─────────────────────────────────────────────────────────────────
|
|
|
|
detect() {
|
|
echo "host"
|
|
echo " kernel $(uname -r)"
|
|
echo " arch $(arch) ($(uname -m))"
|
|
[ -r /etc/os-release ] && \
|
|
echo " distro $(sed -n 's/^PRETTY_NAME="\(.*\)"/\1/p' /etc/os-release)"
|
|
if is_wsl; then echo " platform WSL"; else echo " platform native linux"; fi
|
|
|
|
local total_kb avail_kb
|
|
total_kb=$(awk '/^MemTotal:/{print $2}' /proc/meminfo)
|
|
avail_kb=$(awk '/^MemAvailable:/{print $2}' /proc/meminfo)
|
|
printf " memory %d GB total, %d GB available\n" \
|
|
$((total_kb / 1024 / 1024)) $((avail_kb / 1024 / 1024))
|
|
if [ $((avail_kb / 1024 / 1024)) -lt 4 ]; then
|
|
echo " ! under 4 GB available — a cluster will struggle here."
|
|
echo " rigmini.sh says how much this box will actually give you."
|
|
fi
|
|
|
|
echo " install to $OUT_BIN"
|
|
detect_libc
|
|
detect_prereqs
|
|
detect_docker
|
|
detect_inotify
|
|
return 0
|
|
}
|
|
|
|
# tilt is the one binary here that needs a recent glibc. MEASURED, not guessed:
|
|
# tilt 0.37.6 on Amazon Linux 2 (glibc 2.26) fails with
|
|
#
|
|
# /lib64/libc.so.6: version `GLIBC_2.34' not found (required by .../tilt)
|
|
#
|
|
# which names a symbol rather than the problem. Amazon Linux 2 is a stock
|
|
# WorkSpaces bundle, so this is the likely case, not an exotic one. Report the
|
|
# version now; `verify` catches the actual failure after installing.
|
|
detect_libc() {
|
|
local v=""
|
|
if command -v ldd >/dev/null 2>&1; then
|
|
v=$(ldd --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+$' || true)
|
|
fi
|
|
if [ -z "$v" ]; then
|
|
echo " libc unknown (no ldd) — 'verify' is the real test"
|
|
return 0
|
|
fi
|
|
echo " libc glibc $v"
|
|
if [ "$(printf '%s\n2.34\n' "$v" | sort -V | head -1)" != "2.34" ]; then
|
|
echo " ! older than glibc 2.34, which tilt needs. kubectl, kind, jq and"
|
|
echo " ctlptl are static or libc-only and work here; tilt will not start."
|
|
echo " Install the core tier, or run tilt from a container."
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# What this script needs to do its own job. Reported here so `detect` answers
|
|
# "will install work?" instead of leaving you to find out one download in.
|
|
# Amazon Linux 2 ships without tar, which is exactly the surprise this catches.
|
|
detect_prereqs() {
|
|
local missing=""
|
|
if command -v curl >/dev/null 2>&1; then echo " download curl"
|
|
elif command -v wget >/dev/null 2>&1; then echo " download wget"
|
|
else echo " ! no curl and no wget — nothing can be downloaded"; missing+=" curl"
|
|
fi
|
|
|
|
if command -v sha256sum >/dev/null 2>&1 || command -v shasum >/dev/null 2>&1; then
|
|
echo " checksums ok"
|
|
else
|
|
echo " ! no sha256sum or shasum — downloads could not be verified"
|
|
missing+=" coreutils"
|
|
fi
|
|
|
|
if command -v tar >/dev/null 2>&1 && command -v gzip >/dev/null 2>&1; then
|
|
echo " archives tar + gzip"
|
|
else
|
|
echo " ! no tar/gzip — tilt and ctlptl ship as tarballs, so the dev tier"
|
|
echo " cannot be unpacked. The core tier is two bare binaries and is fine."
|
|
missing+=" tar gzip"
|
|
fi
|
|
|
|
if [ -n "$missing" ]; then
|
|
MANUAL+=("Install what this script needs to run at all:
|
|
$(pkg_install_cmd "${missing# }")")
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
detect_docker() {
|
|
# kind builds a cluster out of containers. Without a reachable daemon,
|
|
# everything here installs perfectly and then does nothing.
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
if [ -S /var/run/docker.sock ]; then
|
|
echo " docker socket present, no cli"
|
|
return 0
|
|
fi
|
|
echo " ! docker not installed — kind has nothing to build a cluster in"
|
|
MANUAL+=("Install Docker. It is the one real prerequisite, and the only
|
|
thing here that needs root:
|
|
$(pkg_install_cmd "$(docker_pkg)")
|
|
sudo systemctl enable --now docker
|
|
sudo usermod -aG docker \"\$USER\"
|
|
then log out and back in, so the new group applies to your shell.")
|
|
return 0
|
|
fi
|
|
if docker info >/dev/null 2>&1; then
|
|
echo " docker $(docker version --format '{{.Server.Version}}' 2>/dev/null)"
|
|
# Distro packages routinely omit the compose plugin, so a working
|
|
# daemon says nothing about whether `docker compose up` will run.
|
|
if docker compose version >/dev/null 2>&1; then
|
|
echo " compose $(docker compose version --short 2>/dev/null)"
|
|
else
|
|
echo " ! no 'docker compose' plugin — compose files will not start."
|
|
echo " The dev tier installs one; no root needed."
|
|
fi
|
|
local n
|
|
n=$(docker ps --filter "label=io.x-k8s.kind.cluster" --format '{{.Names}}' 2>/dev/null | wc -l)
|
|
# Must be an `if`, not `[ ] && echo`: as the last statement here the
|
|
# latter returns 1 when the count is zero, and `set -e` kills the
|
|
# caller. That is the fresh-machine case, where it does most harm.
|
|
if [ "$n" -gt 0 ]; then
|
|
echo " - $n kind node container(s) already running"
|
|
fi
|
|
else
|
|
echo " ! docker cli present but the daemon is unreachable"
|
|
MANUAL+=("Start Docker, or add yourself to the docker group:
|
|
sudo systemctl enable --now docker
|
|
sudo usermod -aG docker \"\$USER\" # then log out and back in")
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# kind and tilt both watch large trees. Distro defaults are far too low and the
|
|
# failure mode is silent: tilt simply stops noticing that files changed.
|
|
detect_inotify() {
|
|
local w i
|
|
w=$(cat /proc/sys/fs/inotify/max_user_watches 2>/dev/null || echo 0)
|
|
i=$(cat /proc/sys/fs/inotify/max_user_instances 2>/dev/null || echo 0)
|
|
echo " inotify watches=$w instances=$i"
|
|
if [ "$w" -lt 524288 ] || [ "$i" -lt 512 ]; then
|
|
echo " ! low — tilt will silently stop seeing file changes"
|
|
MANUAL+=("Raise the inotify limits (needs root):
|
|
echo -e 'fs.inotify.max_user_watches=524288\\nfs.inotify.max_user_instances=512' \\
|
|
| sudo tee /etc/sysctl.d/99-rig.conf
|
|
sudo sysctl --system")
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ── fetch ──────────────────────────────────────────────────────────────────
|
|
|
|
verify_sha() {
|
|
local file="$1" want="$2" name="$3" got
|
|
got=$($SHA "$file" | awk '{print $1}')
|
|
if [ "$got" != "$want" ]; then
|
|
echo >&2
|
|
echo "CHECKSUM MISMATCH for $name — not installing it." >&2
|
|
echo " expected $want" >&2
|
|
echo " got $got" >&2
|
|
echo >&2
|
|
echo "Either the pin in this script is stale, or what arrived is not what" >&2
|
|
echo "the publisher released. Neither is worth guessing about." >&2
|
|
rm -f "$file"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# fetch_bin <name> <url> <sha256> <dest-dir> — a bare binary
|
|
fetch_bin() {
|
|
local name="$1" url="$2" sha="$3" dest="$4"
|
|
local tmp="$dest/.$name.tmp"
|
|
printf ' %-8s ' "$name"
|
|
download "$url" "$tmp"
|
|
verify_sha "$tmp" "$sha" "$name"
|
|
mv "$tmp" "$dest/$name"
|
|
chmod +x "$dest/$name"
|
|
echo "ok"
|
|
}
|
|
|
|
# fetch_tgz <name> <url> <sha256> <dest-dir> <path-inside> <strip>
|
|
# Archive layouts differ, so the caller says which. tilt and ctlptl both ship
|
|
# the binary at the archive root, hence strip=0.
|
|
fetch_tgz() {
|
|
local name="$1" url="$2" sha="$3" dest="$4" inner="$5" strip="$6"
|
|
local tmp="$dest/.$name.tgz"
|
|
printf ' %-8s ' "$name"
|
|
download "$url" "$tmp"
|
|
verify_sha "$tmp" "$sha" "$name"
|
|
# --no-same-owner: some archives ship as uid 1001, and extracting as root
|
|
# would otherwise restore an owner that is not you.
|
|
tar -xzf "$tmp" -C "$dest" --strip-components="$strip" --no-same-owner "$inner"
|
|
rm -f "$tmp"
|
|
chmod +x "$dest/$name"
|
|
echo "ok"
|
|
}
|
|
|
|
fetch() {
|
|
local dest="$OUT_BIN" tier="dev"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--to) dest="${2:?--to needs a directory}"; shift 2 ;;
|
|
core|dev) tier="$1"; shift ;;
|
|
*) echo "unknown argument: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
mkdir -p "$dest"
|
|
|
|
if ! command -v tar >/dev/null 2>&1 && [ "$tier" = "dev" ]; then
|
|
echo "tar is missing, and tilt and ctlptl ship as tarballs." >&2
|
|
echo " $(pkg_install_cmd tar)" >&2
|
|
echo "Or install the core tier, which is two bare binaries: $0 install core" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "fetching '$tier' into $dest (verifying every checksum)"
|
|
fetch_bin kubectl "$KUBECTL_URL" "$KUBECTL_SHA256" "$dest"
|
|
fetch_bin jq "$JQ_URL" "$JQ_SHA256" "$dest"
|
|
if [ "$tier" = "dev" ]; then
|
|
fetch_bin kind "$KIND_URL" "$KIND_SHA256" "$dest"
|
|
fetch_tgz tilt "$TILT_URL" "$TILT_SHA256" "$dest" tilt 0
|
|
fetch_tgz ctlptl "$CTLPTL_URL" "$CTLPTL_SHA256" "$dest" ctlptl 0
|
|
fetch_bin docker-compose "$COMPOSE_URL" "$COMPOSE_SHA256" "$dest"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# A copy in OUT_BIN only gives you `docker-compose`. The hyphenated form is the
|
|
# retired v1 spelling; every compose file written in the last few years assumes
|
|
# `docker compose`, and that resolves plugins by name from this directory.
|
|
install_compose_plugin() {
|
|
local src="$OUT_BIN/docker-compose" dir="$HOME/.docker/cli-plugins"
|
|
[ -x "$src" ] || return 0
|
|
mkdir -p "$dir"
|
|
if [ -e "$dir/docker-compose" ] && [ ! -L "$dir/docker-compose" ]; then
|
|
echo
|
|
echo " ! $dir/docker-compose exists and is not a symlink — left alone"
|
|
MANUAL+=("Something already installs the compose plugin at
|
|
$dir/docker-compose
|
|
To use the pinned build instead:
|
|
ln -sf $src $dir/docker-compose")
|
|
return 0
|
|
fi
|
|
ln -sfn "$src" "$dir/docker-compose"
|
|
echo
|
|
echo " compose plugin linked into $dir"
|
|
return 0
|
|
}
|
|
|
|
# ── verify ─────────────────────────────────────────────────────────────────
|
|
|
|
tier_tools() { [ "$1" = "core" ] && echo "$CORE_TOOLS" || echo "$CORE_TOOLS $DEV_TOOLS"; }
|
|
|
|
# Downloading a verified binary proves it is the right file, not that this
|
|
# machine can run it. On an old distro tilt fails here, with a linker error
|
|
# about a missing symbol, and finding that out now beats finding out during a
|
|
# first cluster build.
|
|
verify_tools() {
|
|
local tier="${1:-dev}" b bin out rc broke=0
|
|
echo "checking that each one actually runs"
|
|
for b in $(tier_tools "$tier"); do
|
|
bin="$OUT_BIN/$b"
|
|
if [ ! -x "$bin" ]; then
|
|
printf ' %-14s not installed\n' "$b"
|
|
continue
|
|
fi
|
|
# Not piped into `head`. With `pipefail` set, a tool that prints more
|
|
# than one line gets SIGPIPE when head closes the pipe, and the
|
|
# pipeline reports 141 — so a working kubectl was announced as "does
|
|
# not run here", with its own correct version string as the evidence.
|
|
# Take the first line afterwards, from the string.
|
|
rc=0
|
|
case "$b" in
|
|
kubectl) out=$("$bin" version --client 2>&1) || rc=$? ;;
|
|
jq) out=$("$bin" --version 2>&1) || rc=$? ;;
|
|
*) out=$("$bin" version 2>&1) || rc=$? ;;
|
|
esac
|
|
out=${out%%$'\n'*}
|
|
if [ "$rc" -eq 0 ]; then
|
|
printf ' %-14s %s\n' "$b" "$out"
|
|
else
|
|
printf ' ! %-12s does not run here: %s\n' "$b" "$out"
|
|
broke=1
|
|
fi
|
|
done
|
|
if [ "$broke" -eq 1 ]; then
|
|
echo
|
|
echo " A binary that downloads and verifies but will not start is almost"
|
|
echo " always this distro's libc being older than the release needs."
|
|
echo " 'detect' prints the glibc version. The core tier (kubectl + jq)"
|
|
echo " has no such dependency and will work regardless."
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ── install ────────────────────────────────────────────────────────────────
|
|
|
|
# Installing into a directory early in PATH silently replaces whatever the
|
|
# machine was already using, which on a shared or corporate machine can break
|
|
# unrelated work — kubectl more than one minor away from its cluster is the
|
|
# common one. Say so; never decide it.
|
|
warn_shadowing() {
|
|
local b existing shadowed="" paths="" tier="${1:-dev}"
|
|
case ":${PATH}:" in
|
|
*":$OUT_BIN:"*) ;;
|
|
*) return 0 ;; # not on PATH, so nothing is being shadowed yet
|
|
esac
|
|
for b in $(tier_tools "$tier"); do
|
|
[ -x "$OUT_BIN/$b" ] || continue
|
|
existing=$(PATH=$(echo "$PATH" | tr ':' '\n' | grep -vx "$OUT_BIN" | paste -sd:) \
|
|
command -v "$b" 2>/dev/null || true)
|
|
[ -n "$existing" ] || continue
|
|
[ "$existing" = "$OUT_BIN/$b" ] && continue
|
|
shadowed+=$(printf ' %-14s %s' "$b" "$existing")$'\n'
|
|
# Only the shadowing copies are the user's to remove. Listing the whole
|
|
# tier would delete tools that shadow nothing and are the only copy.
|
|
paths+="$OUT_BIN/$b "
|
|
done
|
|
[ -n "$shadowed" ] || return 0
|
|
|
|
echo
|
|
echo " ! these were already installed elsewhere and are now shadowed:"
|
|
printf '%s' "$shadowed"
|
|
MANUAL+=("Decide which toolchain wins. To keep the previous one:
|
|
rm -f ${paths% }
|
|
Or keep both, and let the existing one win by putting $OUT_BIN
|
|
last on PATH instead of first:
|
|
export PATH=\"\$PATH:$OUT_BIN\"")
|
|
return 0
|
|
}
|
|
|
|
report_manual() {
|
|
echo
|
|
if [ ${#MANUAL[@]} -eq 0 ]; then
|
|
echo "nothing left to do by hand."
|
|
return 0
|
|
fi
|
|
echo "host actions this cannot perform (${#MANUAL[@]}):"
|
|
echo
|
|
local n=1 m
|
|
for m in "${MANUAL[@]}"; do
|
|
echo " $n. $m"
|
|
echo
|
|
n=$((n + 1))
|
|
done
|
|
return 0
|
|
}
|
|
|
|
install() {
|
|
local tier="${1:-dev}"
|
|
detect
|
|
echo
|
|
fetch "$tier"
|
|
# An `if`, not `[ ] && ...`: on the core tier the test fails, and under
|
|
# `set -e` a bare failing test here would end the run silently.
|
|
if [ "$tier" = "dev" ]; then
|
|
install_compose_plugin
|
|
fi
|
|
echo
|
|
verify_tools "$tier"
|
|
warn_shadowing "$tier"
|
|
|
|
if [ "$tier" = "core" ]; then
|
|
echo
|
|
echo " core tier: no kind, tilt, ctlptl or compose. '$0 install dev' adds them."
|
|
fi
|
|
|
|
case ":${PATH}:" in
|
|
*":$OUT_BIN:"*) ;;
|
|
*) MANUAL+=("Put the toolchain on your PATH — add to ~/.bashrc:
|
|
export PATH=\"${OUT_BIN}:\$PATH\"
|
|
then: source ~/.bashrc") ;;
|
|
esac
|
|
|
|
report_manual
|
|
|
|
if [ "$tier" = "dev" ]; then
|
|
echo "Once Docker is reachable and this is on PATH:"
|
|
echo
|
|
echo " kind create cluster --name scratch"
|
|
echo " kubectl cluster-info --context kind-scratch"
|
|
echo " kind delete cluster --name scratch"
|
|
echo
|
|
echo "That round trip is the real test that this machine can host a rig."
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
list() {
|
|
echo "pinned, linux/amd64 only:"
|
|
printf ' %-14s %s\n' kubectl "$KUBECTL_VERSION"
|
|
printf ' %-14s %s\n' jq "$JQ_VERSION"
|
|
printf ' %-14s %s\n' kind "$KIND_VERSION"
|
|
printf ' %-14s %s\n' tilt "$TILT_VERSION"
|
|
printf ' %-14s %s\n' ctlptl "$CTLPTL_VERSION"
|
|
printf ' %-14s %s\n' docker-compose "$COMPOSE_VERSION"
|
|
echo
|
|
echo " core = $CORE_TOOLS"
|
|
echo " dev = $CORE_TOOLS $DEV_TOOLS"
|
|
echo
|
|
echo "Checksums are pinned in the block at the top of this file. To bump one,"
|
|
echo "take the new checksum from the publisher's own release list — the header"
|
|
echo "comment has the exact commands."
|
|
return 0
|
|
}
|
|
|
|
# ── main ───────────────────────────────────────────────────────────────────
|
|
|
|
require_linux
|
|
|
|
# Read the command, THEN shift — and shift only if there is something there.
|
|
# A bare `shift` with no positional parameters returns 1, and under `set -e`
|
|
# that ended the script before a single line was printed: running this with no
|
|
# arguments at all, the documented default, did nothing and said nothing.
|
|
cmd="${1:-install}"
|
|
[ $# -gt 0 ] && shift
|
|
|
|
case "$cmd" in
|
|
detect) detect; report_manual ;;
|
|
list) list ;;
|
|
verify) verify_tools "${1:-dev}" ;;
|
|
fetch) require_amd64; pick_downloader; pick_sha; fetch "$@" ;;
|
|
install) require_amd64; pick_downloader; pick_sha; install "${1:-dev}" ;;
|
|
*) echo "usage: $0 [detect|list|install|fetch|verify]" >&2
|
|
echo " install [core|dev] (default dev)" >&2
|
|
echo " fetch [core|dev] [--to DIR]" >&2
|
|
echo " OUT_BIN=<dir> overrides the install directory" >&2
|
|
exit 1 ;;
|
|
esac
|