installer
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# Toolchain installer: detect the host, install pinned tools into $OUT_BIN, report
|
||||
# host actions it will not perform (no sudo, no apt). Usually via `make deps`.
|
||||
# Usage: deps.sh [detect [all] | list | verify [core|dev] | fetch [core|dev] [--to DIR] | install [core|dev]
|
||||
# | manifest NAME | manifests [--to DIR]]
|
||||
# | manifest NAME | manifests [--to DIR] | snapshot [DIR]]
|
||||
# Notes: docs/notes/deps.md
|
||||
|
||||
set -euo pipefail
|
||||
@@ -27,6 +27,14 @@ abspath() {
|
||||
|
||||
OUT_BIN="${OUT_BIN:-$HOME/.local/bin}"
|
||||
HOST_ROOT="${HOST_ROOT:-/}"
|
||||
# A host fixture (docs/notes/installer-testing.md) is a root whose kernel files stand in
|
||||
# for this machine's; UNAME_S does the same for the one fact a file cannot carry.
|
||||
if [ "$HOST_ROOT" != / ]; then
|
||||
if [ -r "$HOST_ROOT/proc/meminfo" ]; then MEMINFO="${MEMINFO:-$HOST_ROOT/proc/meminfo}"; fi
|
||||
if [ -r "$HOST_ROOT/proc/sys/vm/overcommit_memory" ]; then
|
||||
OVERCOMMIT_FILE="${OVERCOMMIT_FILE:-$HOST_ROOT/proc/sys/vm/overcommit_memory}"
|
||||
fi
|
||||
fi
|
||||
DEPS_SOURCE="${DEPS_SOURCE:-upstream}"
|
||||
DEPS_ARTIFACTORY_URL="${DEPS_ARTIFACTORY_URL:-}"
|
||||
BAKED_BIN="${BAKED_BIN:-/opt/rig/bin}"
|
||||
@@ -138,7 +146,7 @@ docker_pkg() {
|
||||
|
||||
# Windows outside WSL (Git Bash, MSYS, Cygwin) fails confusingly; name it instead.
|
||||
require_linux() {
|
||||
case "$(uname -s)" in
|
||||
case "${UNAME_S:-$(uname -s)}" in
|
||||
MINGW*|MSYS*|CYGWIN*)
|
||||
cat >&2 <<'EOF'
|
||||
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
||||
@@ -157,7 +165,7 @@ EOF
|
||||
esac
|
||||
}
|
||||
|
||||
is_wsl() { grep -qi microsoft /proc/version 2>/dev/null; }
|
||||
is_wsl() { grep -qi microsoft "$(host_file /proc/version)" 2>/dev/null; }
|
||||
|
||||
detect() {
|
||||
echo "host"
|
||||
@@ -765,6 +773,54 @@ fetch_manifests() { # [--to DIR]
|
||||
done
|
||||
}
|
||||
|
||||
# ── snapshot: this machine as a host fixture ───────────────────────────────
|
||||
# Writes what detect reads, cut down to what it needs — never the environment, the
|
||||
# home directory or the host name — plus the lines detect prints for it. A fact of
|
||||
# one machine: keep it with an overlay or in rig's local/, never in rig itself, and
|
||||
# replay it with ctrl/hosttest.sh. Notes: docs/notes/installer-testing.md
|
||||
snapshot() {
|
||||
local dest r f
|
||||
dest="$(abspath "${1:-host-snapshot}")"
|
||||
if [ -n "$(ls -A "$dest" 2>/dev/null)" ]; then
|
||||
echo "snapshot: $dest already holds something — pick an empty directory" >&2
|
||||
exit 1
|
||||
fi
|
||||
r="$dest/root"
|
||||
mkdir -p "$r/etc" "$r/proc/sys/vm"
|
||||
f=$(host_file /etc/os-release)
|
||||
if [ -r "$f" ]; then grep -E '^(PRETTY_NAME|NAME|VERSION_ID|ID|ID_LIKE)=' "$f" > "$r/etc/os-release"; fi
|
||||
# The kernel release says WSL or not; the full build string names build hosts.
|
||||
echo "Linux version $(awk '{print $3; exit}' "$(host_file /proc/version)" 2>/dev/null || uname -r)" > "$r/proc/version"
|
||||
grep -E '^(MemTotal|MemAvailable|SwapTotal|SwapFree):' "${MEMINFO:-/proc/meminfo}" > "$r/proc/meminfo"
|
||||
cat "${OVERCOMMIT_FILE:-/proc/sys/vm/overcommit_memory}" > "$r/proc/sys/vm/overcommit_memory" 2>/dev/null || true
|
||||
f=$(host_file /etc/wsl.conf)
|
||||
if [ -r "$f" ]; then
|
||||
# Section headers and the two keys detect reads; a [user] default= names a person.
|
||||
grep -E '^[[:space:]]*(\[[a-z0-9]+\]|systemd[[:space:]]*=|generateResolvConf[[:space:]]*=)' "$f" > "$r/etc/wsl.conf" || true
|
||||
fi
|
||||
f=$(ls "$HOST_ROOT"/mnt/c/Users/*/.wslconfig 2>/dev/null | head -1 || true)
|
||||
if [ -n "$f" ] && grep -qE '^\s*memory\s*=' "$f"; then
|
||||
mkdir -p "$r/mnt/c/Users/user"
|
||||
{ echo "[wsl2]"; grep -E '^\s*memory\s*=' "$f"; } > "$r/mnt/c/Users/user/.wslconfig"
|
||||
fi
|
||||
{
|
||||
echo "# for the record; not replayed"
|
||||
echo "arch=$(arch)"
|
||||
echo "glibc=$(ldd --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+$' || echo unknown)"
|
||||
echo "taken=$(date -u +%Y-%m-%d)"
|
||||
} > "$dest/facts.txt"
|
||||
# The lines the fixture itself decides, as detect prints them for it now.
|
||||
{
|
||||
echo "# Written by deps.sh snapshot: what detect said about this machine."
|
||||
env -u MEMINFO -u OVERCOMMIT_FILE HOST_ROOT="$r" bash "./$(basename "${BASH_SOURCE[0]}")" detect all 2>/dev/null \
|
||||
| grep -E '^ (distro|memory|overcommit|systemd|resolv\.conf|wslconfig) |^ ! systemd|^ - resolv\.conf' \
|
||||
| sed 's/^ /+ /'
|
||||
} > "$dest/expect.txt"
|
||||
echo "wrote $dest"
|
||||
(cd "$dest" && find . -type f | sort | sed 's|^\./| |')
|
||||
echo "keep it with an overlay or in rig's local/, never in rig; replay it with rig's hosttest.sh"
|
||||
}
|
||||
|
||||
# Baked mode copies binaries already in the image, so it needs no downloader.
|
||||
need_downloads() {
|
||||
require_amd64
|
||||
@@ -781,10 +837,12 @@ case "$cmd" in
|
||||
manifest) need_downloads
|
||||
fetch_manifest "${1:?usage: $0 manifest <METALLB|CERT_MANAGER|METRICS_SERVER>}" "$MANIFESTS_HOME" ;;
|
||||
manifests) need_downloads; fetch_manifests "$@" ;;
|
||||
snapshot) snapshot "${1:-}" ;;
|
||||
*) echo "usage: $0 [detect [all]|list|verify|fetch|install|manifest NAME|manifests]" >&2
|
||||
echo " install [core|dev] (default dev)" >&2
|
||||
echo " fetch [core|dev] [--to DIR]" >&2
|
||||
echo " manifests [--to DIR] the addons' pinned manifests, verified" >&2
|
||||
echo " snapshot [DIR] this machine as a host fixture (no secrets)" >&2
|
||||
echo " OUT_BIN=<dir> overrides the install directory" >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
|
||||
52
rig/ctrl/hosttest.sh
Executable file
52
rig/ctrl/hosttest.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Replay host fixtures: run `deps.sh detect all` against a stand-in machine and check
|
||||
# what it must (and must not) say. No docker, no network, no root.
|
||||
# Usage: hosttest.sh [FIXTURE_DIR...] default: tests/hosts/* exits 1 on a mismatch
|
||||
# A fixture is root/ (the files detect reads), expect.txt (+ must appear, - must not,
|
||||
# exit N), and an optional env (KEY=value lines). `deps.sh snapshot` writes one.
|
||||
# Notes: docs/notes/installer-testing.md
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
dirs=("$@")
|
||||
if [ ${#dirs[@]} -eq 0 ]; then dirs=(../tests/hosts/*/); fi
|
||||
|
||||
rc=0 passed=0 failed=0
|
||||
for d in "${dirs[@]}"; do
|
||||
d="${d%/}"
|
||||
if [ ! -f "$d/expect.txt" ]; then
|
||||
echo " FAIL $d: no expect.txt — not a host fixture" >&2
|
||||
rc=1; failed=$((failed + 1)); continue
|
||||
fi
|
||||
# Only the fixture's own settings: the caller's HOST_ROOT, MEMINFO or UNAME_S
|
||||
# must not leak into a replay.
|
||||
run=(env -u HOST_ROOT -u MEMINFO -u OVERCOMMIT_FILE -u UNAME_S)
|
||||
if [ -d "$d/root" ]; then run+=(HOST_ROOT="$(cd "$d/root" && pwd)"); fi
|
||||
if [ -f "$d/env" ]; then
|
||||
while IFS= read -r kv; do run+=("$kv"); done < <(grep -vE '^[[:space:]]*(#|$)' "$d/env")
|
||||
fi
|
||||
out=$("${run[@]}" bash ./deps.sh detect all 2>&1)
|
||||
code=$?
|
||||
|
||||
bad=""
|
||||
want_exit=0
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
'+ '*) grep -qF -- "${line#+ }" <<< "$out" || bad+=$'\n'" missing: ${line#+ }" ;;
|
||||
'- '*) grep -qF -- "${line#- }" <<< "$out" && bad+=$'\n'" present: ${line#- }" ;;
|
||||
'exit '*) want_exit="${line#exit }" ;;
|
||||
esac
|
||||
done < <(grep -vE '^[[:space:]]*(#|$)' "$d/expect.txt")
|
||||
if [ "$code" != "$want_exit" ]; then bad+=$'\n'" exit: $code, wanted $want_exit"; fi
|
||||
|
||||
if [ -z "$bad" ]; then
|
||||
printf ' ok %s\n' "$(basename "$d")"
|
||||
passed=$((passed + 1))
|
||||
else
|
||||
printf ' FAIL %s%s\n' "$(basename "$d")" "$bad"
|
||||
rc=1; failed=$((failed + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
printf '%d host fixture(s) as expected, %d not\n' "$passed" "$failed"
|
||||
exit "$rc"
|
||||
101
rig/ctrl/installtest.sh
Executable file
101
rig/ctrl/installtest.sh
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
# The installer on clean machines: the generated kit (standalone/default/rigdeps.sh) in
|
||||
# stock distro containers, as a non-root user, the way it reaches a real machine.
|
||||
# Needs docker and the network; takes minutes. Exits 1 on a failure.
|
||||
# Usage: installtest.sh [IMAGE...] default: ubuntu:22.04 debian:trixie-slim, then offline
|
||||
# Notes: docs/notes/installer-testing.md
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
KIT="$(cd .. && pwd)/standalone/default/rigdeps.sh"
|
||||
images=("$@")
|
||||
if [ ${#images[@]} -eq 0 ]; then images=(ubuntu:22.04 debian:trixie-slim); fi
|
||||
|
||||
rc=0
|
||||
passed=0
|
||||
check() { # name, expected, actual
|
||||
if [ "$2" = "$3" ]; then
|
||||
printf ' ok %s\n' "$1"
|
||||
passed=$((passed + 1))
|
||||
else
|
||||
printf ' FAIL %s\n expected: %s\n got: %s\n' "$1" "$2" "$3"
|
||||
rc=1
|
||||
fi
|
||||
}
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "installtest needs a running docker it can reach" >&2
|
||||
exit 1
|
||||
fi
|
||||
# A stale kit would test yesterday's installer.
|
||||
if ! bash ./standalone.sh check >/dev/null 2>&1; then
|
||||
echo "the kit is stale — run: make standalone" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for img in "${images[@]}"; do
|
||||
printf '\n%s\n' "$img"
|
||||
|
||||
# A stock image has no curl or wget: the installer must say so and stop, not
|
||||
# half-install. This is the bootstrap paradox BOOTSTRAP.md describes.
|
||||
out=$(docker run --rm -v "$KIT:/kit/rigdeps.sh:ro" "$img" bash /kit/rigdeps.sh install dev 2>&1)
|
||||
code=$?
|
||||
check "bare: install refuses" "1" "$code"
|
||||
check "bare: and names what is missing" "yes" \
|
||||
"$(grep -q 'neither curl nor wget' <<< "$out" && echo yes || echo no)"
|
||||
|
||||
# The one root step a machine owner takes, then everything else as a plain user —
|
||||
# the Workspace's case: no sudo from the installer, tools in ~/.local/bin.
|
||||
out=$(docker run --rm -v "$KIT:/kit/rigdeps.sh:ro" "$img" bash -c '
|
||||
set -e
|
||||
apt-get update -qq >/dev/null
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl ca-certificates >/dev/null
|
||||
useradd -m t
|
||||
su t -s /bin/bash -c "
|
||||
set -e
|
||||
bash /kit/rigdeps.sh install dev
|
||||
echo ==verify; PATH=\$HOME/.local/bin:\$PATH bash /kit/rigdeps.sh verify dev
|
||||
echo ==manifests; bash /kit/rigdeps.sh manifests --to \$HOME/m
|
||||
echo ==bin; ls \$HOME/.local/bin
|
||||
echo ==m; ls \$HOME/m
|
||||
"' 2>&1)
|
||||
code=$?
|
||||
check "user: install, verify and manifests succeed" "0" "$code"
|
||||
check "user: the dev tier lands in ~/.local/bin" "ctlptl docker-compose jq kind kubectl tilt" \
|
||||
"$(sed -n '/^==bin$/,/^==m$/p' <<< "$out" | grep -vE '^==' | sort | xargs)"
|
||||
check "user: tells them to put it on PATH" "yes" \
|
||||
"$(grep -q 'Put the toolchain on your PATH' <<< "$out" && echo yes || echo no)"
|
||||
check "user: the three manifests, verified" "3" \
|
||||
"$(sed -n '/^==m$/,$p' <<< "$out" | grep -c '\.yaml$')"
|
||||
if [ "$code" -ne 0 ]; then printf '%s\n' "$out" | tail -15 | sed 's/^/ | /'; fi
|
||||
done
|
||||
|
||||
# The air-gapped path: everything baked into the image, then run with no network at all.
|
||||
printf '\noffline (deps-full, --network none)\n'
|
||||
tag="rig-installtest:full"
|
||||
if docker build -q -f Dockerfile.deps --target deps-full -t "$tag" .. >/dev/null 2>&1; then
|
||||
out=$(docker run --rm --network none --entrypoint bash "$tag" -c '
|
||||
set -e
|
||||
/work/rigdeps.sh install dev
|
||||
/work/rigdeps.sh manifests --to /tmp/m
|
||||
echo ==bin; ls /out/bin
|
||||
echo ==m; ls /tmp/m' 2>&1)
|
||||
code=$?
|
||||
check "offline: install and manifests succeed" "0" "$code"
|
||||
check "offline: the dev tier, from the image" "ctlptl docker-compose jq kind kubectl tilt" \
|
||||
"$(sed -n '/^==bin$/,/^==m$/p' <<< "$out" | grep -vE '^==' | sort | xargs)"
|
||||
check "offline: the three manifests, from the image" "3" \
|
||||
"$(sed -n '/^==m$/,$p' <<< "$out" | grep -c '\.yaml$')"
|
||||
if [ "$code" -ne 0 ]; then printf '%s\n' "$out" | tail -15 | sed 's/^/ | /'; fi
|
||||
docker rmi -f "$tag" >/dev/null 2>&1 || true
|
||||
else
|
||||
check "offline: the deps-full image builds" "yes" "no"
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
printf '%d install checks passed\n' "$passed"
|
||||
else
|
||||
printf 'FAILED — the installer did not do on a clean machine what it says\n' >&2
|
||||
fi
|
||||
exit "$rc"
|
||||
@@ -20,7 +20,7 @@ BUDGET_EXPLICIT=no # whether --budget was given, which retires the guess belo
|
||||
|
||||
# Refuse Git Bash / MSYS / Cygwin and kernels without /proc, with a clear message.
|
||||
require_linux() {
|
||||
case "$(uname -s)" in
|
||||
case "${UNAME_S:-$(uname -s)}" in
|
||||
MINGW*|MSYS*|CYGWIN*)
|
||||
cat >&2 <<'EOF'
|
||||
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# What rig has settled, written down as assertions: one decision per check.
|
||||
# No cluster, docker or network; exits 1 on failure (unlike `make check`).
|
||||
# Usage: make selftest (or: bash ctrl/selftest.sh)
|
||||
# Usage: make selftest [install] (or: bash ctrl/selftest.sh [install])
|
||||
# install: the installer in clean containers — docker, network, minutes (installtest.sh)
|
||||
# Notes: docs/notes/selftest.md
|
||||
set -uo pipefail # NOT -e: one failing check must not abort the rest
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [ "${1:-}" = install ]; then shift; exec bash ./installtest.sh "$@"; fi
|
||||
|
||||
source ./lib/config.sh
|
||||
|
||||
rc=0
|
||||
@@ -316,6 +319,26 @@ check "✖ S7 no house path or host name in rig" "0" \
|
||||
"$(cd .. && grep -rIlE "$HOUSE_PAT" . --exclude-dir=def --exclude-dir=local --exclude=STALE.md 2>/dev/null | wc -l)"
|
||||
|
||||
|
||||
note "the installer detects what each kind of machine needs"
|
||||
# Host fixtures (tests/hosts/): a stand-in root per machine, detect run against it. The
|
||||
# expensive machines — the Workspace, a WSL install — are exactly the ones you cannot
|
||||
# rebuild to test on, so their shapes are replayed here instead.
|
||||
out="$(bash ./hosttest.sh 2>&1)"
|
||||
check "every host fixture detects as expected" "$(ls -d ../tests/hosts/*/ | wc -l) host fixture(s) as expected, 0 not" \
|
||||
"$(tail -1 <<< "$out")"
|
||||
# A snapshot is carried off a machine that may be someone else's: it must hold only the
|
||||
# files detect reads, and nothing that names the machine or the person.
|
||||
SN="$TMP/snapshot/s"
|
||||
bash ./deps.sh snapshot "$SN" >/dev/null 2>&1
|
||||
check "snapshot writes only what detect reads" \
|
||||
"expect.txt facts.txt root/etc/os-release root/proc/meminfo root/proc/sys/vm/overcommit_memory root/proc/version" \
|
||||
"$( (cd "$SN" 2>/dev/null && find . -type f | sed 's|^\./||' | grep -vE '^root/(etc/wsl\.conf|mnt/c/Users/user/\.wslconfig)$' | sort | xargs) )"
|
||||
check "snapshot names no host, user or home" "0" \
|
||||
"$(grep -rlE "$(hostname)|${USER:-nobody}|/home/" "$SN" 2>/dev/null | wc -l)"
|
||||
check "and replays as the machine it was taken on" "yes" \
|
||||
"$(bash ./hosttest.sh "$SN" >/dev/null 2>&1 && echo yes || echo no)"
|
||||
|
||||
|
||||
note "the dev loop parses — needs tilt and kubectl, not a cluster"
|
||||
# A throwaway kubeconfig with kind-named entries (Tilt trusts kind contexts) and a
|
||||
# kubectl that swallows `apply`: the Tiltfile evaluates for real, nothing is contacted.
|
||||
|
||||
Reference in New Issue
Block a user