Compare commits
3 Commits
8f7e409ec4
...
rig-0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 100f389719 | |||
| 3bbfa413a3 | |||
| dee899d86f |
@@ -143,6 +143,10 @@ From here on this machine has curl, so **`make deps` is the short form** for
|
|||||||
every later run and every later copy of this directory. The container path is
|
every later run and every later copy of this directory. The container path is
|
||||||
the first-time path.
|
the first-time path.
|
||||||
|
|
||||||
|
How the installer itself is tested — machine fixtures, clean containers, a
|
||||||
|
Workspace snapshot, a throwaway WSL distro — is in
|
||||||
|
[`docs/notes/installer-testing.md`](docs/notes/installer-testing.md).
|
||||||
|
|
||||||
|
|
||||||
## Prove the machine before blaming the project
|
## Prove the machine before blaming the project
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ tilt: ## dev loop [up|down] (default
|
|||||||
# The counterpart to check: that one asks about the MACHINE and never fails,
|
# The counterpart to check: that one asks about the MACHINE and never fails,
|
||||||
# this one asks about RIG and exits 1. Checks are written as the decisions they
|
# this one asks about RIG and exits 1. Checks are written as the decisions they
|
||||||
# defend, so a failure names what is being undone.
|
# defend, so a failure names what is being undone.
|
||||||
selftest: ## does rig still do what it says? exits 1 if not
|
selftest: ## does rig still do what it says? exits 1 if not [install]
|
||||||
bash ctrl/selftest.sh
|
bash ctrl/selftest.sh $(ARGS)
|
||||||
|
|
||||||
# The one-file versions of rig's tools, one folder per profile, for machines the
|
# The one-file versions of rig's tools, one folder per profile, for machines the
|
||||||
# full rig is not going to. Generated from rig as it is, never edited by hand;
|
# full rig is not going to. Generated from rig as it is, never edited by hand;
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ cluster name, kube context, ports and paths — the same values every other rig
|
|||||||
script resolves through `ctrl/lib/config.sh` — so a copied and renamed rig, or a
|
script resolves through `ctrl/lib/config.sh` — so a copied and renamed rig, or a
|
||||||
moved overlay, deploys into its own cluster with no edits.
|
moved overlay, deploys into its own cluster with no edits.
|
||||||
|
|
||||||
`make help` lists every target.
|
`make help` lists every target. `make selftest` checks rig itself, the installer's
|
||||||
|
detection included; `make selftest install` runs the installer on clean containers
|
||||||
|
([`docs/notes/installer-testing.md`](docs/notes/installer-testing.md)).
|
||||||
|
|
||||||
On a machine where Docker really is the only thing installed, `make deps` has
|
On a machine where Docker really is the only thing installed, `make deps` has
|
||||||
nothing to download with — see [BOOTSTRAP.md](BOOTSTRAP.md), which runs the
|
nothing to download with — see [BOOTSTRAP.md](BOOTSTRAP.md), which runs the
|
||||||
|
|||||||
1
rig/VERSION
Normal file
1
rig/VERSION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0.1.0
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
# The pool is derived from the kind Docker network at install time.
|
# The pool is derived from the kind Docker network at install time.
|
||||||
# Notes: docs/notes/addons.md
|
# Notes: docs/notes/addons.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
ADDON_DIR="$(cd "$(dirname "$0")" && pwd)" # its manifest sits beside it
|
||||||
cd "${RIG_CTRL:-$(dirname "$0")/..}"
|
cd "${RIG_CTRL:-$(dirname "$0")/..}"
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
@@ -61,27 +62,7 @@ $K rollout status daemonset/speaker -n metallb-system --timeout=240s
|
|||||||
# on a race that resolves itself in seconds.
|
# on a race that resolves itself in seconds.
|
||||||
echo " configuring the address pool"
|
echo " configuring the address pool"
|
||||||
for attempt in 1 2 3 4 5 6 7 8 9 10; do
|
for attempt in 1 2 3 4 5 6 7 8 9 10; do
|
||||||
if $K apply -f - >/dev/null 2>&1 <<YAML
|
if render_manifest "$ADDON_DIR/metallb.yaml" | $K apply -f - >/dev/null 2>&1
|
||||||
apiVersion: metallb.io/v1beta1
|
|
||||||
kind: IPAddressPool
|
|
||||||
metadata:
|
|
||||||
name: default
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
addresses:
|
|
||||||
- ${pool_start}-${pool_end}
|
|
||||||
---
|
|
||||||
# Layer 2 mode: one node answers ARP for each address. No BGP peer needed, which
|
|
||||||
# is what makes this work on a laptop.
|
|
||||||
apiVersion: metallb.io/v1beta1
|
|
||||||
kind: L2Advertisement
|
|
||||||
metadata:
|
|
||||||
name: default
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
ipAddressPools:
|
|
||||||
- default
|
|
||||||
YAML
|
|
||||||
then
|
then
|
||||||
echo " pool ready: ${pool_start}-${pool_end}"
|
echo " pool ready: ${pool_start}-${pool_end}"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
19
rig/ctrl/addons/metallb.yaml
Normal file
19
rig/ctrl/addons/metallb.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: metallb.io/v1beta1
|
||||||
|
kind: IPAddressPool
|
||||||
|
metadata:
|
||||||
|
name: default
|
||||||
|
namespace: metallb-system
|
||||||
|
spec:
|
||||||
|
addresses:
|
||||||
|
- ${pool_start}-${pool_end}
|
||||||
|
---
|
||||||
|
# Layer 2 mode: one node answers ARP for each address. No BGP peer needed, which
|
||||||
|
# is what makes this work on a laptop.
|
||||||
|
apiVersion: metallb.io/v1beta1
|
||||||
|
kind: L2Advertisement
|
||||||
|
metadata:
|
||||||
|
name: default
|
||||||
|
namespace: metallb-system
|
||||||
|
spec:
|
||||||
|
ipAddressPools:
|
||||||
|
- default
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Readiness check: is this machine ready to run rig? Reports and instructs; never fixes.
|
# Readiness check: is this machine ready to run rig? Reports and instructs; never fixes.
|
||||||
# Usage: check.sh [all | mem [status|push|all|backup|restore]] (all = every detail)
|
# Usage: check.sh [all | mem [status|push|all|backup|restore]] (all = every detail)
|
||||||
|
# Prints the memory budget and what to do next first, then the configuration as it is.
|
||||||
# Notes: docs/notes/check.md
|
# Notes: docs/notes/check.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
@@ -12,13 +13,11 @@ if [ "${1:-}" = mem ]; then
|
|||||||
exec bash ./mem.sh "${@:-status}"
|
exec bash ./mem.sh "${@:-status}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Compact by default: facts only with `all`; problems (!) always print.
|
# Facts only with `all`; problems (!) always print.
|
||||||
VERBOSE=""
|
VERBOSE=""
|
||||||
if [ "${1:-}" = all ]; then VERBOSE=1; fi
|
if [ "${1:-}" = all ]; then VERBOSE=1; fi
|
||||||
fact() { if [ -n "$VERBOSE" ]; then echo "$@"; fi; }
|
fact() { if [ -n "$VERBOSE" ]; then echo "$@"; fi; }
|
||||||
|
|
||||||
bash ./deps.sh detect ${VERBOSE:+all}
|
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
load_config
|
load_config
|
||||||
|
|
||||||
@@ -66,49 +65,64 @@ port_busy() {
|
|||||||
grep -qi "^ *[0-9]*: [0-9A-F]*$hex " /proc/net/tcp /proc/net/tcp6 2>/dev/null
|
grep -qi "^ *[0-9]*: [0-9A-F]*$hex " /proc/net/tcp /proc/net/tcp6 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
echo
|
# Who listens on a port, as "pid name", when ss can tell (only for our own processes).
|
||||||
echo "rig"
|
port_owner() {
|
||||||
echo " cluster ${CLUSTER} (${KUBECONTEXT}) profile ${PROFILE_NAME}, ${NODES} node(s), registry ${REGISTRY_MODE}"
|
command -v ss >/dev/null 2>&1 || return 0
|
||||||
if [ -n "${OVERLAY:-}" ]; then
|
ss -ltnpH "sport = :$1" 2>/dev/null \
|
||||||
echo " overlay $(basename "$(_abs_from_ctrl "$OVERLAY_DIR")") ($(_abs_from_ctrl "$OVERLAY_DIR"))"
|
| sed -n 's/.*users:(("\([^"]*\)",pid=\([0-9]*\).*/\2 \1/p' | head -1
|
||||||
elif [ -n "$OVERLAY_DIR" ]; then
|
}
|
||||||
fact " overlay none named — rig's own ${OVERLAY_DIR}"
|
|
||||||
fi
|
# This environment's Tilt: a tilt started from this ctrl/ (the Makefile runs it there).
|
||||||
if [ -n "$VERBOSE" ] && [ -n "$OVERLAY_DIR" ]; then
|
tilt_pid=""
|
||||||
ov=$(_from_ctrl "$OVERLAY_DIR") pieces=""
|
if tilt_owner=$(port_owner "$TILT_PORT") && [ -n "$tilt_owner" ]; then
|
||||||
for piece in rig.env k8s/overlays/dev kind-config.yaml.tpl addons Tiltfile; do
|
pid=${tilt_owner%% *}
|
||||||
[ -e "$ov/$piece" ] && pieces+="$piece "
|
if [ "${tilt_owner#* }" = tilt ] && [ "$(readlink "/proc/$pid/cwd" 2>/dev/null)" = "$(pwd -P)" ]; then
|
||||||
done
|
tilt_pid=$pid
|
||||||
echo " provides: ${pieces:-nothing rig reads}"
|
|
||||||
fi
|
|
||||||
fact " manifests ${MANIFESTS_DIR:-none}"
|
|
||||||
fact " kind config ${KIND_CONFIG}"
|
|
||||||
fact " ingress ${INGRESS_MODE}"
|
|
||||||
if [ ! -f ./.env ]; then
|
|
||||||
fact " .env none — built-in defaults (cp ctrl/.env.example ctrl/.env to set values)"
|
|
||||||
fi
|
fi
|
||||||
if [ -n "${STALE_MANIFESTS_DIR:-}" ]; then
|
|
||||||
echo " ! .env MANIFESTS_DIR=${STALE_MANIFESTS_DIR} is the old default; rig's examples moved"
|
|
||||||
echo " to examples/ — delete that line from ctrl/.env (ignored until then)"
|
|
||||||
fi
|
|
||||||
# registry.sh points containerd at certs.d, which only works if the kind config says so,
|
|
||||||
# and a kind config is fixed at creation: a project's own file that drops it fails silently.
|
|
||||||
if [ "$REGISTRY_MODE" != none ] && ! grep -q 'config_path *= *"/etc/containerd/certs.d"' "$KIND_CONFIG"; then
|
|
||||||
echo " ! kind ${KIND_CONFIG} lacks the containerd config_path patch that registry mode"
|
|
||||||
echo " '${REGISTRY_MODE}' needs — copy it from ctrl/k8s/kind-config.yaml.tpl"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── memory: does this cluster fit right now? Warns; never blocks. ──────────
|
# none | stopped | up | unknown (no docker)
|
||||||
|
cluster_state() {
|
||||||
|
docker info >/dev/null 2>&1 || { echo unknown; return; }
|
||||||
|
local f="label=io.x-k8s.kind.cluster=${CLUSTER}"
|
||||||
|
if [ -n "$(docker ps -q --filter "$f")" ]; then echo up
|
||||||
|
elif [ -n "$(docker ps -aq --filter "$f")" ]; then echo stopped
|
||||||
|
else echo none; fi
|
||||||
|
}
|
||||||
|
state=$(cluster_state)
|
||||||
|
|
||||||
|
# ── memory budget: what this environment needs, against what the machine has ──
|
||||||
|
# The workload's share comes from the overlay: MEMORY_BUDGET="name=MB ..." in its rig.env,
|
||||||
|
# and MEMORY_BUDGET_TIP, optionally, says how to switch its largest part off.
|
||||||
|
# rig only knows what a node costs. Warns; never blocks.
|
||||||
total_mb=$(mb_of MemTotal)
|
total_mb=$(mb_of MemTotal)
|
||||||
avail_mb=$(mb_of MemAvailable)
|
avail_mb=$(mb_of MemAvailable)
|
||||||
swap_used_mb=$(( $(mb_of SwapTotal) - $(mb_of SwapFree) ))
|
swap_used_mb=$(( $(mb_of SwapTotal) - $(mb_of SwapFree) ))
|
||||||
overcommit=$(cat "${OVERCOMMIT_FILE:-/proc/sys/vm/overcommit_memory}" 2>/dev/null || echo '?')
|
overcommit=$(cat "${OVERCOMMIT_FILE:-/proc/sys/vm/overcommit_memory}" 2>/dev/null || echo '?')
|
||||||
need_mb=$(( NODES * NODE_MB ))
|
node_mb=$(( NODES * NODE_MB ))
|
||||||
|
|
||||||
|
budget="" work_mb=0 big_mb=0 big=""
|
||||||
|
for item in ${MEMORY_BUDGET:-}; do
|
||||||
|
name=${item%=*} mb=${item##*=}
|
||||||
|
if [ "$name" = "$item" ] || ! [[ "$mb" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo " ! budget '$item' in MEMORY_BUDGET is not name=MB"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[ "$mb" -gt 0 ] || continue
|
||||||
|
budget+=$(printf ' %-18s %6d MB' "$name" "$mb")$'\n'
|
||||||
|
work_mb=$(( work_mb + mb ))
|
||||||
|
if [ "$mb" -gt "$big_mb" ]; then big_mb=$mb big=$name; fi
|
||||||
|
done
|
||||||
|
need_mb=$(( work_mb + node_mb ))
|
||||||
|
|
||||||
rows=$(container_mb)
|
rows=$(container_mb)
|
||||||
# If our cluster is already up, its memory is already out of MemAvailable: need nothing more.
|
# If our cluster is already up, its memory is already out of MemAvailable.
|
||||||
ours_mb=$(awk -F'\t' -v c="$CLUSTER" '$2 == c { s += $1 } END { print s + 0 }' <<< "$rows")
|
ours_mb=$(awk -F'\t' -v c="$CLUSTER" '$2 == c { s += $1 } END { print s + 0 }' <<< "$rows")
|
||||||
still_mb=$(( ours_mb > 0 ? 0 : need_mb ))
|
if [ "$state" = up ]; then
|
||||||
|
still_mb=$(( need_mb > ours_mb ? need_mb - ours_mb : 0 ))
|
||||||
|
else
|
||||||
|
still_mb=$need_mb
|
||||||
|
fi
|
||||||
headroom=$(( avail_mb - still_mb ))
|
headroom=$(( avail_mb - still_mb ))
|
||||||
|
|
||||||
# The biggest things holding memory, other than this cluster: kind clusters summed, the rest by name.
|
# The biggest things holding memory, other than this cluster: kind clusters summed, the rest by name.
|
||||||
@@ -117,24 +131,41 @@ others=$(awk -F'\t' -v c="$CLUSTER" '
|
|||||||
$2 == "-" { k["container \x27" $3 "\x27"] += $1 }
|
$2 == "-" { k["container \x27" $3 "\x27"] += $1 }
|
||||||
END { for (n in k) printf "%d\t%s\n", k[n], n }' <<< "$rows" | sort -rn)
|
END { for (n in k) printf "%d\t%s\n", k[n], n }' <<< "$rows" | sort -rn)
|
||||||
|
|
||||||
if [ "$still_mb" -eq 0 ] && [ "$headroom" -ge 512 ]; then
|
echo
|
||||||
printf " memory up, holding %d MB — %d MB headroom for what you deploy\n" "$ours_mb" "$headroom"
|
echo "memory budget (${CLUSTER})"
|
||||||
elif [ "$still_mb" -eq 0 ]; then
|
printf '%s' "$budget"
|
||||||
printf " ! memory up, but only %d MB headroom for anything you deploy\n" "$headroom"
|
if [ -z "$budget" ]; then
|
||||||
elif [ "$headroom" -ge 512 ]; then
|
echo " workload ? (the overlay lists none: MEMORY_BUDGET in its rig.env)"
|
||||||
printf " memory fits — ~%d MB for %s node(s), %d MB headroom\n" "$need_mb" "$NODES" "$headroom"
|
fi
|
||||||
|
printf ' %-18s %6d MB (kind control plane, kube-system; %s node(s))\n' "node overhead" "$node_mb" "$NODES"
|
||||||
|
echo " ---------"
|
||||||
|
printf ' %-18s %6d MB\n' "required" "$need_mb"
|
||||||
|
if [ "$state" = up ]; then
|
||||||
|
printf ' %-18s %6d MB (%s is up)\n' "held by it now" "$ours_mb" "$CLUSTER"
|
||||||
|
fi
|
||||||
|
printf ' %-18s %6d MB\n' "this machine has" "$total_mb"
|
||||||
|
printf ' %-18s %6d MB (right now)\n' "available" "$avail_mb"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [ "$headroom" -ge 512 ]; then
|
||||||
|
if [ "$state" = up ]; then
|
||||||
|
echo "fits: ${CLUSTER} holds ${ours_mb} MB of its ${need_mb} MB; ${avail_mb} MB still available — ${headroom} MB headroom."
|
||||||
|
else
|
||||||
|
echo "fits: ${need_mb} MB needed, ${avail_mb} MB available — ${headroom} MB headroom."
|
||||||
|
fi
|
||||||
elif [ "$headroom" -ge 0 ]; then
|
elif [ "$headroom" -ge 0 ]; then
|
||||||
printf " ! memory fits, but only %d MB headroom (~%d MB for %s node(s))\n" "$headroom" "$need_mb" "$NODES"
|
echo "! tight: ${still_mb} MB still needed, ${avail_mb} MB available — only ${headroom} MB headroom."
|
||||||
else
|
else
|
||||||
printf " ! memory does not fit: ~%d MB needed, %d MB available\n" "$still_mb" "$avail_mb"
|
echo "! does not fit: ${still_mb} MB still needed, ${avail_mb} MB available."
|
||||||
# Two failures with opposite fixes, and telling them apart is the point.
|
# Two failures with opposite fixes, and telling them apart is the point.
|
||||||
if [ "$still_mb" -le "$total_mb" ]; then
|
if [ "$need_mb" -le "$total_mb" ]; then
|
||||||
echo " something else holds it (below) — stopping that helps, a bigger VM would not."
|
echo " Something else holds the memory (below): stopping it helps, a bigger machine would not."
|
||||||
if grep -q 'kind cluster' <<< "$others"; then
|
if grep -q 'kind cluster' <<< "$others"; then
|
||||||
echo " 'make cluster free' stops the other kind clusters. It stops, never deletes."
|
echo " 'make cluster free' stops the other kind clusters. It stops, never deletes."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " the machine itself is too small: ${total_mb} MB total."
|
echo " The machine itself is too small: ${total_mb} MB in all. Give it more memory"
|
||||||
|
echo " ('make check mem' has the WSL steps), or switch parts of the workload off."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -n "$others" ] && { [ -n "$VERBOSE" ] || [ "$headroom" -lt 512 ]; }; then
|
if [ -n "$others" ] && { [ -n "$VERBOSE" ] || [ "$headroom" -lt 512 ]; }; then
|
||||||
@@ -145,6 +176,12 @@ if [ -n "$others" ] && { [ -n "$VERBOSE" ] || [ "$headroom" -lt 512 ]; }; then
|
|||||||
echo " ... and $((n_others - 6)) more"
|
echo " ... and $((n_others - 6)) more"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# One part that outweighs the rest is the first thing to switch off on a smaller machine.
|
||||||
|
if [ "$work_mb" -gt 0 ] && [ $(( big_mb * 2 )) -gt "$work_mb" ] && [ "$big_mb" -ge 1024 ]; then
|
||||||
|
echo
|
||||||
|
echo "${big} is ${big_mb} of the ${work_mb} MB the workload needs — more than the rest"
|
||||||
|
echo "combined. If you are not working on it, ${MEMORY_BUDGET_TIP:-switching it off in the overlay} gets that back."
|
||||||
|
fi
|
||||||
if [ "$swap_used_mb" -gt 0 ]; then
|
if [ "$swap_used_mb" -gt 0 ]; then
|
||||||
fact " ${swap_used_mb} MB already in swap, which 'available' does not count: expect slow before failing"
|
fact " ${swap_used_mb} MB already in swap, which 'available' does not count: expect slow before failing"
|
||||||
fi
|
fi
|
||||||
@@ -152,47 +189,106 @@ if [ "$overcommit" = "1" ]; then
|
|||||||
fact " overcommit=1: allocations never fail, so read 'fits' as a ceiling (OOM killer settles up)"
|
fact " overcommit=1: allocations never fail, so read 'fits' as a ceiling (OOM killer settles up)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── where things stand, and the next step ──────────────────────────────────
|
||||||
|
echo
|
||||||
|
case "$state" in
|
||||||
|
none)
|
||||||
|
echo "${CLUSTER} does not exist yet. 'make cluster up' creates it${ADDONS:+ (with ${ADDONS})},"
|
||||||
|
echo "then 'make tilt' starts the dev loop." ;;
|
||||||
|
stopped)
|
||||||
|
echo "${CLUSTER} exists but is stopped (nothing is lost). Start it again with:"
|
||||||
|
echo " docker start \$(docker ps -aq -f label=io.x-k8s.kind.cluster=${CLUSTER})" ;;
|
||||||
|
up)
|
||||||
|
if [ -n "$tilt_pid" ]; then
|
||||||
|
echo "${CLUSTER} is up, and its Tilt is running (pid ${tilt_pid}): http://localhost:${TILT_PORT}"
|
||||||
|
else
|
||||||
|
echo "${CLUSTER} is up. 'make tilt' starts the dev loop, at http://localhost:${TILT_PORT}."
|
||||||
|
fi
|
||||||
|
echo "Through the gateway: http://localhost:${HTTP_PORT}" ;;
|
||||||
|
*)
|
||||||
|
echo "docker is not reachable, so the cluster's state is unknown (the machine block below says why)." ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ── the configuration, as it is ────────────────────────────────────────────
|
||||||
|
echo
|
||||||
|
bash ./deps.sh detect ${VERBOSE:+all}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "config"
|
||||||
|
echo " cluster ${CLUSTER} ($([ "$state" = none ] && echo 'not created' || echo "$state"))"
|
||||||
|
echo " context ${KUBECONTEXT}"
|
||||||
|
if [ -n "${OVERLAY:-}" ]; then
|
||||||
|
echo " overlay $(_abs_from_ctrl "$OVERLAY_DIR")"
|
||||||
|
elif [ -n "$OVERLAY_DIR" ]; then
|
||||||
|
echo " overlay none named — rig's own ${OVERLAY_DIR}"
|
||||||
|
fi
|
||||||
|
if [ -n "$VERBOSE" ] && [ -n "$OVERLAY_DIR" ]; then
|
||||||
|
ov=$(_from_ctrl "$OVERLAY_DIR") pieces=""
|
||||||
|
for piece in rig.env k8s/overlays/dev kind-config.yaml.tpl addons Tiltfile; do
|
||||||
|
[ -e "$ov/$piece" ] && pieces+="$piece "
|
||||||
|
done
|
||||||
|
echo " provides: ${pieces:-nothing rig reads}"
|
||||||
|
fi
|
||||||
|
echo " profile ${PROFILE_NAME}"
|
||||||
|
echo " nodes ${NODES}"
|
||||||
|
echo " kind config ${KIND_CONFIG}"
|
||||||
|
echo " manifests ${MANIFESTS_DIR:-none}"
|
||||||
|
echo " ingress ${INGRESS_MODE}"
|
||||||
|
if [ ! -f ./.env ]; then
|
||||||
|
fact " .env none — built-in defaults (cp ctrl/.env.example ctrl/.env to set values)"
|
||||||
|
fi
|
||||||
|
if [ -n "${STALE_MANIFESTS_DIR:-}" ]; then
|
||||||
|
echo " ! .env MANIFESTS_DIR=${STALE_MANIFESTS_DIR} is the old default; rig's examples moved"
|
||||||
|
echo " to examples/ — delete that line from ctrl/.env (ignored until then)"
|
||||||
|
fi
|
||||||
|
# registry.sh points containerd at certs.d, which only works if the kind config says so,
|
||||||
|
# and a kind config is fixed at creation: a project's own file that drops it fails silently.
|
||||||
|
if [ "$REGISTRY_MODE" != none ] && ! grep -q 'config_path *= *"/etc/containerd/certs.d"' "$KIND_CONFIG"; then
|
||||||
|
echo " ! kind ${KIND_CONFIG} lacks the containerd config_path patch that registry mode"
|
||||||
|
echo " '${REGISTRY_MODE}' needs — copy it from ctrl/k8s/kind-config.yaml.tpl"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── ports: checked before creation; docker reports a clash only halfway through. ──
|
# ── ports: checked before creation; docker reports a clash only halfway through. ──
|
||||||
# Ports held by our own cluster are not clashes. Second grep, not `tr -d ':->'` (a tr range).
|
# Ports held by our own cluster are not clashes: its node containers, and its registry,
|
||||||
ours=$(docker ps --filter "label=io.x-k8s.kind.cluster=${CLUSTER}" \
|
# which is a container of its own. Second grep, not `tr -d ':->'` (a tr range).
|
||||||
--format '{{.Ports}}' 2>/dev/null | tr ',' '\n' \
|
ours=$({ docker ps --filter "label=io.x-k8s.kind.cluster=${CLUSTER}" --format '{{.Ports}}'
|
||||||
| grep -oE ':[0-9]+->' | grep -oE '[0-9]+' || true)
|
docker ps --filter "name=^${CLUSTER}-registry\$" --format '{{.Ports}}'; } 2>/dev/null \
|
||||||
|
| tr ',' '\n' | grep -oE ':[0-9]+->' | grep -oE '[0-9]+' || true)
|
||||||
clash=0 list="" mine=0
|
|
||||||
for entry in "HTTP:${HTTP_PORT}" "HTTPS:${HTTPS_PORT}" \
|
clash=0 list=""
|
||||||
"TILT:${TILT_PORT}" "REGISTRY:${REGISTRY_PORT}"; do
|
for entry in "http:${HTTP_PORT}" "https:${HTTPS_PORT}" \
|
||||||
|
"tilt:${TILT_PORT}" "registry:${REGISTRY_PORT}"; do
|
||||||
name="${entry%%:*}"; p="${entry#*:}"
|
name="${entry%%:*}"; p="${entry#*:}"
|
||||||
[ -n "$p" ] || continue
|
[ -n "$p" ] || continue
|
||||||
list+="$p "
|
list+="$name $p "
|
||||||
if ! port_busy "$p"; then
|
if ! port_busy "$p"; then
|
||||||
fact "$(printf " %-9s %-6s free" "$name" "$p")"
|
fact "$(printf " %-9s %-6s free" "$name" "$p")"
|
||||||
elif echo "$ours" | grep -qx "$p"; then
|
elif echo "$ours" | grep -qx "$p"; then
|
||||||
mine=1
|
|
||||||
fact "$(printf " %-9s %-6s in use by this environment's cluster" "$name" "$p")"
|
fact "$(printf " %-9s %-6s in use by this environment's cluster" "$name" "$p")"
|
||||||
|
elif [ "$name" = tilt ] && [ -n "$tilt_pid" ]; then
|
||||||
|
fact "$(printf " %-9s %-6s in use by this environment's Tilt" "$name" "$p")"
|
||||||
else
|
else
|
||||||
printf " ! ports %s %s IN USE by something else\n" "$name" "$p"
|
owner=$(port_owner "$p")
|
||||||
|
if [ "$name" = tilt ] && [ "${owner#* }" = tilt ]; then
|
||||||
|
printf " ! ports tilt %s is held by another Tilt (pid %s, in %s)\n" "$p" "${owner%% *}" \
|
||||||
|
"$(readlink "/proc/${owner%% *}/cwd" 2>/dev/null || echo '?')"
|
||||||
|
echo " stop it (kill ${owner%% *}) before 'make tilt'"
|
||||||
|
else
|
||||||
|
printf " ! ports %s %s IN USE by %s\n" "$name" "$p" "${owner:+pid ${owner}}${owner:-something else}"
|
||||||
clash=1
|
clash=1
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
echo " ports ${list% }"
|
||||||
if [ "$clash" -eq 1 ]; then
|
if [ "$clash" -eq 1 ]; then
|
||||||
echo " override it in ctrl/.env (e.g. HTTP_PORT=21080), or rename this directory"
|
echo " override it in ctrl/.env (e.g. HTTP_PORT=21080), or rename this directory"
|
||||||
elif [ "$mine" -eq 1 ]; then
|
|
||||||
echo " ports ${list% } held by this cluster"
|
|
||||||
else
|
|
||||||
echo " ports ${list% } free"
|
|
||||||
fi
|
fi
|
||||||
if [ -n "${OVERLAY:-}" ]; then
|
|
||||||
fact " derived from the overlay's folder name"
|
|
||||||
else
|
|
||||||
fact " derived from the directory name; pin them: bash ctrl/ports.sh persist"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── what `make cluster up` wires in beside the cluster ─────────────────────
|
# ── what `make cluster up` wires in beside the cluster ─────────────────────
|
||||||
REG_NAME="${CLUSTER}-registry"
|
REG_NAME="${CLUSTER}-registry"
|
||||||
if state=$(docker inspect -f '{{.State.Status}}' "$REG_NAME" 2>/dev/null); then
|
if reg_state=$(docker inspect -f '{{.State.Status}}' "$REG_NAME" 2>/dev/null); then
|
||||||
echo " registry localhost:${REGISTRY_PORT} ($state)"
|
echo " registry ${REGISTRY_MODE} localhost:${REGISTRY_PORT} (${reg_state})"
|
||||||
else
|
else
|
||||||
fact " registry no container yet — 'make cluster up' starts it"
|
echo " registry ${REGISTRY_MODE} (no container yet)"
|
||||||
fi
|
fi
|
||||||
echo " addons ${ADDONS:-none}"
|
echo " addons ${ADDONS:-none}"
|
||||||
if [ -n "$VERBOSE" ]; then
|
if [ -n "$VERBOSE" ]; then
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Toolchain installer: detect the host, install pinned tools into $OUT_BIN, report
|
# 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`.
|
# 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]
|
# 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
|
# Notes: docs/notes/deps.md
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -27,6 +27,14 @@ abspath() {
|
|||||||
|
|
||||||
OUT_BIN="${OUT_BIN:-$HOME/.local/bin}"
|
OUT_BIN="${OUT_BIN:-$HOME/.local/bin}"
|
||||||
HOST_ROOT="${HOST_ROOT:-/}"
|
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_SOURCE="${DEPS_SOURCE:-upstream}"
|
||||||
DEPS_ARTIFACTORY_URL="${DEPS_ARTIFACTORY_URL:-}"
|
DEPS_ARTIFACTORY_URL="${DEPS_ARTIFACTORY_URL:-}"
|
||||||
BAKED_BIN="${BAKED_BIN:-/opt/rig/bin}"
|
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.
|
# Windows outside WSL (Git Bash, MSYS, Cygwin) fails confusingly; name it instead.
|
||||||
require_linux() {
|
require_linux() {
|
||||||
case "$(uname -s)" in
|
case "${UNAME_S:-$(uname -s)}" in
|
||||||
MINGW*|MSYS*|CYGWIN*)
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
cat >&2 <<'EOF'
|
cat >&2 <<'EOF'
|
||||||
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
||||||
@@ -157,7 +165,7 @@ EOF
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
is_wsl() { grep -qi microsoft /proc/version 2>/dev/null; }
|
is_wsl() { grep -qi microsoft "$(host_file /proc/version)" 2>/dev/null; }
|
||||||
|
|
||||||
detect() {
|
detect() {
|
||||||
echo "host"
|
echo "host"
|
||||||
@@ -765,6 +773,54 @@ fetch_manifests() { # [--to DIR]
|
|||||||
done
|
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.
|
# Baked mode copies binaries already in the image, so it needs no downloader.
|
||||||
need_downloads() {
|
need_downloads() {
|
||||||
require_amd64
|
require_amd64
|
||||||
@@ -781,10 +837,12 @@ case "$cmd" in
|
|||||||
manifest) need_downloads
|
manifest) need_downloads
|
||||||
fetch_manifest "${1:?usage: $0 manifest <METALLB|CERT_MANAGER|METRICS_SERVER>}" "$MANIFESTS_HOME" ;;
|
fetch_manifest "${1:?usage: $0 manifest <METALLB|CERT_MANAGER|METRICS_SERVER>}" "$MANIFESTS_HOME" ;;
|
||||||
manifests) need_downloads; fetch_manifests "$@" ;;
|
manifests) need_downloads; fetch_manifests "$@" ;;
|
||||||
|
snapshot) snapshot "${1:-}" ;;
|
||||||
*) echo "usage: $0 [detect [all]|list|verify|fetch|install|manifest NAME|manifests]" >&2
|
*) echo "usage: $0 [detect [all]|list|verify|fetch|install|manifest NAME|manifests]" >&2
|
||||||
echo " install [core|dev] (default dev)" >&2
|
echo " install [core|dev] (default dev)" >&2
|
||||||
echo " fetch [core|dev] [--to DIR]" >&2
|
echo " fetch [core|dev] [--to DIR]" >&2
|
||||||
echo " manifests [--to DIR] the addons' pinned manifests, verified" >&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
|
echo " OUT_BIN=<dir> overrides the install directory" >&2
|
||||||
exit 1 ;;
|
exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -16,12 +16,30 @@ serve() {
|
|||||||
echo "no docs/index.html" >&2
|
echo "no docs/index.html" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
# One worker and no access log: a few static pages need no more, and the
|
||||||
|
# terminal then shows only real errors. The config is a temp file, gone on exit.
|
||||||
|
# Global, not local: the trap runs after serve() has returned.
|
||||||
|
DOCS_CONF=$(mktemp)
|
||||||
|
trap 'rm -f "$DOCS_CONF"' EXIT
|
||||||
|
cat > "$DOCS_CONF" <<'NGINX'
|
||||||
|
worker_processes 1;
|
||||||
|
error_log stderr error;
|
||||||
|
events { worker_connections 64; }
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
access_log off;
|
||||||
|
server { listen 80; root /usr/share/nginx/html; }
|
||||||
|
}
|
||||||
|
NGINX
|
||||||
|
chmod 644 "$DOCS_CONF"
|
||||||
echo "docs for '$CLUSTER' on http://localhost:${DOCS_PORT}"
|
echo "docs for '$CLUSTER' on http://localhost:${DOCS_PORT}"
|
||||||
echo " (ctrl-c to stop; nothing is installed and nothing persists)"
|
echo " (ctrl-c to stop; nothing is installed and nothing persists)"
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--name "${CLUSTER}-docs" \
|
--name "${CLUSTER}-docs" \
|
||||||
|
-e NGINX_ENTRYPOINT_QUIET_LOGS=1 \
|
||||||
-p "${DOCS_PORT}:80" \
|
-p "${DOCS_PORT}:80" \
|
||||||
-v "$REPO/docs:/usr/share/nginx/html:ro" \
|
-v "$REPO/docs:/usr/share/nginx/html:ro" \
|
||||||
|
-v "$DOCS_CONF:/etc/nginx/nginx.conf:ro" \
|
||||||
nginx:alpine
|
nginx:alpine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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"
|
||||||
@@ -208,6 +208,29 @@ render_kind_config() {
|
|||||||
"$KIND_CONFIG"
|
"$KIND_CONFIG"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Render an addon's manifest (a .yaml beside its .sh) to stdout. Every ${NAME}, or
|
||||||
|
# ${NAME:-default}, is filled from the loaded config or the calling script's variables;
|
||||||
|
# a NAME with neither is an error, so a typo never becomes an empty image. $VAR and
|
||||||
|
# $(VAR) are left alone: they belong to the container. Notes: docs/notes/addons.md
|
||||||
|
render_manifest() {
|
||||||
|
local file="$1" text ref key def val
|
||||||
|
text=$(<"$file") || return 1
|
||||||
|
while IFS= read -r ref; do
|
||||||
|
key=${ref#\$\{}; key=${key%\}}; def=""
|
||||||
|
if [[ $key == *:-* ]]; then def=${key#*:-}; key=${key%%:-*}; fi
|
||||||
|
if [ -n "${!key:-}" ]; then
|
||||||
|
val=${!key}
|
||||||
|
elif [[ $ref == *:-* ]]; then
|
||||||
|
val=$def
|
||||||
|
else
|
||||||
|
echo "render_manifest: $file needs \${$key}, which nothing sets" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
text=${text//"$ref"/"$val"}
|
||||||
|
done < <(grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}' "$file" | sort -u)
|
||||||
|
printf '%s\n' "$text"
|
||||||
|
}
|
||||||
|
|
||||||
_config_restore() {
|
_config_restore() {
|
||||||
local line
|
local line
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
|
|||||||
@@ -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.
|
# Refuse Git Bash / MSYS / Cygwin and kernels without /proc, with a clear message.
|
||||||
require_linux() {
|
require_linux() {
|
||||||
case "$(uname -s)" in
|
case "${UNAME_S:-$(uname -s)}" in
|
||||||
MINGW*|MSYS*|CYGWIN*)
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
cat >&2 <<'EOF'
|
cat >&2 <<'EOF'
|
||||||
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# What rig has settled, written down as assertions: one decision per check.
|
# What rig has settled, written down as assertions: one decision per check.
|
||||||
# No cluster, docker or network; exits 1 on failure (unlike `make 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
|
# Notes: docs/notes/selftest.md
|
||||||
set -uo pipefail # NOT -e: one failing check must not abort the rest
|
set -uo pipefail # NOT -e: one failing check must not abort the rest
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
if [ "${1:-}" = install ]; then shift; exec bash ./installtest.sh "$@"; fi
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
|
|
||||||
rc=0
|
rc=0
|
||||||
@@ -304,7 +307,7 @@ HOUSE_PAT="$(printf '%s' 'semes' 'ter|local' '\.ar\b')"
|
|||||||
check "✖ S2 no example environment name from the copies era" "0" \
|
check "✖ S2 no example environment name from the copies era" "0" \
|
||||||
"$(cd .. && grep -rIlE "$COPIES_PAT" . --exclude-dir=def --exclude-dir=local --exclude=STALE.md 2>/dev/null | wc -l)"
|
"$(cd .. && grep -rIlE "$COPIES_PAT" . --exclude-dir=def --exclude-dir=local --exclude=STALE.md 2>/dev/null | wc -l)"
|
||||||
check "✖ S3 ctrl/addons makes the cluster work, nothing more" "cert-manager metallb metrics-server" \
|
check "✖ S3 ctrl/addons makes the cluster work, nothing more" "cert-manager metallb metrics-server" \
|
||||||
"$(ls addons/ | sed 's/\.sh$//' | sort | xargs)"
|
"$(ls addons/*.sh | xargs -n1 basename | sed 's/\.sh$//' | sort | xargs)" # the scripts; their manifests sit beside them
|
||||||
check "✖ S3 versions.env pins no workload image" "0" \
|
check "✖ S3 versions.env pins no workload image" "0" \
|
||||||
"$(grep -cE '^(POSTGRES|REDIS|AIRFLOW)_IMAGE=' versions.env)"
|
"$(grep -cE '^(POSTGRES|REDIS|AIRFLOW)_IMAGE=' versions.env)"
|
||||||
check "✖ S4 no namespace named after the cluster" "0" "$(grep -c "CLUSTER + ':namespace'" Tiltfile)"
|
check "✖ S4 no namespace named after the cluster" "0" "$(grep -c "CLUSTER + ':namespace'" Tiltfile)"
|
||||||
@@ -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)"
|
"$(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"
|
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
|
# 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.
|
# kubectl that swallows `apply`: the Tiltfile evaluates for real, nothing is contacted.
|
||||||
@@ -355,6 +378,16 @@ note "the examples are overlays that work as shipped"
|
|||||||
bad=""
|
bad=""
|
||||||
for f in ../examples/*/addons/*.sh; do [ -e "$f" ] && { bash -n "$f" 2>/dev/null || bad+="$f "; }; done
|
for f in ../examples/*/addons/*.sh; do [ -e "$f" ] && { bash -n "$f" 2>/dev/null || bad+="$f "; }; done
|
||||||
check "every example addon parses" "" "$bad"
|
check "every example addon parses" "" "$bad"
|
||||||
|
# An addon's manifest may only name what the config or its own script sets, so a typo
|
||||||
|
# fails here rather than as an empty value on a cluster.
|
||||||
|
bad=""
|
||||||
|
for y in addons/*.yaml ../examples/*/addons/*.yaml; do
|
||||||
|
[ -e "$y" ] || continue
|
||||||
|
for n in $(grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*\}' "$y" | tr -d '${}' | sort -u); do
|
||||||
|
grep -qE "(^|[[:space:]])$n=" "${y%.yaml}.sh" versions.env ../examples/*/rig.env 2>/dev/null || bad+="$y:$n "
|
||||||
|
done
|
||||||
|
done
|
||||||
|
check "every addon manifest names only what is set" "" "$bad"
|
||||||
if command -v python3 >/dev/null; then
|
if command -v python3 >/dev/null; then
|
||||||
bad=""
|
bad=""
|
||||||
for f in ../examples/*/dags/*.py; do
|
for f in ../examples/*/dags/*.py; do
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
// TODO: PLACEHOLDER — replace with the real estate topology.
|
|
||||||
//
|
|
||||||
// This is where the extracted platform diagrams land. The shape below is
|
|
||||||
// illustrative only: it shows how a mocked dependency, a real service and a
|
|
||||||
// remote system are meant to sit together, not what the system actually is.
|
|
||||||
//
|
|
||||||
// The intended end state is that this file stops being hand-written and is
|
|
||||||
// generated from the running cluster, so the diagram becomes a report of what
|
|
||||||
// exists rather than a drawing of what was once intended.
|
|
||||||
digraph estate {
|
|
||||||
rankdir=LR
|
|
||||||
bgcolor="#0a0e17"
|
|
||||||
fontname="Helvetica"
|
|
||||||
node [fontname="Helvetica" fontsize=11 style=filled color="#1e2a4a" fontcolor="#e8eaf0" shape=box]
|
|
||||||
edge [fontname="Helvetica" fontsize=9 fontcolor="#8892a8" color="#4a5568"]
|
|
||||||
|
|
||||||
label="Estate topology — PLACEHOLDER"
|
|
||||||
labelloc=t
|
|
||||||
fontsize=16
|
|
||||||
fontcolor="#ffc107"
|
|
||||||
|
|
||||||
subgraph cluster_new {
|
|
||||||
label="New"
|
|
||||||
style=dashed
|
|
||||||
color="#1e2a4a"
|
|
||||||
fontcolor="#8892a8"
|
|
||||||
|
|
||||||
api [label="service under work\n(real: built and hot-reloaded)" fillcolor="#1a3a1a" fontcolor="#00c853"]
|
|
||||||
}
|
|
||||||
|
|
||||||
subgraph cluster_core {
|
|
||||||
label="Core (mocked)"
|
|
||||||
style=dashed
|
|
||||||
color="#1e2a4a"
|
|
||||||
fontcolor="#8892a8"
|
|
||||||
|
|
||||||
svc_a [label="upstream service\n(mock: canned responses)" fillcolor="#121829"]
|
|
||||||
db [label="datastore\n(mock)" fillcolor="#121829" shape=cylinder]
|
|
||||||
}
|
|
||||||
|
|
||||||
subgraph cluster_legacy {
|
|
||||||
label="Legacy estate (mocked)"
|
|
||||||
style=dashed
|
|
||||||
color="#1e2a4a"
|
|
||||||
fontcolor="#8892a8"
|
|
||||||
|
|
||||||
batch [label="batch drop\n(mock: writes files on a timer)" fillcolor="#121829"]
|
|
||||||
}
|
|
||||||
|
|
||||||
remote [label="external system\n(remote: ExternalName,\nreachable only from a VDI)" fillcolor="#3a1a1a" fontcolor="#ffc107" shape=octagon]
|
|
||||||
|
|
||||||
api -> svc_a [label="HTTP"]
|
|
||||||
api -> db [label="query"]
|
|
||||||
api -> batch [label="file handoff" style=dashed]
|
|
||||||
api -> remote [label="only when reachable" style=dashed color="#ffc107"]
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
|
||||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<!-- Generated by graphviz version 14.1.2 (0)
|
|
||||||
-->
|
|
||||||
<!-- Title: estate Pages: 1 -->
|
|
||||||
<svg width="579pt" height="362pt"
|
|
||||||
viewBox="0.00 0.00 579.00 362.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 357.62)">
|
|
||||||
<title>estate</title>
|
|
||||||
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-357.62 575.41,-357.62 575.41,4 -4,4"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="285.7" y="-334.42" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#ffc107">Estate topology — PLACEHOLDER</text>
|
|
||||||
<g id="clust1" class="cluster">
|
|
||||||
<title>cluster_new</title>
|
|
||||||
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="8,-130.12 8,-210.12 199,-210.12 199,-130.12 8,-130.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-190.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">New</text>
|
|
||||||
</g>
|
|
||||||
<g id="clust2" class="cluster">
|
|
||||||
<title>cluster_core</title>
|
|
||||||
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="357.33,-172.12 357.33,-318.12 534.83,-318.12 534.83,-172.12 357.33,-172.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-298.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Core (mocked)</text>
|
|
||||||
</g>
|
|
||||||
<g id="clust3" class="cluster">
|
|
||||||
<title>cluster_legacy</title>
|
|
||||||
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="341.83,-84.12 341.83,-164.12 551.33,-164.12 551.33,-84.12 341.83,-84.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.58" y="-144.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Legacy estate (mocked)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api -->
|
|
||||||
<g id="node1" class="node">
|
|
||||||
<title>api</title>
|
|
||||||
<polygon fill="#1a3a1a" stroke="#1e2a4a" points="191,-174.12 16,-174.12 16,-138.12 191,-138.12 191,-174.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-159.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">service under work</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-145.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">(real: built and hot-reloaded)</text>
|
|
||||||
</g>
|
|
||||||
<!-- svc_a -->
|
|
||||||
<g id="node2" class="node">
|
|
||||||
<title>svc_a</title>
|
|
||||||
<polygon fill="#121829" stroke="#1e2a4a" points="526.83,-282.12 365.33,-282.12 365.33,-246.12 526.83,-246.12 526.83,-282.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-267.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">upstream service</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-253.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock: canned responses)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api->svc_a -->
|
|
||||||
<g id="edge1" class="edge">
|
|
||||||
<title>api->svc_a</title>
|
|
||||||
<path fill="none" stroke="#4a5568" d="M149.07,-174.56C167.54,-182.07 189.23,-190.7 209,-198.12 258.25,-216.6 270.12,-222.85 320.75,-237.12 331.43,-240.13 342.71,-243 353.94,-245.67"/>
|
|
||||||
<polygon fill="#4a5568" stroke="#4a5568" points="353.12,-249.07 363.66,-247.92 354.71,-242.25 353.12,-249.07"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-234.24" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">HTTP</text>
|
|
||||||
</g>
|
|
||||||
<!-- db -->
|
|
||||||
<g id="node3" class="node">
|
|
||||||
<title>db</title>
|
|
||||||
<path fill="#121829" stroke="#1e2a4a" d="M480.7,-223.81C480.7,-226.22 465.18,-228.18 446.08,-228.18 426.97,-228.18 411.45,-226.22 411.45,-223.81 411.45,-223.81 411.45,-184.43 411.45,-184.43 411.45,-182.02 426.97,-180.06 446.08,-180.06 465.18,-180.06 480.7,-182.02 480.7,-184.43 480.7,-184.43 480.7,-223.81 480.7,-223.81"/>
|
|
||||||
<path fill="none" stroke="#1e2a4a" d="M480.7,-223.81C480.7,-221.39 465.18,-219.43 446.08,-219.43 426.97,-219.43 411.45,-221.39 411.45,-223.81"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-207.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">datastore</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-193.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api->db -->
|
|
||||||
<g id="edge2" class="edge">
|
|
||||||
<title>api->db</title>
|
|
||||||
<path fill="none" stroke="#4a5568" d="M191.32,-168.36C257.83,-177.73 346.91,-190.28 399.91,-197.75"/>
|
|
||||||
<polygon fill="#4a5568" stroke="#4a5568" points="399.4,-201.22 409.79,-199.15 400.38,-194.29 399.4,-201.22"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-185.69" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">query</text>
|
|
||||||
</g>
|
|
||||||
<!-- batch -->
|
|
||||||
<g id="node4" class="node">
|
|
||||||
<title>batch</title>
|
|
||||||
<polygon fill="#121829" stroke="#1e2a4a" points="537.33,-128.12 354.83,-128.12 354.83,-92.12 537.33,-92.12 537.33,-128.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-113.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">batch drop</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-99.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock: writes files on a timer)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api->batch -->
|
|
||||||
<g id="edge3" class="edge">
|
|
||||||
<title>api->batch</title>
|
|
||||||
<path fill="none" stroke="#4a5568" stroke-dasharray="5,2" d="M191.32,-144.39C237.67,-138.13 294.98,-130.39 343.4,-123.85"/>
|
|
||||||
<polygon fill="#4a5568" stroke="#4a5568" points="343.61,-127.36 353.05,-122.55 342.67,-120.42 343.61,-127.36"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-143.94" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">file handoff</text>
|
|
||||||
</g>
|
|
||||||
<!-- remote -->
|
|
||||||
<g id="node5" class="node">
|
|
||||||
<title>remote</title>
|
|
||||||
<polygon fill="#3a1a1a" stroke="#1e2a4a" points="571.41,-21.74 571.41,-52.5 497.99,-74.24 394.17,-74.24 320.75,-52.5 320.75,-21.74 394.17,0 497.99,0 571.41,-21.74"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-46.92" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">external system</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-33.42" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">(remote: ExternalName,</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-19.92" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">reachable only from a VDI)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api->remote -->
|
|
||||||
<g id="edge4" class="edge">
|
|
||||||
<title>api->remote</title>
|
|
||||||
<path fill="none" stroke="#ffc107" stroke-dasharray="5,2" d="M149.55,-137.68C167.9,-130.34 189.37,-121.97 209,-114.87 254.45,-98.43 305.32,-81.49 348.17,-67.64"/>
|
|
||||||
<polygon fill="#ffc107" stroke="#ffc107" points="349,-71.05 357.44,-64.65 346.85,-64.39 349,-71.05"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-117.57" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">only when reachable</text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 6.9 KiB |
@@ -2,6 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>rig — local environment installer</title>
|
<title>rig — local environment installer</title>
|
||||||
<style>
|
<style>
|
||||||
@@ -237,13 +238,9 @@
|
|||||||
<div class="nav-backdrop"></div>
|
<div class="nav-backdrop"></div>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="#start">Start here</a>
|
<a href="#start">Start here</a>
|
||||||
<a href="#steps">The steps</a>
|
|
||||||
<a href="#install">Installation</a>
|
<a href="#install">Installation</a>
|
||||||
<a href="#environments">Environments</a>
|
<a href="#environments">Environments</a>
|
||||||
<a href="#overlays">Overlays</a>
|
<a href="#overlays">Overlays</a>
|
||||||
<a href="#profiles">Profiles</a>
|
|
||||||
<a href="#registry">Registry</a>
|
|
||||||
<a href="#architecture">Architecture</a>
|
|
||||||
<a href="#troubleshooting">Troubleshooting</a>
|
<a href="#troubleshooting">Troubleshooting</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -251,114 +248,68 @@
|
|||||||
|
|
||||||
<section class="section" id="start">
|
<section class="section" id="start">
|
||||||
<h2>Start here</h2>
|
<h2>Start here</h2>
|
||||||
<p class="lede">A runnable local model of a large, regulated estate — legacy and new side by side.</p>
|
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<p>rig builds a disposable Kubernetes environment on your machine so you can
|
<p>rig builds a disposable Kubernetes environment on your machine, so you can
|
||||||
explore how a system fits together without needing access to any of it. Its
|
see how a system fits together without access to any of it. It is for
|
||||||
job is <b>onboarding and exploration</b>, not a production replica.</p>
|
<b>onboarding and exploration</b>, not a production replica.</p>
|
||||||
|
|
||||||
<p>Most services in it are deliberately <b>not real</b>. What has to be faithful
|
<p>Most services in it are <b>not real</b>. What has to be faithful is the
|
||||||
is the topology — the names, the ports, the dependency order, who can reach whom,
|
topology: names, ports, dependency order, who can reach whom, and how it fails.
|
||||||
and how it fails. The workloads themselves are noise. This is what makes the
|
That is what makes a whole estate fit on a laptop. A real 20-service platform
|
||||||
whole estate fit on a laptop: a real 20-service platform will not fit even once
|
does not fit once in 14 GB; stubs of about 30 MB each fit three times.</p>
|
||||||
on 14 GB, but mocks are about 30 MB each, so three faithful copies do.</p>
|
|
||||||
|
|
||||||
<h3>The only prerequisite</h3>
|
<h3>The only prerequisite</h3>
|
||||||
<p><b>Docker.</b> No curl, no jq, no python, no apt repositories to configure.</p>
|
<p><b>Docker.</b> No curl, no jq, no python, no apt repositories to configure.</p>
|
||||||
<pre><code><span class="c"># then, in the environment directory:</span>
|
<pre><code><span class="c"># in the environment directory:</span>
|
||||||
make check <span class="c"># is this machine ready? reports, never fixes</span>
|
make check <span class="c"># is this machine ready? reports, never changes anything</span>
|
||||||
make deps <span class="c"># install the pinned toolchain</span>
|
make deps <span class="c"># install the pinned toolchain; safe to re-run</span>
|
||||||
make cluster up <span class="c"># cluster + registry + addons; ports derive by themselves</span>
|
make cluster up <span class="c"># cluster + registry + addons; ports derive by themselves</span>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Read <code>make check</code> before <code>make deps</code>. It never changes
|
<p>Read <code>make check</code> before <code>make deps</code>: it lists what is
|
||||||
anything — it prints what it found and, at the end, the steps it cannot perform
|
missing, including the steps only you can do, such as anything needing
|
||||||
for you.</p>
|
<code>sudo</code> or a Windows-side restart.</p>
|
||||||
</div>
|
|
||||||
</section>
|
<h3>make cluster up</h3>
|
||||||
|
<p>It prints what the kind config locks in before spending the time, because
|
||||||
<section class="section" id="steps">
|
that config is fixed when the cluster is created. Re-running is safe and
|
||||||
<h2>The steps</h2>
|
continues where an interrupted run stopped, rather than reporting that the
|
||||||
<p class="lede">Start to finish, in order, with what each one actually does.</p>
|
cluster already exists and leaving every node <code>NotReady</code>.</p>
|
||||||
<div class="prose">
|
|
||||||
|
|
||||||
<h3>1 · make check</h3>
|
|
||||||
<p>Asks whether this machine is ready. It <b>changes nothing</b> — it
|
|
||||||
reports what it found and, at the end, the things only a human can do
|
|
||||||
(anything needing <code>sudo</code>, or a Windows-side restart). Read it
|
|
||||||
before installing anything; it is faster than discovering the same problems
|
|
||||||
one failure at a time.</p>
|
|
||||||
<pre><code>make check</code></pre>
|
|
||||||
|
|
||||||
<h3>2 · make deps</h3>
|
|
||||||
<p>Installs the pinned toolchain — only what is missing — and tells you
|
|
||||||
if its directory is not on PATH yet. Running it twice is safe.</p>
|
|
||||||
<pre><code>make deps</code></pre>
|
|
||||||
|
|
||||||
<h3>3 · make cluster up</h3>
|
|
||||||
<p>Builds the cluster, starts its registry and installs the profile's
|
|
||||||
addons — there is nothing else to run first. It prints what the profile
|
|
||||||
locks in <i>before</i> spending the time, because the kind config is
|
|
||||||
fixed at creation and cannot be changed afterwards.</p>
|
|
||||||
<p>Re-running is safe and, more importantly, <b>convergent</b>: if a first
|
|
||||||
attempt was interrupted before the CNI was installed, running it again
|
|
||||||
finishes the job rather than reporting "already exists" and leaving every
|
|
||||||
node permanently NotReady.</p>
|
|
||||||
<pre><code>make cluster up <span class="c"># built-in defaults — no profile needed</span>
|
<pre><code>make cluster up <span class="c"># built-in defaults — no profile needed</span>
|
||||||
make cluster up PROFILE=mirror <span class="c"># after copying env.d/mirror.env.example: cached registry</span>
|
make cluster up PROFILE=mirror <span class="c"># images through an internal registry</span>
|
||||||
make cluster up OVERLAY=examples/data <span class="c"># an overlay: what runs, kept outside rig</span>
|
make cluster up OVERLAY=examples/data <span class="c"># an overlay: what runs, kept outside rig</span>
|
||||||
make cluster reset <span class="c"># destroy and rebuild — how an edited kind config takes effect</span>
|
make cluster reset <span class="c"># destroy and rebuild: how an edited kind config takes effect</span>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
<h3>4 · make docs</h3>
|
|
||||||
<p>Serves this page from a throwaway container. Works with no cluster and
|
|
||||||
no toolchain, which is deliberate: these pages are the instructions for
|
|
||||||
building everything else, so they cannot depend on it.</p>
|
|
||||||
<pre><code>make docs</code></pre>
|
|
||||||
|
|
||||||
<h3>Checking on things</h3>
|
<h3>Checking on things</h3>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>make cluster list</dt><dd>Every cluster on the machine, its memory cost and its port block. The usual reason a new one will not start is an old one you forgot about; <code>make cluster free</code> frees them without deleting.</dd>
|
<dt>make cluster list</dt><dd>Every cluster on the machine, its memory cost and its port block. A new cluster usually fails to start because of an old one; <code>make cluster free</code> stops them without deleting.</dd>
|
||||||
<dt>make check</dt><dd>Short: host, toolchain, and whether this cluster fits, its ports, registry and addons. Details only appear when something needs attention; <code>make check all</code> prints every one.</dd>
|
<dt>make check</dt><dd>Host, toolchain, and whether this cluster fits, its ports, registry and addons. Only problems print; <code>make check all</code> prints everything.</dd>
|
||||||
<dt>make check mem</dt><dd>Memory in depth: what caps it, how far it really climbs, and on WSL the <code>.wslconfig</code> backup and restore.</dd>
|
<dt>make check mem</dt><dd>Memory in depth: what caps it, how far it climbs, and on WSL the <code>.wslconfig</code> backup and restore.</dd>
|
||||||
|
<dt>make docs</dt><dd>Serves these pages from a throwaway container. It needs no cluster and no toolchain, because these are the instructions for building both.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h3>Running more than one</h3>
|
|
||||||
<p>Name another overlay, or copy the directory and rename it. Cluster name,
|
|
||||||
context, image tags and the port block all follow the folder name — the
|
|
||||||
overlay's, or rig's — so the second environment collides with nothing and
|
|
||||||
neither one's teardown can reach the other.</p>
|
|
||||||
<pre><code>OVERLAY=local/platform-v2 make cluster up
|
|
||||||
cp -r rig ../platform-v3 && cd ../platform-v3 && make cluster up
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section" id="install">
|
<section class="section" id="install">
|
||||||
<h2>Installation</h2>
|
<h2>Installation</h2>
|
||||||
<p class="lede">A container installs onto the host and then gets out of the way.</p>
|
|
||||||
<div class="graph-container">
|
<div class="graph-container">
|
||||||
<a href="viewer.html?src=graphs/01-install.svg"><img src="graphs/01-install.svg" alt="Installation flow"></a>
|
<a href="viewer.html?src=graphs/01-install.svg"><img src="graphs/01-install.svg" alt="How the installer works"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<p>The installer is a container, not a shell script, for a specific reason: a
|
<p>The installer is a container, not a shell script, because a stock slim Debian
|
||||||
stock slim Debian has no <code>curl</code>, no <code>wget</code>, no
|
has no curl, wget, jq, python3 or CA bundle: a shell installer could not make a
|
||||||
<code>jq</code>, no <code>python3</code> and <b>no CA bundle</b>. A shell
|
verified HTTPS request, let alone check one. The container carries its own
|
||||||
installer could not make a verified HTTPS request, let alone check one. The
|
toolchain, so the host needs only Docker. Nothing runs inside it afterwards —
|
||||||
container carries its own toolchain, so the host needs nothing but Docker.</p>
|
kind, kubectl, tilt and jq run natively.</p>
|
||||||
|
|
||||||
<p>The cluster never runs inside that container. Everything it installs —
|
<p>Every tool is a single binary at a pinned version, checked against a published
|
||||||
kind, kubectl, tilt, jq — runs natively afterwards, so nothing pays a
|
SHA256. Node images are pinned <b>by digest</b>, so upgrading kind cannot move
|
||||||
container tax during daily work.</p>
|
your Kubernetes version.</p>
|
||||||
|
|
||||||
<h3>Pinned and verified</h3>
|
<h3>Two tiers</h3>
|
||||||
<p>Every tool is a single binary fetched at a pinned version and checked
|
<p>A managed or corporate-issued machine is not somewhere to install development
|
||||||
against a published SHA256. Node images are pinned <b>by digest</b>, so
|
tools by default, so installing cluster tooling is a decision rather than a side
|
||||||
upgrading kind cannot silently move your Kubernetes version.</p>
|
effect:</p>
|
||||||
|
|
||||||
<h3>Not every machine should get cluster tooling</h3>
|
|
||||||
<p>A managed or corporate-issued machine — the kind that holds the access
|
|
||||||
you cannot get anywhere else — is not somewhere to install development
|
|
||||||
tools by default. So the toolchain comes in two tiers:</p>
|
|
||||||
<table>
|
<table>
|
||||||
<tr><th>tier</th><th>installs</th><th>for</th></tr>
|
<tr><th>tier</th><th>installs</th><th>for</th></tr>
|
||||||
<tr><td><code>core</code></td><td>kubectl, jq</td><td>talk to a cluster someone else runs</td></tr>
|
<tr><td><code>core</code></td><td>kubectl, jq</td><td>talk to a cluster someone else runs</td></tr>
|
||||||
@@ -367,11 +318,6 @@ cp -r rig ../platform-v3 && cd ../platform-v3 && make cluster up
|
|||||||
<pre><code>make deps core <span class="c"># kubectl and jq only — nothing that creates a cluster</span>
|
<pre><code>make deps core <span class="c"># kubectl and jq only — nothing that creates a cluster</span>
|
||||||
make deps <span class="c"># dev, the default</span>
|
make deps <span class="c"># dev, the default</span>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Testing <i>in situ</i> on a managed machine is still possible — install
|
|
||||||
the <code>dev</code> tier deliberately when you need it. The point is that
|
|
||||||
it should be a decision rather than a side effect of installing.</p>
|
|
||||||
<p>The documentation itself needs neither tier: <code>make docs</code>
|
|
||||||
wants only Docker.</p>
|
|
||||||
|
|
||||||
<h3>Air-gapped</h3>
|
<h3>Air-gapped</h3>
|
||||||
<pre><code>make deps image full <span class="c"># bakes every binary into the image</span>
|
<pre><code>make deps image full <span class="c"># bakes every binary into the image</span>
|
||||||
@@ -384,185 +330,129 @@ docker load < rig.tgz && make cluster up PROFILE=offline <span clas
|
|||||||
|
|
||||||
<section class="section" id="environments">
|
<section class="section" id="environments">
|
||||||
<h2>Environments</h2>
|
<h2>Environments</h2>
|
||||||
<p class="lede">One folder is one environment — an overlay's, or rig's own. Copy it, rename it, run it.</p>
|
|
||||||
<div class="graph-container">
|
<div class="graph-container">
|
||||||
<a href="viewer.html?src=graphs/02-environment.svg"><img src="graphs/02-environment.svg" alt="Environment derivation"></a>
|
<a href="viewer.html?src=graphs/02-environment.svg"><img src="graphs/02-environment.svg" alt="Environment derivation"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<p>Running several versions of a system at once means several clusters on one
|
<p>One folder is one environment. Running several versions of a system at once
|
||||||
machine, not several machines. Everything that could collide is derived from
|
means several clusters on one machine, and everything that could collide is
|
||||||
the folder name (the overlay's when one is named):</p>
|
derived from the folder name (the overlay's, when one is named):</p>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>cluster + context</dt><dd><code>platform-v2/</code> builds <code>platform-v2</code> on <code>kind-platform-v2</code>.</dd>
|
<dt>cluster + context</dt><dd><code>platform-v2/</code> builds <code>platform-v2</code> on <code>kind-platform-v2</code>.</dd>
|
||||||
<dt>port block</dt><dd>Ten ports from a hash of the name, in the 20000+ range — clear of 80, 443, 3000, 5432, 8000 and 8080.</dd>
|
<dt>port block</dt><dd>Ten ports from a hash of the name, above 20000 — clear of 80, 443, 3000, 5432, 8000 and 8080.</dd>
|
||||||
<dt>registry + images</dt><dd>Named after the environment, so two copies never share one.</dd>
|
<dt>registry + images</dt><dd>Named after the environment, so two copies never share one.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<p>Two copies therefore never collide, and neither one's
|
<p>So two copies never collide, and neither one's <code>make cluster down</code>
|
||||||
<code>make cluster down</code> can touch the other. <code>make check</code>
|
can touch the other. <code>make check</code> shows the block;
|
||||||
shows the block; <code>bash ctrl/ports.sh persist</code> freezes it into
|
<code>bash ctrl/ports.sh persist</code> freezes it into <code>ctrl/.env</code>.</p>
|
||||||
<code>ctrl/.env</code> if you want it fixed rather than derived.</p>
|
|
||||||
|
|
||||||
<h3>Configuration layers</h3>
|
<h3>Configuration layers</h3>
|
||||||
<p>Weakest first, later wins: built-in defaults → pinned versions → a
|
<p>Weakest first, later wins: built-in defaults → pinned versions → a profile, if
|
||||||
profile, if you name one → the overlay's <code>rig.env</code> →
|
you name one → the overlay's <code>rig.env</code> → <code>ctrl/.env</code> → the
|
||||||
<code>ctrl/.env</code> → the environment. So
|
caller's environment. So a value on the command line always beats every file.</p>
|
||||||
<code>make cluster up PROFILE=<name></code> always beats every file.</p>
|
|
||||||
|
<h3>Profiles</h3>
|
||||||
|
<p>A profile says how this machine reaches the world. rig needs none.</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>example</th><th>registry</th><th>for</th></tr>
|
||||||
|
<tr><td>none</td><td><code>local</code></td><td>the built-in defaults</td></tr>
|
||||||
|
<tr><td><code>mirror.env.example</code></td><td><code>mirror</code></td><td>images through an internal registry</td></tr>
|
||||||
|
<tr><td><code>offline.env.example</code></td><td><code>local</code></td><td>air-gapped</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Registry modes</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>mode</th><th>what it does</th></tr>
|
||||||
|
<tr><td><code>none</code></td><td>images are built straight into the node</td></tr>
|
||||||
|
<tr><td><code>local</code></td><td>a registry container wired into the cluster</td></tr>
|
||||||
|
<tr><td><code>mirror</code></td><td>that container as a pull-through cache of the corporate registry</td></tr>
|
||||||
|
<tr><td><code>remote</code></td><td>no local container; pull direct with an <code>imagePullSecret</code></td></tr>
|
||||||
|
</table>
|
||||||
|
<p><code>mirror</code> is what a locked-down network looks like: images come from
|
||||||
|
the corporate registry, you do not hammer it, and you keep working when the
|
||||||
|
connection drops. It is also only reachable from where that registry is routable —
|
||||||
|
from a laptop, use <code>local</code> or <code>offline</code>.</p>
|
||||||
|
<p><b>Corporate CA trust is the usual failure.</b> It has to reach three places:
|
||||||
|
the host Docker daemon, every node's containerd (nodes do not inherit host trust),
|
||||||
|
and any in-cluster client. Set <code>REGISTRY_CA_FILE</code>;
|
||||||
|
<code>make check</code> reports which is still missing. Without it the error is
|
||||||
|
<code>x509: certificate signed by unknown authority</code>.</p>
|
||||||
|
|
||||||
|
<h3>LoadBalancer services</h3>
|
||||||
|
<p>Real manifests use <code>type: LoadBalancer</code>. On a bare local cluster
|
||||||
|
those Services sit at <code>EXTERNAL-IP <pending></code> forever, with no
|
||||||
|
error: the deployment looks healthy and is unreachable. The <code>metallb</code>
|
||||||
|
addon fixes that, so upstream manifests work unchanged. Its address pool is
|
||||||
|
derived from the cluster's Docker network at install time, because Docker picks
|
||||||
|
that subnet and it differs between machines.</p>
|
||||||
|
<p>Those addresses are reachable from the Linux side, including inside WSL. A
|
||||||
|
browser on Windows has no route to them: use the ingress host ports instead.</p>
|
||||||
|
|
||||||
|
<h3>Networking and memory</h3>
|
||||||
|
<p>The cluster uses kind's built-in networking, which does enforce standard
|
||||||
|
NetworkPolicy — verified against a no-policy control. A pluggable CNI was tried
|
||||||
|
and removed: it added policy features nothing here needs, in exchange for a
|
||||||
|
slower boot and one more thing to get right at creation time.</p>
|
||||||
|
<p>Every cluster is a running container tree whether you use it or not.
|
||||||
|
<code>make cluster list</code> shows what each costs, and
|
||||||
|
<code>make cluster free</code> stops the others without deleting them.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section" id="overlays">
|
<section class="section" id="overlays">
|
||||||
<h2>Overlays</h2>
|
<h2>Overlays</h2>
|
||||||
<p class="lede">What runs lives outside rig — rig reads it and never writes into it.</p>
|
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<p>An overlay is one folder, kept outside rig's version control, holding a
|
<p>An overlay is one folder, kept outside rig's version control, holding a use
|
||||||
use case. Every piece is optional:</p>
|
case. rig reads it and never writes into it. Every piece is optional:</p>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>in the overlay</th><th>what rig does with it</th></tr>
|
<tr><th>in the overlay</th><th>what rig does with it</th></tr>
|
||||||
<tr><td><code>rig.env</code></td><td>a config layer: addons, namespaces, images — anything a profile could set</td></tr>
|
<tr><td><code>rig.env</code></td><td>a config layer: addons, namespaces, images — anything a profile could set</td></tr>
|
||||||
<tr><td><code>k8s/overlays/dev/</code></td><td>the manifests the dev loop applies</td></tr>
|
<tr><td><code>k8s/overlays/dev/</code></td><td>the manifests the dev loop applies</td></tr>
|
||||||
<tr><td><code>kind-config.yaml.tpl</code></td><td>the cluster's shape, when it needs its own (mounts, ports)</td></tr>
|
<tr><td><code>kind-config.yaml.tpl</code></td><td>the cluster's shape, when it needs its own (mounts, ports)</td></tr>
|
||||||
<tr><td><code>addons/<name>.sh</code></td><td>addons, found before rig's own</td></tr>
|
<tr><td><code>addons/<name>.sh</code></td><td>addons, found before rig's own; each applies its own <code><name>.yaml</code></td></tr>
|
||||||
<tr><td><code>Tiltfile</code></td><td>the workload's half of the dev loop, included by rig's</td></tr>
|
<tr><td><code>Tiltfile</code></td><td>the workload's half of the dev loop, included by rig's</td></tr>
|
||||||
</table>
|
</table>
|
||||||
<pre><code>cp -r examples/starter local/myenv <span class="c"># local/ is gitignored</span>
|
<pre><code>cp -r examples/starter local/myenv <span class="c"># local/ is gitignored</span>
|
||||||
OVERLAY=local/myenv make cluster up
|
OVERLAY=local/myenv make cluster up
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>With none named, rig runs its own <code>examples/starter</code>.
|
<p>With none named, rig runs its own <code>examples/starter</code>.
|
||||||
<code>examples/data</code> carries postgres, redis and airflow as an
|
<code>examples/data</code> carries postgres, redis and airflow as an overlay's own
|
||||||
overlay's own addons. A project can also carry rig at
|
addons. A project can also carry rig at <code><project>/rig/</code> and be
|
||||||
<code><project>/rig/</code> and be the overlay itself, with a
|
the overlay itself, with a three-line forwarding Makefile — see
|
||||||
three-line forwarding Makefile — see <code>docs/notes/overlay.md</code>.</p>
|
<code>docs/notes/overlay.md</code>.</p>
|
||||||
</div>
|
|
||||||
</section>
|
<h3>What runs in it</h3>
|
||||||
|
<p>Each component is one of three things, and switching between them is a
|
||||||
<section class="section" id="profiles">
|
one-line change:</p>
|
||||||
<h2>Profiles</h2>
|
|
||||||
<p class="lede">How this machine reaches the world — optional; rig needs none.</p>
|
|
||||||
<div class="prose">
|
|
||||||
<table>
|
|
||||||
<tr><th>example</th><th>registry</th><th>for</th></tr>
|
|
||||||
<tr><td><i>none</i></td><td>local</td><td>the built-in defaults; no profile needed</td></tr>
|
|
||||||
<tr><td><code>mirror.env.example</code></td><td>mirror</td><td>images through an internal registry</td></tr>
|
|
||||||
<tr><td><code>offline.env.example</code></td><td>local</td><td>air-gapped</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="note"><p><b>The kind config cannot be re-applied.</b> Edit
|
|
||||||
<code>ctrl/k8s/kind-config.yaml.tpl</code> (or the overlay's own); it takes effect when the cluster is created. <code>cluster up</code> prints what it
|
|
||||||
locks in before spending the time, and <code>make cluster reset</code> is
|
|
||||||
the way out.</p></div>
|
|
||||||
|
|
||||||
<h3>LoadBalancer services</h3>
|
|
||||||
<p>Real manifests use <code>type: LoadBalancer</code>, because a real
|
|
||||||
cluster has one. On a bare local cluster those Services sit at
|
|
||||||
<code>EXTERNAL-IP <pending></code> forever, with no error anywhere —
|
|
||||||
the deployment looks healthy and simply is not reachable.</p>
|
|
||||||
<p>The <code>metallb</code> addon fixes that, so the same manifests work
|
|
||||||
here as upstream and nothing has to be rewritten to NodePort. Its address
|
|
||||||
pool is derived from the cluster's Docker network at install time rather
|
|
||||||
than hardcoded, because Docker picks that subnet and it differs between
|
|
||||||
machines.</p>
|
|
||||||
<div class="note"><p><b>Where those addresses are reachable from.</b> The
|
|
||||||
pool lives on the Docker bridge, so LoadBalancer IPs work from the Linux
|
|
||||||
side — including from inside WSL. A browser on Windows has no route to
|
|
||||||
them. Use the ingress host ports for anything you need to open in a
|
|
||||||
browser.</p></div>
|
|
||||||
|
|
||||||
<h3>Networking</h3>
|
|
||||||
<p>The cluster uses kind's built-in networking, which <b>does</b> enforce
|
|
||||||
standard NetworkPolicy — verified against a no-policy control, not assumed.
|
|
||||||
The widely repeated claim that it accepts policies and silently ignores
|
|
||||||
them is out of date.</p>
|
|
||||||
<p>A pluggable CNI was tried and removed: it only added
|
|
||||||
GlobalNetworkPolicy, policy tiers and egress-CIDR rules, none of which are
|
|
||||||
needed yet, in exchange for a slower boot and one more thing that has to be
|
|
||||||
right at creation time. Worth revisiting only when a policy the built-in
|
|
||||||
cannot express actually comes up.</p>
|
|
||||||
|
|
||||||
<h3>Memory</h3>
|
|
||||||
<p>Every cluster is a running container tree whether you are using it or not.
|
|
||||||
<code>make cluster list</code> shows what exists and what it costs;
|
|
||||||
<code>make cluster free</code> stops the others without deleting them.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section" id="registry">
|
|
||||||
<h2>Registry</h2>
|
|
||||||
<p class="lede">Local, cached, or straight to the corporate registry.</p>
|
|
||||||
<div class="prose">
|
|
||||||
<table>
|
|
||||||
<tr><th>mode</th><th>what it does</th></tr>
|
|
||||||
<tr><td><code>none</code></td><td>images are built straight into the node</td></tr>
|
|
||||||
<tr><td><code>local</code></td><td>a registry container wired into the cluster</td></tr>
|
|
||||||
<tr><td><code>mirror</code></td><td>that container as a <b>pull-through cache</b> of the corporate registry</td></tr>
|
|
||||||
<tr><td><code>remote</code></td><td>no local container; pull direct with an imagePullSecret</td></tr>
|
|
||||||
</table>
|
|
||||||
<p><code>mirror</code> is what a locked-down network actually looks like:
|
|
||||||
images originate from the corporate registry, you do not hammer it, and you
|
|
||||||
keep working when the connection drops.</p>
|
|
||||||
|
|
||||||
<div class="note"><p><b>The corporate CA will bite you.</b> A corporate
|
|
||||||
registry is usually behind an internal CA, and trust has to reach
|
|
||||||
<b>three</b> places: the host Docker daemon, every cluster node's containerd
|
|
||||||
(nodes do <i>not</i> inherit host trust), and any in-cluster client. Set
|
|
||||||
<code>REGISTRY_CA_FILE</code> and <code>make check</code> reports which is
|
|
||||||
still missing. The symptom otherwise is an opaque
|
|
||||||
<code>x509: certificate signed by unknown authority</code>.</p></div>
|
|
||||||
|
|
||||||
<p>Reachability also depends on where you are: if the registry is only
|
|
||||||
routable from a managed workspace, <code>mirror</code> and <code>remote</code>
|
|
||||||
will not resolve from a laptop at all. That is what <code>local</code> and
|
|
||||||
<code>offline</code> are for.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section" id="architecture">
|
|
||||||
<h2>Architecture</h2>
|
|
||||||
<p class="lede">The estate being modelled.</p>
|
|
||||||
<div class="note"><p><b>TODO — placeholder.</b> The diagram below is
|
|
||||||
illustrative only: it shows how a mocked dependency, a service under active
|
|
||||||
work, and an unreachable remote system sit together. It is not the real
|
|
||||||
topology. Replace <code>docs/graphs/03-architecture.dot</code> with the
|
|
||||||
extracted platform diagrams, then run <code>make docs graphs</code>.</p></div>
|
|
||||||
<div class="graph-container">
|
|
||||||
<a href="viewer.html?src=graphs/03-architecture.svg"><img src="graphs/03-architecture.svg" alt="Estate topology (placeholder)"></a>
|
|
||||||
</div>
|
|
||||||
<div class="prose">
|
|
||||||
<p>Each component is one of three things, and switching between them should be
|
|
||||||
a one-line change rather than a rewrite:</p>
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt>real</dt><dd>Built from source and hot-reloaded. The thing you are actually working on — usually exactly one.</dd>
|
<dt>real</dt><dd>Built from source and hot-reloaded. What you are working on — usually exactly one.</dd>
|
||||||
<dt>mock</dt><dd>A generic stub with canned responses. Everything you do not care about today.</dd>
|
<dt>mock</dt><dd>A generic stub with canned responses. Everything you do not care about today.</dd>
|
||||||
<dt>remote</dt><dd>No pod at all: a Service of type ExternalName pointing at the real system. In-cluster DNS resolves identically, so callers never change.</dd>
|
<dt>remote</dt><dd>No pod at all: a Service of type <code>ExternalName</code> pointing at the real system. In-cluster DNS resolves identically, so callers never change.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<p>The intended end state is that this diagram is <b>generated from the
|
|
||||||
running cluster</b> rather than drawn by hand — so it becomes a report of
|
|
||||||
what exists instead of a picture of what was once intended.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section" id="troubleshooting">
|
<section class="section" id="troubleshooting">
|
||||||
<h2>Troubleshooting</h2>
|
<h2>Troubleshooting</h2>
|
||||||
<p class="lede">The failures that are hard to diagnose from their symptoms.</p>
|
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h3>Tilt stops noticing file changes</h3>
|
<h3>Tilt stops noticing file changes</h3>
|
||||||
<p>Almost always <code>inotify</code> limits, and it fails <i>silently</i> —
|
<p>Almost always inotify limits, and it fails silently: nothing errors, changes
|
||||||
nothing errors, changes just stop being picked up. Defaults on WSL are far too
|
just stop being picked up. Defaults on WSL are far too low.
|
||||||
low. <code>make check</code> reports it and prints the fix.</p>
|
<code>make check</code> reports it and prints the fix.</p>
|
||||||
|
|
||||||
<h3>Cluster creation dies halfway with a port error</h3>
|
<h3>Cluster creation dies halfway with a port error</h3>
|
||||||
<p>Docker reports <code>failed to bind host port … address already in use</code>
|
<p>Docker reports <code>failed to bind host port … address already in use</code>
|
||||||
partway through creating the cluster. Run <code>make check</code> first — it
|
partway through creating the cluster. <code>make check</code> tests every port in
|
||||||
checks every port in this environment's block before anything is built.</p>
|
this environment's block before anything is built.</p>
|
||||||
|
|
||||||
<h3>Every node stays NotReady</h3>
|
<h3>Every node stays NotReady</h3>
|
||||||
<p>Usually a cluster created with the default CNI disabled but the real CNI
|
<p>Usually a cluster created with the default CNI disabled and the real CNI never
|
||||||
never installed — typically an interrupted first run. Just run
|
installed, after an interrupted first run. Run <code>make cluster up</code> again:
|
||||||
<code>make cluster up</code> again: it converges rather than exiting early, and
|
it finishes the missing steps instead of stopping because the cluster exists.</p>
|
||||||
will finish the missing steps.</p>
|
|
||||||
|
|
||||||
<h3>x509: certificate signed by unknown authority</h3>
|
<h3>x509: certificate signed by unknown authority</h3>
|
||||||
<p>Corporate CA trust has not reached one of the three places it needs to be.
|
<p>Corporate CA trust has not reached one of the three places it needs to be. See
|
||||||
See <a href="#registry">Registry</a>.</p>
|
<a href="#environments">Environments</a>.</p>
|
||||||
|
|
||||||
<h3>kubectl says the context does not exist</h3>
|
<h3>kubectl says the context does not exist</h3>
|
||||||
<p>The cluster can exist while its context does not — a reset or a switched
|
<p>The cluster can exist while its context does not — a reset or a switched
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
Each addon is its own idempotent script — adding one is adding a file, not editing a dispatcher. `ADDONS` names them, in install order; the overlay's `addons/<name>.sh` is found before rig's `ctrl/addons/<name>.sh`, and every one runs from rig's `ctrl/` with `RIG_CTRL` exported, wherever its file lives (see [overlay.md](overlay.md)).
|
Each addon is its own idempotent script — adding one is adding a file, not editing a dispatcher. `ADDONS` names them, in install order; the overlay's `addons/<name>.sh` is found before rig's `ctrl/addons/<name>.sh`, and every one runs from rig's `ctrl/` with `RIG_CTRL` exported, wherever its file lives (see [overlay.md](overlay.md)).
|
||||||
|
|
||||||
|
## The manifest beside the script
|
||||||
|
|
||||||
|
An addon's Kubernetes objects are a plain `<name>.yaml` beside its `<name>.sh`, not a heredoc inside it: the YAML reads, diffs and lints as YAML, and the script keeps only what YAML cannot do (secrets made once, a database created, waiting for a rollout). The script renders it with `render_manifest "$ADDON_DIR/<name>.yaml"` (`lib/config.sh`) and pipes it to `kubectl apply`.
|
||||||
|
|
||||||
|
`render_manifest` fills `${NAME}` and `${NAME:-default}` from the loaded config or the script's own variables, and fails on a name nothing sets, so a typo is an error rather than an empty image. `$VAR` and `$(VAR)` are left alone: they are the container's, as in postgres' `pg_isready -U $POSTGRES_USER`. selftest checks that every name a manifest uses is set somewhere. `ADDON_DIR` is taken before the script's `cd` into rig's `ctrl/`: `ADDON_DIR="$(cd "$(dirname "$0")" && pwd)"`.
|
||||||
|
|
||||||
## What rig ships, and what it does not
|
## What rig ships, and what it does not
|
||||||
|
|
||||||
rig's own addons make the *cluster* work, and are useless outside one: metallb, cert-manager, metrics-server. Things a workload happens to need — a database, a cache, a scheduler — are the workload's, and which workload needs which is not rig's business, so they live with the overlay. `examples/data/addons/` has postgres, redis and airflow as a worked example; an overlay that wants them copies them in.
|
rig's own addons make the *cluster* work, and are useless outside one: metallb, cert-manager, metrics-server. Things a workload happens to need — a database, a cache, a scheduler — are the workload's, and which workload needs which is not rig's business, so they live with the overlay. `examples/data/addons/` has postgres, redis and airflow as a worked example; an overlay that wants them copies them in.
|
||||||
|
|||||||
@@ -34,14 +34,37 @@ A port held by THIS environment's own cluster is not a clash; it is the thing wo
|
|||||||
|
|
||||||
The ports are extracted with a second grep rather than `tr -d ':->'`: in tr, ':->' is the character RANGE ':' to '>', which does not contain '-', so the trailing dash survives and nothing ever matches.
|
The ports are extracted with a second grep rather than `tr -d ':->'`: in tr, ':->' is the character RANGE ':' to '>', which does not contain '-', so the trailing dash survives and nothing ever matches.
|
||||||
|
|
||||||
## Compact by default
|
## Budget first, configuration after (2026-09-23)
|
||||||
|
|
||||||
`make check` prints one line per question — host, toolchain, and for this rig: cluster, memory,
|
`make check` answers the question people run it for first, in the shape the adapter rig had
|
||||||
ports, registry, addons — and adds detail only where something needs attention (`!` lines, the
|
(the user preferred it): a memory budget with one row per part, the node overhead, the total
|
||||||
"held elsewhere" list when memory is tight, the clashing port). `make check all` prints every fact,
|
against what the machine has and has available; then a one-line verdict (`fits`, `! tight`,
|
||||||
as the full report did before 2026-09-17. `deps.sh detect all` is the same switch for the host part,
|
`! does not fit`, with the two opposite fixes told apart); then where things stand and the next
|
||||||
so the standalone `rigdeps.sh detect` is short too. Changed because the long report buried the few
|
command — not created, stopped (with the `docker start` line), up (with the Tilt and gateway
|
||||||
lines that mattered.
|
URLs, and whether this environment's Tilt is already running).
|
||||||
|
|
||||||
|
Below that, the configuration as it is, raw: the host and toolchain (`deps.sh detect`), then
|
||||||
|
one `key value` line each for cluster, context, overlay, profile, nodes, kind config, manifests,
|
||||||
|
ingress, ports (named), registry, addons. `!` lines always print; `make check all` adds every
|
||||||
|
fact (per-port detail, addon list, swap and overcommit notes). `deps.sh detect all` is the same
|
||||||
|
switch for the host part, so the standalone `rigdeps.sh detect` is short too.
|
||||||
|
|
||||||
|
## MEMORY_BUDGET
|
||||||
|
|
||||||
|
rig knows what a node costs (NODE_MB), not what a workload does, so the parts come from the
|
||||||
|
overlay's rig.env: `MEMORY_BUDGET="name=MB ..."`, in the order listed; a 0 is left out, and an
|
||||||
|
entry that is not `name=MB` is a `!` line. rig.env is sourced, so an overlay can size a part by
|
||||||
|
its own knob (poc_rig's airflow follows its mode). When one part outweighs the rest together
|
||||||
|
(and is at least 1 GB), check names it as the first thing to switch off; `MEMORY_BUDGET_TIP`
|
||||||
|
says how, in the overlay's words. No budget: one `?` row says where to add it.
|
||||||
|
|
||||||
|
## Tilt: ours or another
|
||||||
|
|
||||||
|
A Tilt still running after `make cluster down` holds the Tilt port, and the next `make tilt`
|
||||||
|
fails with "address already in use" (2026-09-23, the Workspace). check reads the listener with
|
||||||
|
`ss -p` (our own processes only): a `tilt` whose working directory is this ctrl/ is this
|
||||||
|
environment's Tilt, reported as running with its URL; another `tilt` is a `!` line with its pid,
|
||||||
|
its folder and the `kill` that frees the port. Anything else is the ordinary port clash.
|
||||||
|
|
||||||
## overlay and kind config
|
## overlay and kind config
|
||||||
|
|
||||||
|
|||||||
@@ -160,3 +160,7 @@ Read the command, THEN shift — and shift only if there is something there. A b
|
|||||||
## manifest / manifests
|
## manifest / manifests
|
||||||
|
|
||||||
The manifests rig's own addons apply are pinned in versions.env like the binaries, and fetched by the same code: `resolve_url` for the source (upstream, artifactory, baked), `verify` for the sum. `manifest <NAME>` makes one present in `vendor/manifests/` (rig's folder, gitignored) and prints only its path, so an addon can apply it; a cached copy whose sum still matches is reused, one that does not is fetched again. `manifests [--to DIR]` fetches all three, which is how the deps-full image bakes them and how an offline machine is given them. Why the addons stopped applying URLs is in versions.md.
|
The manifests rig's own addons apply are pinned in versions.env like the binaries, and fetched by the same code: `resolve_url` for the source (upstream, artifactory, baked), `verify` for the sum. `manifest <NAME>` makes one present in `vendor/manifests/` (rig's folder, gitignored) and prints only its path, so an addon can apply it; a cached copy whose sum still matches is reused, one that does not is fetched again. `manifests [--to DIR]` fetches all three, which is how the deps-full image bakes them and how an offline machine is given them. Why the addons stopped applying URLs is in versions.md.
|
||||||
|
|
||||||
|
## snapshot, and the test hooks
|
||||||
|
|
||||||
|
`snapshot [DIR]` writes this machine as a host fixture: the files detect reads, cut down to what it needs (no environment, home or host name), plus the lines detect printed for it. It is in the kit, so the Workspace needs nothing else to take one. Detection reads through `HOST_ROOT`, `MEMINFO`, `OVERCOMMIT_FILE` and `UNAME_S`, so a fixture can stand in for a machine; with `HOST_ROOT` set, the first two default into it. How the fixtures, the clean-container run and the WSL recipe fit together: [installer-testing.md](installer-testing.md).
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ out `python -m http.server` too — a minimal Debian has no python3. What it doe
|
|||||||
have, by definition, is Docker: the single prerequisite rig already demands. So a
|
have, by definition, is Docker: the single prerequisite rig already demands. So a
|
||||||
throwaway nginx container serves a read-only bind mount.
|
throwaway nginx container serves a read-only bind mount.
|
||||||
|
|
||||||
|
It runs with a config of its own: one worker (nginx's `auto` starts one per core,
|
||||||
|
twelve on an ordinary laptop, for a handful of static pages), no access log and a
|
||||||
|
quiet entrypoint, so the terminal shows the URL and nothing else unless something
|
||||||
|
fails. The pages declare an empty icon, so the browser does not ask for a
|
||||||
|
`favicon.ico` that is not there.
|
||||||
|
|
||||||
## Committed SVGs
|
## Committed SVGs
|
||||||
|
|
||||||
Rendered SVGs are committed alongside their `.dot` sources for the same reason:
|
Rendered SVGs are committed alongside their `.dot` sources for the same reason:
|
||||||
|
|||||||
118
rig/docs/notes/installer-testing.md
Normal file
118
rig/docs/notes/installer-testing.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# Testing the installer
|
||||||
|
|
||||||
|
## Why it needs its own tiers
|
||||||
|
|
||||||
|
The installer is `ctrl/deps.sh`. What reaches a machine is its generated one-file kit,
|
||||||
|
`standalone/default/rigdeps.sh`: the AWS Workspace gets that file and nothing else. The
|
||||||
|
machines it must work on are exactly the ones you cannot rebuild to test on:
|
||||||
|
- the Workspace cannot be recreated;
|
||||||
|
- redoing WSL means rebuilding the machine you work on every day;
|
||||||
|
- a fresh install happens once per machine, so a bug there is found by the one person least
|
||||||
|
able to diagnose it.
|
||||||
|
|
||||||
|
So each tier is cheaper than the one after it, and each catches what the one before cannot.
|
||||||
|
|
||||||
|
| tier | runs | needs | proves | cannot prove |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 1 host fixtures | every `make selftest` | nothing | detection reads each kind of machine right: distro, WSL, memory, overcommit, systemd, `.wslconfig`, the Git Bash refusal | that anything installs |
|
||||||
|
| 2 clean containers | `make selftest install` | docker, network, ~1–2 min | a stock Ubuntu 22.04 / Debian refuses cleanly when bare, then installs, verifies and fetches the manifests **as a non-root user**; the offline image works with no network | WSL, the Workspace's own policies, docker itself |
|
||||||
|
| 3a Workspace snapshot | by hand, when the Workspace changes | the kit, once, on the Workspace | tier 1 replays the real Workspace's shape from then on | its install (that is tier 2's Ubuntu 22.04 plus the real run) |
|
||||||
|
| 3b WSL throwaway | by hand, before teaching or after a WSL change | a Windows machine | the whole path in a real WSL distro that is not your daily one | Windows-side setup (not built, see below) |
|
||||||
|
|
||||||
|
## Tier 1 — host fixtures
|
||||||
|
|
||||||
|
`tests/hosts/<name>/` is a stand-in machine:
|
||||||
|
|
||||||
|
```
|
||||||
|
root/ the files detect reads: etc/os-release, proc/version, proc/meminfo,
|
||||||
|
proc/sys/vm/overcommit_memory, etc/wsl.conf, mnt/c/Users/<u>/.wslconfig
|
||||||
|
env optional KEY=value lines, e.g. UNAME_S=MINGW64_NT-10.0 (a fact no file carries)
|
||||||
|
expect.txt "+ text" must appear, "- text" must not, "exit N" (default 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
`bash ctrl/hosttest.sh [DIR...]` runs `deps.sh detect all` with `HOST_ROOT` pointing at the
|
||||||
|
root (which also sets `MEMINFO` and `OVERCOMMIT_FILE`) and checks the lines. Only host facts are
|
||||||
|
asserted: docker, PATH and the filesystem belong to the machine running the test.
|
||||||
|
|
||||||
|
Shipped: `ubuntu-22.04` (the Workspace's shape: 7.6 GB, overcommit 1, swap in use),
|
||||||
|
`debian-trixie`, `wsl-bare` (systemd off, no `.wslconfig`), `wsl-ready`, `git-bash`. To add a
|
||||||
|
case, add a folder. Anything true of one real machine goes with an overlay or in `local/`,
|
||||||
|
never here.
|
||||||
|
|
||||||
|
## Tier 2 — clean containers
|
||||||
|
|
||||||
|
`make selftest install` (`ctrl/installtest.sh [IMAGE...]`). It first refuses to run on a stale
|
||||||
|
kit, then, for `ubuntu:22.04` and `debian:trixie-slim`:
|
||||||
|
1. **bare:** `install` must refuse and name curl/wget — the bootstrap paradox;
|
||||||
|
2. **after the one root step** (`apt-get install curl ca-certificates`), as a plain user:
|
||||||
|
`install dev`, `verify dev`, `manifests --to ~/m`. The dev tier lands in `~/.local/bin`,
|
||||||
|
the PATH advice is printed, and all three manifests are there, verified.
|
||||||
|
|
||||||
|
Then it builds `deps-full` and runs `install` + `manifests` with `--network none`: the
|
||||||
|
air-gapped path. Measured 2026-09-22: 15 checks, 75 s.
|
||||||
|
|
||||||
|
## Tier 3a — the Workspace snapshot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash rigdeps.sh snapshot ~/rig-host-snapshot # on the Workspace; the kit is enough
|
||||||
|
```
|
||||||
|
|
||||||
|
It writes the files detect reads, cut down to what detect needs:
|
||||||
|
- the `os-release` name fields;
|
||||||
|
- the kernel release;
|
||||||
|
- four meminfo numbers;
|
||||||
|
- the overcommit value;
|
||||||
|
- `wsl.conf`'s section headers and its two keys;
|
||||||
|
- a `.wslconfig` memory line, under the neutral user `user`;
|
||||||
|
- `facts.txt` (arch, glibc, date);
|
||||||
|
- `expect.txt`, the lines detect printed for it.
|
||||||
|
|
||||||
|
No environment, no home, no host name: selftest asserts both the file list and the absence
|
||||||
|
of those.
|
||||||
|
|
||||||
|
Carry the folder back, keep it in `local/hosts/workspace/` (gitignored) or with the overlay,
|
||||||
|
and from then on:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ctrl/hosttest.sh local/hosts/workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Add your own `+`/`-` lines to its `expect.txt` for what must stay true there. Take a new one
|
||||||
|
when the Workspace changes (an image update, a memory change).
|
||||||
|
|
||||||
|
## Tier 3b — WSL, in a throwaway distro
|
||||||
|
|
||||||
|
Never the distro you work in: a second one, imported from a rootfs, removed afterwards. WSL
|
||||||
|
runs every distro in one VM, so this changes nothing in yours — **except that
|
||||||
|
`wsl --shutdown` stops both**; don't run it while you work. From PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# a pristine Ubuntu 22.04 rootfs: from cloud-images.ubuntu.com/wsl/, or
|
||||||
|
# `wsl --export Ubuntu-22.04 ubuntu-22.04.tar` of an untouched install kept for this
|
||||||
|
wsl --import rig-test C:\wsl\rig-test .\ubuntu-22.04.tar
|
||||||
|
wsl -d rig-test -u root -- bash -c "apt-get update && apt-get install -y curl ca-certificates && useradd -m t"
|
||||||
|
copy .\rigdeps.sh \\wsl$\rig-test\home\t\
|
||||||
|
wsl -d rig-test -u t -- bash -lc "cd ~ && bash rigdeps.sh detect all && bash rigdeps.sh install dev && bash rigdeps.sh verify dev && bash rigdeps.sh snapshot ~/snap"
|
||||||
|
# carry \\wsl$\rig-test\home\t\snap back as a fixture, then:
|
||||||
|
wsl --unregister rig-test
|
||||||
|
```
|
||||||
|
|
||||||
|
What to look for: detect says `WSL`, and names the systemd and `.wslconfig` steps; install and
|
||||||
|
verify pass. This is the tier to run before teaching Windows users, or after a WSL update.
|
||||||
|
Nothing in rig automates it: a throwaway-distro harness is the multi-distro machinery that
|
||||||
|
was removed on purpose.
|
||||||
|
|
||||||
|
## Not built: a Windows-host installer
|
||||||
|
|
||||||
|
Everything above starts inside Linux. The Windows side — enabling WSL, getting a distro, and
|
||||||
|
the tools a Windows user needs before rig (python and the like) — is still manual, in the
|
||||||
|
README's "Starting from plain Windows". A Windows-host installer is recorded for the future,
|
||||||
|
for teaching; it would need tier 3b's approach, a machine that is not your daily one, to test.
|
||||||
|
|
||||||
|
## The hooks the tiers rely on
|
||||||
|
|
||||||
|
`deps.sh` reads host files through `HOST_ROOT` (`host_file()`), memory through `MEMINFO`, the
|
||||||
|
overcommit mode through `OVERCOMMIT_FILE`, the kernel name through `UNAME_S`, and WSL through
|
||||||
|
`host_file /proc/version`. With `HOST_ROOT` set, `MEMINFO` and `OVERCOMMIT_FILE` default into
|
||||||
|
it. The deps image relies on `HOST_ROOT=/host` for the same reason: a container asking about
|
||||||
|
its host.
|
||||||
@@ -60,6 +60,9 @@ air-gapped install); an overlay says what runs. `ctrl/.env` is still this
|
|||||||
machine's, and the caller still wins over everything.
|
machine's, and the caller still wins over everything.
|
||||||
|
|
||||||
`rig.env` may not set `PROFILE` or `OVERLAY`: both are chosen before it loads.
|
`rig.env` may not set `PROFILE` or `OVERLAY`: both are chosen before it loads.
|
||||||
|
|
||||||
|
`MEMORY_BUDGET` (and optionally `MEMORY_BUDGET_TIP`) is how an overlay tells `make check` what its
|
||||||
|
workload costs: rig only knows the node. See check.md.
|
||||||
The paths it sets (`MANIFESTS_DIR`, `KIND_CONFIG`) are relative to the overlay.
|
The paths it sets (`MANIFESTS_DIR`, `KIND_CONFIG`) are relative to the overlay.
|
||||||
`MANIFESTS_DIR=none` means rig applies no manifests and the overlay's Tiltfile
|
`MANIFESTS_DIR=none` means rig applies no manifests and the overlay's Tiltfile
|
||||||
does, e.g. when kustomize needs flags.
|
does, e.g. when kustomize needs flags.
|
||||||
@@ -100,7 +103,8 @@ load_config
|
|||||||
```
|
```
|
||||||
|
|
||||||
`OVERLAY_DIR` is set, so an addon can find files beside it
|
`OVERLAY_DIR` is set, so an addon can find files beside it
|
||||||
(`$(_from_ctrl "$OVERLAY_DIR")/...`). rig's own `ctrl/addons/` holds only what
|
(`$(_from_ctrl "$OVERLAY_DIR")/...`). Its manifest is a `<name>.yaml` beside the script,
|
||||||
|
rendered with `render_manifest` (see [addons.md](addons.md)). rig's own `ctrl/addons/` holds only what
|
||||||
makes a cluster work (metallb, cert-manager, metrics-server);
|
makes a cluster work (metallb, cert-manager, metrics-server);
|
||||||
`examples/data/addons/` shows workload ones.
|
`examples/data/addons/` shows workload ones.
|
||||||
|
|
||||||
@@ -151,6 +155,31 @@ $(wordlist 2,$(words $(GOALS)),$(GOALS)):
|
|||||||
The cluster is then named after `<project>`, exactly as a copied rig named
|
The cluster is then named after `<project>`, exactly as a copied rig named
|
||||||
`<project>` was, so moving a copied rig to this layout keeps its cluster and ports.
|
`<project>` was, so moving a copied rig to this layout keeps its cluster and ports.
|
||||||
|
|
||||||
|
rig can also live outside the project, which then holds only the overlay. The
|
||||||
|
forwarder names it with one variable, a machine path that is never committed (the
|
||||||
|
shell, or an ignored `.env.local`), defaulting to the vendored layout:
|
||||||
|
|
||||||
|
```make
|
||||||
|
RIG_DIR ?= $(HERE)/rig
|
||||||
|
override RIG_DIR := $(abspath $(patsubst ~/%,$(HOME)/%,$(RIG_DIR)))
|
||||||
|
```
|
||||||
|
|
||||||
|
No search path: an unset `RIG_DIR` means `./rig`, and nothing else is tried. Moving rig
|
||||||
|
changes nothing in the cluster's name, context or ports; moving or renaming the overlay
|
||||||
|
folder does.
|
||||||
|
|
||||||
|
`VERSION`, at rig's top, names the rig. It travels with the folder, vendored, a
|
||||||
|
worktree or a plain copy, and needs no git. A project records the one it was last run
|
||||||
|
against and warns on another; it does not refuse:
|
||||||
|
|
||||||
|
```make
|
||||||
|
RIG_REF := 0.1.0
|
||||||
|
RIG_AT := $(strip $(shell cat '$(RIG_DIR)/VERSION' 2>/dev/null))
|
||||||
|
ifneq ($(RIG_AT),$(RIG_REF))
|
||||||
|
$(warning rig at $(RIG_DIR) is $(or $(RIG_AT),unversioned); this overlay was last run against rig $(RIG_REF))
|
||||||
|
endif
|
||||||
|
```
|
||||||
|
|
||||||
`rig.env` holds no secrets, by this contract. A repository whose `.gitignore` has a
|
`rig.env` holds no secrets, by this contract. A repository whose `.gitignore` has a
|
||||||
broad `*.env` (a common secrets rule) would still hide it, so an overlay living in
|
broad `*.env` (a common secrets rule) would still hide it, so an overlay living in
|
||||||
such a repo re-includes it in its own `.gitignore`: `!rig.env`.
|
such a repo re-includes it in its own `.gitignore`: `!rig.env`.
|
||||||
|
|||||||
@@ -89,3 +89,11 @@ The offline example profile must install rig's addons with no network. Each addo
|
|||||||
## the examples are overlays that work as shipped
|
## the examples are overlays that work as shipped
|
||||||
|
|
||||||
`examples/` is what real overlays are copied from, so every addon there must parse, and every DAG must be valid Python. What they deploy is exercised by the parse checks above, not here.
|
`examples/` is what real overlays are copied from, so every addon there must parse, and every DAG must be valid Python. What they deploy is exercised by the parse checks above, not here.
|
||||||
|
|
||||||
|
## the installer detects what each kind of machine needs
|
||||||
|
|
||||||
|
The machines the installer must work on — the Workspace, a WSL install — are the ones you cannot rebuild to test on, so their shapes are replayed from fixtures (`tests/hosts/`, run by `hosttest.sh`). And a snapshot is carried off a machine that may be someone else's, so it must hold only what detect reads and nothing that names the machine or the person; that, and that it replays as the machine it was taken on, is asserted on a snapshot of the machine running the test.
|
||||||
|
|
||||||
|
## make selftest install
|
||||||
|
|
||||||
|
The installer on clean machines: stock Ubuntu 22.04 and Debian containers, the generated kit, a plain user; then the offline image with no network. Docker, network and a minute or two, so it is its own verb rather than part of the cheap run. See [installer-testing.md](installer-testing.md).
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
<title>Graph Viewer</title>
|
<title>Graph Viewer</title>
|
||||||
<style>
|
<style>
|
||||||
* { margin: 0; padding: 0; }
|
* { margin: 0; padding: 0; }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
# Requires the postgres addon; refuses to install without it.
|
# Requires the postgres addon; refuses to install without it.
|
||||||
# Notes: ../README.md
|
# Notes: ../README.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
ADDON_DIR="$(cd "$(dirname "$0")" && pwd)" # its manifest sits beside it
|
||||||
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
@@ -70,77 +71,7 @@ if [ -d "$dags" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo " applying manifests"
|
echo " applying manifests"
|
||||||
$K apply -n "$NS" -f - >/dev/null <<YAML
|
render_manifest "$ADDON_DIR/airflow.yaml" | $K apply -n "$NS" -f - >/dev/null
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: airflow
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: airflow
|
|
||||||
ports:
|
|
||||||
- port: 8080
|
|
||||||
targetPort: 8080
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: airflow
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: airflow
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: airflow
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: airflow
|
|
||||||
image: ${AIRFLOW_IMAGE}
|
|
||||||
args: ["standalone"]
|
|
||||||
env:
|
|
||||||
- name: AIRFLOW__CORE__EXECUTOR
|
|
||||||
value: LocalExecutor
|
|
||||||
- name: AIRFLOW__CORE__LOAD_EXAMPLES
|
|
||||||
value: "false"
|
|
||||||
- name: AIRFLOW__DATABASE__SQL_ALCHEMY_CONN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow-connections, key: SQL_ALCHEMY_CONN}
|
|
||||||
- name: AIRFLOW_CONN_APP_DB
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow-connections, key: AIRFLOW_CONN_APP_DB}
|
|
||||||
- name: AIRFLOW__CORE__FERNET_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow, key: FERNET_KEY}
|
|
||||||
- name: _AIRFLOW_WWW_USER_USERNAME
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow, key: ADMIN_USER}
|
|
||||||
- name: _AIRFLOW_WWW_USER_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow, key: ADMIN_PASSWORD}
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
volumeMounts:
|
|
||||||
- name: dags
|
|
||||||
mountPath: /opt/airflow/dags
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 8080
|
|
||||||
# First boot runs the whole migration before it serves anything.
|
|
||||||
initialDelaySeconds: 60
|
|
||||||
periodSeconds: 15
|
|
||||||
failureThreshold: 20
|
|
||||||
volumes:
|
|
||||||
- name: dags
|
|
||||||
configMap:
|
|
||||||
name: airflow-dags
|
|
||||||
optional: true
|
|
||||||
YAML
|
|
||||||
|
|
||||||
echo " waiting for airflow (the first boot migrates the database, so this is slow)..."
|
echo " waiting for airflow (the first boot migrates the database, so this is slow)..."
|
||||||
$K rollout status deployment/airflow -n "$NS" --timeout=600s
|
$K rollout status deployment/airflow -n "$NS" --timeout=600s
|
||||||
|
|||||||
69
rig/examples/data/addons/airflow.yaml
Normal file
69
rig/examples/data/addons/airflow.yaml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: airflow
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: airflow
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: airflow
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: airflow
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: airflow
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: airflow
|
||||||
|
image: ${AIRFLOW_IMAGE}
|
||||||
|
args: ["standalone"]
|
||||||
|
env:
|
||||||
|
- name: AIRFLOW__CORE__EXECUTOR
|
||||||
|
value: LocalExecutor
|
||||||
|
- name: AIRFLOW__CORE__LOAD_EXAMPLES
|
||||||
|
value: "false"
|
||||||
|
- name: AIRFLOW__DATABASE__SQL_ALCHEMY_CONN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow-connections, key: SQL_ALCHEMY_CONN}
|
||||||
|
- name: AIRFLOW_CONN_APP_DB
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow-connections, key: AIRFLOW_CONN_APP_DB}
|
||||||
|
- name: AIRFLOW__CORE__FERNET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow, key: FERNET_KEY}
|
||||||
|
- name: _AIRFLOW_WWW_USER_USERNAME
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow, key: ADMIN_USER}
|
||||||
|
- name: _AIRFLOW_WWW_USER_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow, key: ADMIN_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
volumeMounts:
|
||||||
|
- name: dags
|
||||||
|
mountPath: /opt/airflow/dags
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8080
|
||||||
|
# First boot runs the whole migration before it serves anything.
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 20
|
||||||
|
volumes:
|
||||||
|
- name: dags
|
||||||
|
configMap:
|
||||||
|
name: airflow-dags
|
||||||
|
optional: true
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# PostgreSQL for this overlay: plain manifests, one replica on a PVC, password generated once and kept.
|
# PostgreSQL for this overlay: plain manifests, one replica on a PVC, password generated once and kept.
|
||||||
# Notes: ../README.md
|
# Notes: ../README.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
ADDON_DIR="$(cd "$(dirname "$0")" && pwd)" # its manifest sits beside it
|
||||||
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
@@ -26,77 +27,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo " applying manifests"
|
echo " applying manifests"
|
||||||
$K apply -n "$NS" -f - >/dev/null <<YAML
|
render_manifest "$ADDON_DIR/postgres.yaml" | $K apply -n "$NS" -f - >/dev/null
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: postgres-data
|
|
||||||
spec:
|
|
||||||
accessModes: [ReadWriteOnce]
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: ${POSTGRES_STORAGE:-2Gi}
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: postgres
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: postgres
|
|
||||||
ports:
|
|
||||||
- port: 5432
|
|
||||||
targetPort: 5432
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: postgres
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
# One volume, one writer. Rolling would start a second pod against the same
|
|
||||||
# PVC before the first exits, and Postgres refuses to share a data directory.
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: postgres
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: postgres
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: postgres
|
|
||||||
image: ${POSTGRES_IMAGE}
|
|
||||||
envFrom:
|
|
||||||
- secretRef:
|
|
||||||
name: postgres
|
|
||||||
env:
|
|
||||||
# The image initialises into the volume root otherwise, and a
|
|
||||||
# lost+found from the PVC makes it refuse to initdb.
|
|
||||||
- name: PGDATA
|
|
||||||
value: /var/lib/postgresql/data/pgdata
|
|
||||||
ports:
|
|
||||||
- containerPort: 5432
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /var/lib/postgresql/data
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command: ["sh", "-c", "pg_isready -U \$POSTGRES_USER -d \$POSTGRES_DB"]
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 5
|
|
||||||
livenessProbe:
|
|
||||||
exec:
|
|
||||||
command: ["sh", "-c", "pg_isready -U \$POSTGRES_USER -d \$POSTGRES_DB"]
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 15
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: postgres-data
|
|
||||||
YAML
|
|
||||||
|
|
||||||
echo " waiting for postgres..."
|
echo " waiting for postgres..."
|
||||||
$K rollout status deployment/postgres -n "$NS" --timeout=240s
|
$K rollout status deployment/postgres -n "$NS" --timeout=240s
|
||||||
|
|||||||
69
rig/examples/data/addons/postgres.yaml
Normal file
69
rig/examples/data/addons/postgres.yaml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: postgres-data
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: ${POSTGRES_STORAGE:-2Gi}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
# One volume, one writer. Rolling would start a second pod against the same
|
||||||
|
# PVC before the first exits, and Postgres refuses to share a data directory.
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: ${POSTGRES_IMAGE}
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: postgres
|
||||||
|
env:
|
||||||
|
# The image initialises into the volume root otherwise, and a
|
||||||
|
# lost+found from the PVC makes it refuse to initdb.
|
||||||
|
- name: PGDATA
|
||||||
|
value: /var/lib/postgresql/data/pgdata
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["sh", "-c", "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"]
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["sh", "-c", "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"]
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 15
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: postgres-data
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# Redis for this overlay: cache and broker (Celery), no persistence.
|
# Redis for this overlay: cache and broker (Celery), no persistence.
|
||||||
# Notes: ../README.md
|
# Notes: ../README.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
ADDON_DIR="$(cd "$(dirname "$0")" && pwd)" # its manifest sits beside it
|
||||||
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
@@ -13,43 +14,7 @@ NS="${DATA_NAMESPACE:-data}"
|
|||||||
$K get namespace "$NS" >/dev/null 2>&1 || $K create namespace "$NS"
|
$K get namespace "$NS" >/dev/null 2>&1 || $K create namespace "$NS"
|
||||||
|
|
||||||
echo " applying manifests"
|
echo " applying manifests"
|
||||||
$K apply -n "$NS" -f - >/dev/null <<YAML
|
render_manifest "$ADDON_DIR/redis.yaml" | $K apply -n "$NS" -f - >/dev/null
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: redis
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: redis
|
|
||||||
ports:
|
|
||||||
- port: 6379
|
|
||||||
targetPort: 6379
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: redis
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: redis
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: redis
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: redis
|
|
||||||
image: ${REDIS_IMAGE}
|
|
||||||
ports:
|
|
||||||
- containerPort: 6379
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command: ["redis-cli", "ping"]
|
|
||||||
initialDelaySeconds: 3
|
|
||||||
periodSeconds: 5
|
|
||||||
YAML
|
|
||||||
|
|
||||||
echo " waiting for redis..."
|
echo " waiting for redis..."
|
||||||
$K rollout status deployment/redis -n "$NS" --timeout=180s
|
$K rollout status deployment/redis -n "$NS" --timeout=180s
|
||||||
|
|||||||
35
rig/examples/data/addons/redis.yaml
Normal file
35
rig/examples/data/addons/redis.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: redis
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: redis
|
||||||
|
ports:
|
||||||
|
- port: 6379
|
||||||
|
targetPort: 6379
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: redis
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: redis
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: redis
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: redis
|
||||||
|
image: ${REDIS_IMAGE}
|
||||||
|
ports:
|
||||||
|
- containerPort: 6379
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["redis-cli", "ping"]
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
@@ -7,6 +7,9 @@
|
|||||||
# LocalExecutor and needs no broker: add redis (before airflow) only to switch to Celery.
|
# LocalExecutor and needs no broker: add redis (before airflow) only to switch to Celery.
|
||||||
ADDONS="metallb postgres airflow"
|
ADDONS="metallb postgres airflow"
|
||||||
|
|
||||||
|
# What each part costs in MB, for `make check`'s memory budget (rig adds the kind node).
|
||||||
|
MEMORY_BUDGET="airflow=1800 postgres=300"
|
||||||
|
|
||||||
# Namespace for the dependency containers (k8s/base/kustomization.yaml names it too).
|
# Namespace for the dependency containers (k8s/base/kustomization.yaml names it too).
|
||||||
DATA_NAMESPACE=data
|
DATA_NAMESPACE=data
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ endif
|
|||||||
help: ## list targets
|
help: ## list targets
|
||||||
@grep -hE '^[a-z][a-z-]*:.*?##' $(MAKEFILE_LIST) | sed 's/:.*##/\t/' | expand -t16
|
@grep -hE '^[a-z][a-z-]*:.*?##' $(MAKEFILE_LIST) | sed 's/:.*##/\t/' | expand -t16
|
||||||
|
|
||||||
deps: ## rigdeps.sh [detect|list|verify|fetch|install|manifest|manifests] (default detect)
|
deps: ## rigdeps.sh [detect|list|verify|fetch|install|manifest|manifests|snapshot] (default detect)
|
||||||
bash $(HERE)rigdeps.sh $(or $(ARGS),detect)
|
bash $(HERE)rigdeps.sh $(or $(ARGS),detect)
|
||||||
|
|
||||||
mem: ## rigmini.sh [status|push|all|backup|restore] (default status)
|
mem: ## rigmini.sh [status|push|all|backup|restore] (default status)
|
||||||
|
|||||||
@@ -61,6 +61,32 @@ render_kind_config ()
|
|||||||
fi;
|
fi;
|
||||||
sed -e "s|\${CLUSTER}|${CLUSTER}|g" -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" -e "s|\${HOST_WORKDIR}|${host_workdir}|g" -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" "$KIND_CONFIG"
|
sed -e "s|\${CLUSTER}|${CLUSTER}|g" -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" -e "s|\${HOST_WORKDIR}|${host_workdir}|g" -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" "$KIND_CONFIG"
|
||||||
}
|
}
|
||||||
|
render_manifest ()
|
||||||
|
{
|
||||||
|
local file="$1" text ref key def val;
|
||||||
|
text=$(< "$file") || return 1;
|
||||||
|
while IFS= read -r ref; do
|
||||||
|
key=${ref#\$\{};
|
||||||
|
key=${key%\}};
|
||||||
|
def="";
|
||||||
|
if [[ $key == *:-* ]]; then
|
||||||
|
def=${key#*:-};
|
||||||
|
key=${key%%:-*};
|
||||||
|
fi;
|
||||||
|
if [ -n "${!key:-}" ]; then
|
||||||
|
val=${!key};
|
||||||
|
else
|
||||||
|
if [[ $ref == *:-* ]]; then
|
||||||
|
val=$def;
|
||||||
|
else
|
||||||
|
echo "render_manifest: $file needs \${$key}, which nothing sets" 1>&2;
|
||||||
|
return 1;
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
text=${text//"$ref"/"$val"};
|
||||||
|
done < <(grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}' "$file" | sort -u);
|
||||||
|
printf '%s\n' "$text"
|
||||||
|
}
|
||||||
|
|
||||||
# ── configuration, frozen for profile 'default' ──
|
# ── configuration, frozen for profile 'default' ──
|
||||||
load_config() {
|
load_config() {
|
||||||
@@ -128,7 +154,7 @@ load_config() {
|
|||||||
# Toolchain installer: detect the host, install pinned tools into $OUT_BIN, report
|
# 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`.
|
# 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]
|
# 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
|
# Notes: docs/notes/deps.md
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -152,6 +178,14 @@ abspath() {
|
|||||||
|
|
||||||
OUT_BIN="${OUT_BIN:-$HOME/.local/bin}"
|
OUT_BIN="${OUT_BIN:-$HOME/.local/bin}"
|
||||||
HOST_ROOT="${HOST_ROOT:-/}"
|
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_SOURCE="${DEPS_SOURCE:-upstream}"
|
||||||
DEPS_ARTIFACTORY_URL="${DEPS_ARTIFACTORY_URL:-}"
|
DEPS_ARTIFACTORY_URL="${DEPS_ARTIFACTORY_URL:-}"
|
||||||
BAKED_BIN="${BAKED_BIN:-/opt/rig/bin}"
|
BAKED_BIN="${BAKED_BIN:-/opt/rig/bin}"
|
||||||
@@ -263,7 +297,7 @@ docker_pkg() {
|
|||||||
|
|
||||||
# Windows outside WSL (Git Bash, MSYS, Cygwin) fails confusingly; name it instead.
|
# Windows outside WSL (Git Bash, MSYS, Cygwin) fails confusingly; name it instead.
|
||||||
require_linux() {
|
require_linux() {
|
||||||
case "$(uname -s)" in
|
case "${UNAME_S:-$(uname -s)}" in
|
||||||
MINGW*|MSYS*|CYGWIN*)
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
cat >&2 <<'EOF'
|
cat >&2 <<'EOF'
|
||||||
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
||||||
@@ -282,7 +316,7 @@ EOF
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
is_wsl() { grep -qi microsoft /proc/version 2>/dev/null; }
|
is_wsl() { grep -qi microsoft "$(host_file /proc/version)" 2>/dev/null; }
|
||||||
|
|
||||||
detect() {
|
detect() {
|
||||||
echo "host"
|
echo "host"
|
||||||
@@ -890,6 +924,54 @@ fetch_manifests() { # [--to DIR]
|
|||||||
done
|
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.
|
# Baked mode copies binaries already in the image, so it needs no downloader.
|
||||||
need_downloads() {
|
need_downloads() {
|
||||||
require_amd64
|
require_amd64
|
||||||
@@ -906,10 +988,12 @@ case "$cmd" in
|
|||||||
manifest) need_downloads
|
manifest) need_downloads
|
||||||
fetch_manifest "${1:?usage: $0 manifest <METALLB|CERT_MANAGER|METRICS_SERVER>}" "$MANIFESTS_HOME" ;;
|
fetch_manifest "${1:?usage: $0 manifest <METALLB|CERT_MANAGER|METRICS_SERVER>}" "$MANIFESTS_HOME" ;;
|
||||||
manifests) need_downloads; fetch_manifests "$@" ;;
|
manifests) need_downloads; fetch_manifests "$@" ;;
|
||||||
|
snapshot) snapshot "${1:-}" ;;
|
||||||
*) echo "usage: $0 [detect [all]|list|verify|fetch|install|manifest NAME|manifests]" >&2
|
*) echo "usage: $0 [detect [all]|list|verify|fetch|install|manifest NAME|manifests]" >&2
|
||||||
echo " install [core|dev] (default dev)" >&2
|
echo " install [core|dev] (default dev)" >&2
|
||||||
echo " fetch [core|dev] [--to DIR]" >&2
|
echo " fetch [core|dev] [--to DIR]" >&2
|
||||||
echo " manifests [--to DIR] the addons' pinned manifests, verified" >&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
|
echo " OUT_BIN=<dir> overrides the install directory" >&2
|
||||||
exit 1 ;;
|
exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -61,6 +61,32 @@ render_kind_config ()
|
|||||||
fi;
|
fi;
|
||||||
sed -e "s|\${CLUSTER}|${CLUSTER}|g" -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" -e "s|\${HOST_WORKDIR}|${host_workdir}|g" -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" "$KIND_CONFIG"
|
sed -e "s|\${CLUSTER}|${CLUSTER}|g" -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" -e "s|\${HOST_WORKDIR}|${host_workdir}|g" -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" "$KIND_CONFIG"
|
||||||
}
|
}
|
||||||
|
render_manifest ()
|
||||||
|
{
|
||||||
|
local file="$1" text ref key def val;
|
||||||
|
text=$(< "$file") || return 1;
|
||||||
|
while IFS= read -r ref; do
|
||||||
|
key=${ref#\$\{};
|
||||||
|
key=${key%\}};
|
||||||
|
def="";
|
||||||
|
if [[ $key == *:-* ]]; then
|
||||||
|
def=${key#*:-};
|
||||||
|
key=${key%%:-*};
|
||||||
|
fi;
|
||||||
|
if [ -n "${!key:-}" ]; then
|
||||||
|
val=${!key};
|
||||||
|
else
|
||||||
|
if [[ $ref == *:-* ]]; then
|
||||||
|
val=$def;
|
||||||
|
else
|
||||||
|
echo "render_manifest: $file needs \${$key}, which nothing sets" 1>&2;
|
||||||
|
return 1;
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
text=${text//"$ref"/"$val"};
|
||||||
|
done < <(grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}' "$file" | sort -u);
|
||||||
|
printf '%s\n' "$text"
|
||||||
|
}
|
||||||
|
|
||||||
# ── configuration, frozen for profile 'default' ──
|
# ── configuration, frozen for profile 'default' ──
|
||||||
load_config() {
|
load_config() {
|
||||||
@@ -145,7 +171,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.
|
# Refuse Git Bash / MSYS / Cygwin and kernels without /proc, with a clear message.
|
||||||
require_linux() {
|
require_linux() {
|
||||||
case "$(uname -s)" in
|
case "${UNAME_S:-$(uname -s)}" in
|
||||||
MINGW*|MSYS*|CYGWIN*)
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
cat >&2 <<'EOF'
|
cat >&2 <<'EOF'
|
||||||
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
||||||
|
|||||||
7
rig/tests/hosts/debian-trixie/expect.txt
Normal file
7
rig/tests/hosts/debian-trixie/expect.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# A plain native Debian with memory to spare and no swap.
|
||||||
|
+ distro Debian GNU/Linux 13 (trixie)
|
||||||
|
+ native linux
|
||||||
|
+ memory 15861 MB total, 11264 MB available
|
||||||
|
+ overcommit 0 heuristic
|
||||||
|
- in swap
|
||||||
|
- WSL
|
||||||
4
rig/tests/hosts/debian-trixie/root/etc/os-release
Normal file
4
rig/tests/hosts/debian-trixie/root/etc/os-release
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
|
||||||
|
NAME="Debian GNU/Linux"
|
||||||
|
VERSION_ID="13"
|
||||||
|
ID=debian
|
||||||
4
rig/tests/hosts/debian-trixie/root/proc/meminfo
Normal file
4
rig/tests/hosts/debian-trixie/root/proc/meminfo
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
MemTotal: 16241664 kB
|
||||||
|
MemAvailable: 11534336 kB
|
||||||
|
SwapTotal: 0 kB
|
||||||
|
SwapFree: 0 kB
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
rig/tests/hosts/debian-trixie/root/proc/version
Normal file
1
rig/tests/hosts/debian-trixie/root/proc/version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Linux version 6.12.48+deb13-amd64
|
||||||
1
rig/tests/hosts/git-bash/env
Normal file
1
rig/tests/hosts/git-bash/env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
UNAME_S=MINGW64_NT-10.0-19045
|
||||||
5
rig/tests/hosts/git-bash/expect.txt
Normal file
5
rig/tests/hosts/git-bash/expect.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Git Bash on Windows: refused before anything runs, pointing at WSL.
|
||||||
|
exit 1
|
||||||
|
+ This has to run inside WSL, not Git Bash / MSYS / Cygwin.
|
||||||
|
+ wsl --install
|
||||||
|
- distro
|
||||||
7
rig/tests/hosts/ubuntu-22.04/expect.txt
Normal file
7
rig/tests/hosts/ubuntu-22.04/expect.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# A managed Ubuntu 22.04 machine: native, tight on memory, already swapping.
|
||||||
|
+ distro Ubuntu 22.04.5 LTS
|
||||||
|
+ native linux
|
||||||
|
+ memory 7680 MB total, 1843 MB available, 4096 MB in swap
|
||||||
|
+ overcommit 1 always
|
||||||
|
- WSL
|
||||||
|
- systemd not enabled
|
||||||
5
rig/tests/hosts/ubuntu-22.04/root/etc/os-release
Normal file
5
rig/tests/hosts/ubuntu-22.04/root/etc/os-release
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
PRETTY_NAME="Ubuntu 22.04.5 LTS"
|
||||||
|
NAME="Ubuntu"
|
||||||
|
VERSION_ID="22.04"
|
||||||
|
ID=ubuntu
|
||||||
|
ID_LIKE=debian
|
||||||
4
rig/tests/hosts/ubuntu-22.04/root/proc/meminfo
Normal file
4
rig/tests/hosts/ubuntu-22.04/root/proc/meminfo
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
MemTotal: 7864320 kB
|
||||||
|
MemAvailable: 1887436 kB
|
||||||
|
SwapTotal: 8388608 kB
|
||||||
|
SwapFree: 4194304 kB
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
1
rig/tests/hosts/ubuntu-22.04/root/proc/version
Normal file
1
rig/tests/hosts/ubuntu-22.04/root/proc/version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Linux version 6.8.0-1030-aws
|
||||||
7
rig/tests/hosts/wsl-bare/expect.txt
Normal file
7
rig/tests/hosts/wsl-bare/expect.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Fresh WSL: systemd off, resolv.conf generated, no .wslconfig — all three said.
|
||||||
|
+ Ubuntu 22.04.5 LTS amd64, WSL
|
||||||
|
+ ! systemd not enabled in /etc/wsl.conf
|
||||||
|
+ - resolv.conf is WSL-generated
|
||||||
|
+ Enable systemd — add to /etc/wsl.conf:
|
||||||
|
+ Cap/raise the WSL VM memory
|
||||||
|
- native linux
|
||||||
4
rig/tests/hosts/wsl-bare/root/etc/os-release
Normal file
4
rig/tests/hosts/wsl-bare/root/etc/os-release
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
PRETTY_NAME="Ubuntu 22.04.5 LTS"
|
||||||
|
NAME="Ubuntu"
|
||||||
|
VERSION_ID="22.04"
|
||||||
|
ID=ubuntu
|
||||||
4
rig/tests/hosts/wsl-bare/root/proc/meminfo
Normal file
4
rig/tests/hosts/wsl-bare/root/proc/meminfo
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
MemTotal: 8055020 kB
|
||||||
|
MemAvailable: 7340032 kB
|
||||||
|
SwapTotal: 2097152 kB
|
||||||
|
SwapFree: 2097152 kB
|
||||||
1
rig/tests/hosts/wsl-bare/root/proc/version
Normal file
1
rig/tests/hosts/wsl-bare/root/proc/version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Linux version 5.15.167.4-microsoft-standard-WSL2
|
||||||
7
rig/tests/hosts/wsl-ready/expect.txt
Normal file
7
rig/tests/hosts/wsl-ready/expect.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# WSL set up as BOOTSTRAP says: systemd on, resolv.conf pinned, VM memory set.
|
||||||
|
+ amd64, WSL
|
||||||
|
+ systemd enabled in wsl.conf
|
||||||
|
+ resolv.conf pinned (generateResolvConf=false)
|
||||||
|
+ wslconfig memory set: memory=12GB
|
||||||
|
- systemd not enabled
|
||||||
|
- Cap/raise the WSL VM memory
|
||||||
4
rig/tests/hosts/wsl-ready/root/etc/os-release
Normal file
4
rig/tests/hosts/wsl-ready/root/etc/os-release
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
PRETTY_NAME="Ubuntu 22.04.5 LTS"
|
||||||
|
NAME="Ubuntu"
|
||||||
|
VERSION_ID="22.04"
|
||||||
|
ID=ubuntu
|
||||||
5
rig/tests/hosts/wsl-ready/root/etc/wsl.conf
Normal file
5
rig/tests/hosts/wsl-ready/root/etc/wsl.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[boot]
|
||||||
|
systemd=true
|
||||||
|
|
||||||
|
[network]
|
||||||
|
generateResolvConf=false
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[wsl2]
|
||||||
|
memory=12GB
|
||||||
4
rig/tests/hosts/wsl-ready/root/proc/meminfo
Normal file
4
rig/tests/hosts/wsl-ready/root/proc/meminfo
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
MemTotal: 8055020 kB
|
||||||
|
MemAvailable: 7340032 kB
|
||||||
|
SwapTotal: 2097152 kB
|
||||||
|
SwapFree: 2097152 kB
|
||||||
1
rig/tests/hosts/wsl-ready/root/proc/version
Normal file
1
rig/tests/hosts/wsl-ready/root/proc/version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Linux version 5.15.167.4-microsoft-standard-WSL2
|
||||||
@@ -1,97 +1,29 @@
|
|||||||
{
|
{
|
||||||
"_comment": "Template for distill.json, the job distill.sh reads when no repo is named on the command line. Copy this to distill.json (gitignored) and edit that — which repos a machine distils is not a fact about the tool. Every key the file understands appears below, once, with the value it would fall back to if you deleted the line. Preview any change with: ./distill.sh list — or ./distill.sh check, which resolves every path and ref in about a second.",
|
"_comment": "Template for distill.json, the job distill.sh reads when no repo is named on the command line. Copy this to distill.json (gitignored) and edit that. Everything that shapes the run is at the top; 'repos' is just a list of paths. Preview any change with: ./distill.sh list",
|
||||||
|
|
||||||
"_command": "tree = a directory per repo, a copy you can open, grep and build. digest = one .md per repo, the whole thing flattened into a single readable file (header, file tree, manifest, then every file under its path). both = each repo comes out as a directory AND a .md — they answer different questions and the selection work is shared, so the second costs only a copy. list = write nothing, just report what would be kept, weighed. check = write nothing and read nothing but git's index: does every path exist, does every ref resolve, is there room. Every other command runs check first anyway.",
|
"_command": "tree = a directory per repo. digest = one .md per repo, flattened into a single readable file. both = each repo as a directory AND a .md. list = write nothing, just report what would be kept.",
|
||||||
"command": "list",
|
"command": "list",
|
||||||
|
|
||||||
"_out": "Where the run writes. Relative to wherever you invoke the script from, so an absolute path is the safer thing to write down; ~ is expanded. Ignored by list and check.",
|
|
||||||
"out": "distilled",
|
"out": "distilled",
|
||||||
|
|
||||||
"_root": "Where a bare slug in 'repos' resolves, so entries can read \"path\": \"myrepo\" instead of the whole absolute path. Leave it out and slugs resolve against the current directory, or DISTILL_ROOT. Absolute paths never touch it.",
|
"_branch_mode": "full = each branch is a complete, standalone copy. diff = only the files that differ from diff_base (it gets a _PARTIAL.md saying so).",
|
||||||
"root": "~/wdir",
|
|
||||||
|
|
||||||
"_branch_mode": "full = each branch is a complete, standalone copy. diff = only the files that differ from diff_base, which is far smaller but is a changelist, not the branch (it gets a _PARTIAL.md saying so). diff_base must name one of the refs actually listed, and it is the one that comes out whole. Set per entry to override this default.",
|
|
||||||
"branch_mode": "full",
|
"branch_mode": "full",
|
||||||
"diff_base": "main",
|
"diff_base": "main",
|
||||||
|
|
||||||
"_filters": "Applied to every repo, and overridable per entry. exclude/include are path globs; a pattern with no / also matches basenames at any depth. 'all' keeps the noise (lockfiles, minified, maps, compiled output, caches) that is dropped by default. max_bytes drops anything larger outright, and names it in MANIFEST.md.",
|
"_filters": "Applied to every repo. exclude/include are path globs; a pattern with no / also matches basenames at any depth. 'all' keeps the noise (lockfiles, images, minified, maps). max_bytes skips anything larger and names it in MANIFEST.md.",
|
||||||
"exclude": ["*.ipynb", "fixtures/**"],
|
"exclude": [],
|
||||||
"include": [],
|
"include": [],
|
||||||
"all": false,
|
"all": false,
|
||||||
"max_bytes": "2M",
|
"max_bytes": null,
|
||||||
|
|
||||||
"_budget": "Three different answers to 'this is too much to read', and they do not compete. clip_bytes inlines only the head and tail of any file over that size, with a marker saying how much was cut. max_tokens holds each digest to roughly that many tokens by clipping largest-first — one shared ceiling, lowered until the total fits — so the biggest file pays and the hundred small ones that describe the project keep every line. Both of those lose text; split_tokens loses none, it just stops writing one enormous file: over ~N tokens the digest becomes NAME.md (an index holding the tree and the manifest, saying which part each file is in) plus NAME.part-01.md, NAME.part-02.md… cut between files in path order, each part standing on its own. Default 100k; 0 never splits. Clipping is a statement about the document, not the files: the tree copy always has them whole. Numbers take k/M/G, decimal.",
|
"_split_tokens": "A digest over this many tokens is written as NAME.md (an index: the tree, and the manifest saying which part holds each file) plus NAME.part-01.md, NAME.part-02.md... holding the files, cut between files. Each part stands on its own. Default 100k; 0 never splits. Can also be set per entry.",
|
||||||
"clip_bytes": null,
|
|
||||||
"max_tokens": null,
|
|
||||||
"split_tokens": "100k",
|
"split_tokens": "100k",
|
||||||
|
|
||||||
"_with_root": "Only matters when an entry names a subpath. Keeps the repo's top-level files (README, pyproject.toml, package.json) alongside the subtree, so the copy still says which project it is a part of.",
|
|
||||||
"with_root": false,
|
|
||||||
|
|
||||||
"_keep_secrets": ".env files, private keys, keystores and the like are dropped by default and named in MANIFEST.md. 'all' does not bring them back — only this does, and only deliberately.",
|
|
||||||
"keep_secrets": false,
|
|
||||||
|
|
||||||
"_run": "skip_unchanged leaves alone anything whose source and settings have not moved since the last run into this destination. prune deletes what an earlier run into 'out' wrote and this run no longer produces, so dropping a repo from the list drops its output too. Anything distill did not write (it keeps the list in out/.distill-owned) is never touched, so 'out' can be a folder shared with other things.",
|
|
||||||
"skip_unchanged": true,
|
"skip_unchanged": true,
|
||||||
"prune": true,
|
"prune": true,
|
||||||
|
|
||||||
"_bundle": "Also write _BUNDLE.md: every .md in the destination concatenated, in one document, behind an index. A directory of files is right for grep and for reading; an upload box takes one file. Costs a second copy of the text on disk and nothing else. Note that it concatenates whatever split_tokens produced, parts included — bundle and split are opposite answers to the same question, and you can have both on disk.",
|
"_repos": "'path' is absolute, or a name resolved under --root. 'branches' is optional — leave it out for the working tree as it stands, uncommitted changes included. A path may appear more than once.",
|
||||||
"bundle": false,
|
|
||||||
|
|
||||||
"_refs_patch": "Put the full diff, not just the diffstat, in NAME@REFS.md. Answers 'what changed' instead of 'how much'.",
|
|
||||||
"refs_patch": false,
|
|
||||||
|
|
||||||
"_raw_fences": "Write runs of backticks and tildes into the digest as they are, instead of escaping them as ⟪BT3⟫ / ⟪TL3⟫. Escaping is the default because a fence inside a file otherwise closes the fence the digest is being read inside; explode.sh puts the characters back. Turn this off only when nothing is going to render the result as markdown.",
|
|
||||||
"raw_fences": false,
|
|
||||||
|
|
||||||
"_top": "'list' only: how many heavy files and directories to show. 0 for none.",
|
|
||||||
"top": 10,
|
|
||||||
|
|
||||||
"_repos": "One entry per thing you want out. 'path' is absolute, or a name resolved under 'root'. The same path may appear as many times as you like, which is why this is a list rather than a keyed object — one entry per repo could not hold two branches of the same repo. Every filter and budget key above may be repeated inside an entry, where it replaces the top-level value for that entry alone; a command-line option still wins over both.",
|
|
||||||
"repos": [
|
"repos": [
|
||||||
{
|
{ "path": "/path/to/some-repo" },
|
||||||
"_note": "The plainest entry: the working tree as it stands right now, uncommitted changes included, under every setting from the top of the file.",
|
{ "path": "/path/to/another-repo", "branches": ["origin/main", "origin/feature/example"] }
|
||||||
"path": "/path/to/some-repo"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"_note": "Named refs. Each is read with ls-tree/archive, so nothing is checked out and a dirty working tree is never touched. In full mode each of these is a complete copy. A hash, a tag or HEAD~3 works anywhere a branch name does.",
|
|
||||||
"path": "/path/to/another-repo",
|
|
||||||
"branches": ["origin/main", "v1.4.0", "3f2a9c1"]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"_note": "Many branches. In full mode this would be one complete copy per branch, most of them the same bytes — the right answer for an archive and the wrong one for anything with a context window. So: origin/main whole, and the rest as changelists against it.",
|
|
||||||
"path": "/path/to/busy-repo",
|
|
||||||
"branch_mode": "diff",
|
|
||||||
"diff_base": "origin/main",
|
|
||||||
"branches": ["origin/main", "origin/topic-a", "origin/topic-b"]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"_note": "A subtree, and only it — with the repo's own ignore rules, refs and deltas still applying, because the repo root is found for you. subpath may be a list: 'the part I am working on' is usually the module plus the tests or docs beside it, and two entries for that would be two outputs to read separately. 'name' overrides what the output is called, which is what you want when the same repo appears more than once.",
|
|
||||||
"path": "slug-resolved-under-root",
|
|
||||||
"subpath": ["src/api", "tests/api"],
|
|
||||||
"with_root": true,
|
|
||||||
"name": "api-only"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"_note": "Per-entry filters and budget. One generated file does not get to spend the whole digest; this entry also keeps the lockfiles the top of the file drops, and splits later than everything else.",
|
|
||||||
"path": "/path/to/heavy-repo",
|
|
||||||
"include": ["src/**", "*.md"],
|
|
||||||
"exclude": ["src/generated/**"],
|
|
||||||
"all": true,
|
|
||||||
"max_bytes": "5M",
|
|
||||||
"clip_bytes": "64k",
|
|
||||||
"max_tokens": "80k",
|
|
||||||
"split_tokens": "250k"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"_note": "Kept in the file, out of the run. Better than deleting the entry when it is something you distil occasionally — the paths and the branch list stay written down.",
|
|
||||||
"path": "/path/to/retired-repo",
|
|
||||||
"enabled": false
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,12 +128,8 @@
|
|||||||
# --strict refuse a dirty working tree (only affects worktree copies)
|
# --strict refuse a dirty working tree (only affects worktree copies)
|
||||||
# --skip-unchanged leave alone anything whose source and settings have not
|
# --skip-unchanged leave alone anything whose source and settings have not
|
||||||
# moved since the last run into this destination
|
# moved since the last run into this destination
|
||||||
# --prune delete what an earlier run into DEST wrote and this run no
|
# --prune delete anything in DEST this run did not produce, so
|
||||||
# longer produces, so dropping a repo from the list drops its
|
# dropping a repo from the list drops its output too
|
||||||
# output too. Only what distill created is ever removed
|
|
||||||
# (DEST/.distill-owned records it): other things in DEST, and
|
|
||||||
# folders that existed before a run first synced into them,
|
|
||||||
# are never touched, so DEST can be a shared folder
|
|
||||||
# --bundle also write DEST/_BUNDLE.md: every digest concatenated into
|
# --bundle also write DEST/_BUNDLE.md: every digest concatenated into
|
||||||
# one document, for anything that takes a single file
|
# one document, for anything that takes a single file
|
||||||
# --refs-patch put the full diff, not just the diffstat, in NAME@REFS.md
|
# --refs-patch put the full diff, not just the diffstat, in NAME@REFS.md
|
||||||
@@ -1354,17 +1350,6 @@ STATE_FILE=""
|
|||||||
STATE_OLD="$TMP/state.old"
|
STATE_OLD="$TMP/state.old"
|
||||||
STATE_NEW="$TMP/state.new"
|
STATE_NEW="$TMP/state.new"
|
||||||
PRODUCED=()
|
PRODUCED=()
|
||||||
# Which top-level entries of DEST distill wrote, as of the previous run. Prune
|
|
||||||
# reads this, not the directory: DEST may be a folder shared with things that
|
|
||||||
# were never distill's, and those must survive a prune however it is set.
|
|
||||||
#
|
|
||||||
# Owned means created, not merely written to. An entry that was already there
|
|
||||||
# the first time a run wrote into it — a folder being synced into, holding files
|
|
||||||
# of its own — is updated but never claimed, so dropping it from the list can
|
|
||||||
# never delete what was there before distill.
|
|
||||||
OWNED_FILE=""
|
|
||||||
OWNED_OLD="$TMP/owned.old"
|
|
||||||
EXISTED_BEFORE="$TMP/existed.before"
|
|
||||||
|
|
||||||
# Saved after every entry, not once at the end. A run over a dozen repos and
|
# Saved after every entry, not once at the end. A run over a dozen repos and
|
||||||
# their branches takes long enough to get interrupted, and a record written only
|
# their branches takes long enough to get interrupted, and a record written only
|
||||||
@@ -1862,10 +1847,6 @@ if [ "$CMD" != list ] && [ "$CMD" != check ] && [ -z "$DRY" ]; then
|
|||||||
: > "$STATE_NEW"
|
: > "$STATE_NEW"
|
||||||
produced "$DEST/MANIFEST.md"
|
produced "$DEST/MANIFEST.md"
|
||||||
produced "$STATE_FILE"
|
produced "$STATE_FILE"
|
||||||
OWNED_FILE="$DEST/.distill-owned"
|
|
||||||
[ -f "$OWNED_FILE" ] && cp "$OWNED_FILE" "$OWNED_OLD"
|
|
||||||
find "$DEST" -mindepth 1 -maxdepth 1 -printf '%f\n' > "$EXISTED_BEFORE"
|
|
||||||
produced "$OWNED_FILE"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Repos named on the command line run under the global options, as before.
|
# Repos named on the command line run under the global options, as before.
|
||||||
@@ -1969,19 +1950,16 @@ if [ -n "$BUNDLE" ] && [ "$CMD" != tree ] && [ "$CMD" != list ] && [ -z "$DRY" ]
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Something distill wrote on an earlier run and this run did not produce came
|
# Anything at the top of the destination this run did not produce came from a
|
||||||
# from a previous, longer list. Only that: an entry distill never wrote is not
|
# previous, longer list. Scoped to depth 1 and to a destination we just wrote
|
||||||
# an orphan but someone else's, and DEST may well be a folder shared with them.
|
# to: this deletes, so it should never go hunting.
|
||||||
# Scoped to depth 1 and to a destination we just wrote to: this deletes, so it
|
if [ -n "$PRUNE" ] && [ "$CMD" != list ] && [ -z "$DRY" ]; then
|
||||||
# should never go hunting.
|
|
||||||
if [ -n "$PRUNE" ] && [ "$CMD" != list ] && [ -z "$DRY" ] && [ -f "$OWNED_OLD" ]; then
|
|
||||||
while IFS= read -r -d '' entry; do
|
while IFS= read -r -d '' entry; do
|
||||||
keep=""
|
keep=""
|
||||||
# A digest left alone as unchanged registers only its index, not the
|
# A digest left alone as unchanged registers only its index, not the
|
||||||
# parts beside it, which belong to it all the same.
|
# parts beside it, which belong to it all the same.
|
||||||
owner="$entry"
|
owner="$entry"
|
||||||
case "$entry" in *.part-[0-9][0-9].md) owner="${entry%.part-[0-9][0-9].md}.md" ;; esac
|
case "$entry" in *.part-[0-9][0-9].md) owner="${entry%.part-[0-9][0-9].md}.md" ;; esac
|
||||||
grep -qxF "$(basename "$owner")" "$OWNED_OLD" || continue
|
|
||||||
for kept in ${PRODUCED[@]+"${PRODUCED[@]}"}; do
|
for kept in ${PRODUCED[@]+"${PRODUCED[@]}"}; do
|
||||||
{ [ "$entry" = "$kept" ] || [ "$owner" = "$kept" ]; } && { keep=1; break; }
|
{ [ "$entry" = "$kept" ] || [ "$owner" = "$kept" ]; } && { keep=1; break; }
|
||||||
done
|
done
|
||||||
@@ -1992,27 +1970,6 @@ if [ -n "$PRUNE" ] && [ "$CMD" != list ] && [ -z "$DRY" ] && [ -f "$OWNED_OLD" ]
|
|||||||
done < <(find "$DEST" -mindepth 1 -maxdepth 1 -print0)
|
done < <(find "$DEST" -mindepth 1 -maxdepth 1 -print0)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Written whether or not prune is on: what this run created, plus whatever an
|
|
||||||
# earlier run created that is still on disk, so turning prune on later still
|
|
||||||
# knows everything distill put here.
|
|
||||||
if [ -n "$OWNED_FILE" ]; then
|
|
||||||
{
|
|
||||||
for kept in ${PRODUCED[@]+"${PRODUCED[@]}"}; do
|
|
||||||
kept="$(basename "$kept")"
|
|
||||||
if ! grep -qxF "$kept" "$EXISTED_BEFORE" \
|
|
||||||
|| { [ -f "$OWNED_OLD" ] && grep -qxF "$kept" "$OWNED_OLD"; }; then
|
|
||||||
printf '%s\n' "$kept"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ -f "$OWNED_OLD" ]; then
|
|
||||||
while IFS= read -r o; do
|
|
||||||
if [ -n "$o" ] && [ -e "$DEST/$o" ]; then printf '%s\n' "$o"; fi
|
|
||||||
done < "$OWNED_OLD"
|
|
||||||
fi
|
|
||||||
} | LC_ALL=C sort -u > "$OWNED_FILE.partial"
|
|
||||||
mv "$OWNED_FILE.partial" "$OWNED_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$SKIP_UNCHANGED" ] && [ "$CMD" != list ] && [ -z "$DRY" ]; then
|
if [ -n "$SKIP_UNCHANGED" ] && [ "$CMD" != list ] && [ -z "$DRY" ]; then
|
||||||
mv "$STATE_NEW" "$STATE_FILE"
|
mv "$STATE_NEW" "$STATE_FILE"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user