Merge branch 'rig-work'
This commit is contained in:
13
rig/Makefile
13
rig/Makefile
@@ -5,7 +5,6 @@
|
||||
# bash file, and that file holds the variants:
|
||||
#
|
||||
# make cluster up -> ctrl/cluster.sh up
|
||||
# make newbox destroy -> ctrl/newbox.sh destroy
|
||||
#
|
||||
# Config layers, weakest first: ctrl/versions.env (pinned toolchain) <
|
||||
# ctrl/env.d/<profile>.env (cluster shape) < ctrl/.env (local, gitignored) <
|
||||
@@ -52,7 +51,7 @@ $(eval $(ARGS):;@:)
|
||||
endif
|
||||
|
||||
.PHONY: help setup check selftest mem deps deps-image pins cluster registry addons ports \
|
||||
newbox dockerhost docs tilt \
|
||||
docs tilt \
|
||||
kind-up kind-down kind-reset tilt-up tilt-down
|
||||
|
||||
help: ## list targets
|
||||
@@ -60,7 +59,7 @@ help: ## list targets
|
||||
|
||||
# ── setup ──────────────────────────────────────────────────────────────────
|
||||
|
||||
setup: ## prepare this machine [core] [--share-docker] [--cluster]
|
||||
setup: ## prepare this machine [core] [--cluster]
|
||||
bash ctrl/setup.sh $(ARGS)
|
||||
|
||||
check: ## is this machine ready? reports, never fixes
|
||||
@@ -100,14 +99,6 @@ addons: ## profile addons [install|list] (default
|
||||
ports: ## this environment's port block [show|persist]
|
||||
bash ctrl/ports.sh $(or $(ARGS),show)
|
||||
|
||||
# ── host ───────────────────────────────────────────────────────────────────
|
||||
|
||||
newbox: ## throwaway environment [create|status|shell|destroy]
|
||||
bash ctrl/newbox.sh $(or $(ARGS),status)
|
||||
|
||||
dockerhost: ## share Docker between distros [status|share|unshare]
|
||||
$(if $(filter share unshare,$(ARGS)),sudo ,)bash ctrl/dockerhost.sh $(or $(ARGS),status)
|
||||
|
||||
# ── docs + dev loop ────────────────────────────────────────────────────────
|
||||
|
||||
docs: ## documentation [serve|graphs] (default serve)
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Share ONE Docker daemon across WSL distros, instead of running one per distro.
|
||||
#
|
||||
# Why this exists
|
||||
# ---------------
|
||||
# WSL2 distros share a kernel and a network stack. Two dockerd instances then
|
||||
# contend over docker0 and iptables, which can disturb the daemon you actually
|
||||
# depend on. Docker Desktop avoids this by running a single daemon in a
|
||||
# dedicated distro and sharing its socket — this is the same idea, without
|
||||
# Docker Desktop.
|
||||
#
|
||||
# So a throwaway rig box does NOT install Docker. It borrows the daemon from
|
||||
# whichever distro is the designated host. That also makes the test more honest:
|
||||
# rig never installs Docker anyway — Docker is its documented prerequisite.
|
||||
#
|
||||
# How
|
||||
# ---
|
||||
# /mnt/wsl is a tmpfs with `shared` mount propagation, visible to every distro
|
||||
# in the WSL VM. The owning distro exposes its socket there; guests point
|
||||
# DOCKER_HOST at it. Two ways, with different costs:
|
||||
#
|
||||
# share bind-mount the existing socket onto the shared tmpfs.
|
||||
# Instant, and dockerd is NEVER restarted. Lasts until the
|
||||
# next WSL shutdown.
|
||||
# share --persist additionally install a systemd drop-in so dockerd listens
|
||||
# there itself. Survives restarts, but requires one Docker
|
||||
# restart now — which stops every container that has no
|
||||
# restart policy, since live-restore is off by default.
|
||||
#
|
||||
# The bind mount is the default precisely because the persistent version's cost
|
||||
# is paid on a machine that is already working.
|
||||
#
|
||||
# Reversibility is the whole design
|
||||
# ---------------------------------
|
||||
# `unshare` removes the bind mount (no restart) and, if present, the drop-in.
|
||||
# The original systemd unit is never edited — only an additive drop-in file is
|
||||
# ever created — so undoing is deletion, not repair. `status` always states
|
||||
# which of the three roles a distro is in, in those words.
|
||||
#
|
||||
# Nothing here runs automatically. It does nothing until invoked.
|
||||
#
|
||||
# Usage:
|
||||
# dockerhost.sh status # which distro owns Docker; what this one uses
|
||||
# dockerhost.sh share # share it (bind mount, no daemon restart)
|
||||
# dockerhost.sh share --persist # ...and survive WSL restarts (restarts Docker)
|
||||
# dockerhost.sh unshare # undo it; this distro owns its Docker again
|
||||
# dockerhost.sh use [--persist] # point THIS distro at the shared socket
|
||||
set -euo pipefail
|
||||
|
||||
SHARED_DIR=/mnt/wsl/shared-docker
|
||||
SHARED_SOCK="$SHARED_DIR/docker.sock"
|
||||
OWNER_FILE="$SHARED_DIR/OWNER"
|
||||
DROPIN=/etc/systemd/system/docker.service.d/10-rig-shared-socket.conf
|
||||
PROFILE_D=/etc/profile.d/rig-docker-host.sh
|
||||
|
||||
distro_name() { echo "${WSL_DISTRO_NAME:-$(hostname)}"; }
|
||||
|
||||
require_wsl() {
|
||||
grep -qi microsoft /proc/version 2>/dev/null && return 0
|
||||
echo "dockerhost is WSL-only: it relies on /mnt/wsl being shared between distros." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── status ─────────────────────────────────────────────────────────────────
|
||||
|
||||
status() {
|
||||
require_wsl
|
||||
echo "distro $(distro_name)"
|
||||
|
||||
if [ -f "$DROPIN" ] || mountpoint -q "$SHARED_SOCK" 2>/dev/null; then
|
||||
echo "role SHARING — this distro's Docker is offered to other distros"
|
||||
elif [ -n "${DOCKER_HOST:-}" ] && [ "${DOCKER_HOST}" = "unix://$SHARED_SOCK" ]; then
|
||||
echo "role BORROWING — using another distro's Docker"
|
||||
else
|
||||
echo "role standalone — this WSL installation has the main host Docker"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ -S "$SHARED_SOCK" ]; then
|
||||
echo "shared sock $SHARED_SOCK (present)"
|
||||
[ -f "$OWNER_FILE" ] && sed 's/^/ /' "$OWNER_FILE"
|
||||
else
|
||||
echo "shared sock none — no distro is sharing right now"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "DOCKER_HOST ${DOCKER_HOST:-(unset — using /var/run/docker.sock)}"
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
echo "docker $(docker version --format '{{.Server.Version}}' 2>/dev/null || echo unreachable)"
|
||||
else
|
||||
echo "docker cli not installed"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── share / unshare (run on the host distro) ───────────────────────────────
|
||||
|
||||
# Default: expose the EXISTING socket by bind-mounting it onto the shared tmpfs.
|
||||
# /mnt/wsl has `shared` propagation, so the mount is visible in other distros.
|
||||
#
|
||||
# The point of doing it this way is that dockerd is never restarted. Restarting
|
||||
# it stops every container that has no restart policy (live-restore is off by
|
||||
# default), which on a working machine means quietly killing whatever you had
|
||||
# running. Not a trade worth making just to expose a socket.
|
||||
#
|
||||
# Cost: a bind mount does not survive a WSL VM shutdown. `--persist` adds the
|
||||
# systemd drop-in as well, which does survive but needs that one restart.
|
||||
share_bind() {
|
||||
mkdir -p "$SHARED_DIR"
|
||||
chmod 0755 "$SHARED_DIR"
|
||||
|
||||
if mountpoint -q "$SHARED_SOCK" 2>/dev/null; then
|
||||
echo "already bind-mounted at $SHARED_SOCK"
|
||||
else
|
||||
[ -S /var/run/docker.sock ] || { echo "no /var/run/docker.sock here" >&2; exit 1; }
|
||||
# The target must exist as a file for a bind mount onto it.
|
||||
[ -e "$SHARED_SOCK" ] || : > "$SHARED_SOCK"
|
||||
mount --bind /var/run/docker.sock "$SHARED_SOCK"
|
||||
echo "bind-mounted /var/run/docker.sock -> $SHARED_SOCK (no daemon restart)"
|
||||
fi
|
||||
|
||||
cat > "$OWNER_FILE" <<EOF
|
||||
owner distro: $(distro_name)
|
||||
docker gid: $(getent group docker | cut -d: -f3)
|
||||
socket: $SHARED_SOCK
|
||||
method: bind-mount (until the next WSL shutdown)
|
||||
EOF
|
||||
}
|
||||
|
||||
share() {
|
||||
require_wsl
|
||||
[ "$(id -u)" -eq 0 ] || { echo "run with sudo: sudo bash ctrl/dockerhost.sh share" >&2; exit 1; }
|
||||
|
||||
share_bind
|
||||
|
||||
if [ "${1:-}" != "--persist" ]; then
|
||||
echo
|
||||
echo "This lasts until the next WSL shutdown. To make it survive, re-run with"
|
||||
echo "--persist — but note that adds a systemd drop-in and RESTARTS Docker,"
|
||||
echo "which stops any container that has no restart policy."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$DROPIN" ]; then
|
||||
echo "drop-in already present — sharing persists across restarts."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "--persist: installing a systemd drop-in and restarting Docker."
|
||||
echo "Containers without a restart policy will stop and will NOT come back."
|
||||
docker ps --format ' {{.Names}} restart={{.HostConfig.RestartPolicy.Name}}' 2>/dev/null \
|
||||
|| docker ps --format ' {{.Names}}' 2>/dev/null || true
|
||||
echo
|
||||
|
||||
local exec_line
|
||||
exec_line=$(systemctl cat docker.service | grep -m1 '^ExecStart=')
|
||||
if [ -z "$exec_line" ]; then
|
||||
echo "could not read docker.service ExecStart — refusing to guess" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$DROPIN")" "$SHARED_DIR"
|
||||
# Additive only: blank the inherited ExecStart, then restate it verbatim
|
||||
# with one extra -H. Nothing about the original unit is edited.
|
||||
cat > "$DROPIN" <<EOF
|
||||
# Added by rig (ctrl/dockerhost.sh share).
|
||||
#
|
||||
# Adds a SECOND listening socket on the WSL-shared tmpfs so other distros can
|
||||
# use this daemon instead of running their own. The original socket is
|
||||
# untouched, so this distro behaves exactly as before.
|
||||
#
|
||||
# To undo: sudo bash ctrl/dockerhost.sh unshare
|
||||
[Service]
|
||||
ExecStartPre=-/bin/mkdir -p $SHARED_DIR
|
||||
ExecStartPre=-/bin/chmod 0755 $SHARED_DIR
|
||||
ExecStart=
|
||||
${exec_line} -H unix://$SHARED_SOCK
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl restart docker
|
||||
|
||||
# Guests need a group with a MATCHING GID to use the socket; GIDs are not
|
||||
# consistent across distros, so record ours rather than assume.
|
||||
cat > "$OWNER_FILE" <<EOF
|
||||
owner distro: $(distro_name)
|
||||
docker gid: $(getent group docker | cut -d: -f3)
|
||||
socket: $SHARED_SOCK
|
||||
EOF
|
||||
|
||||
echo "sharing from '$(distro_name)'"
|
||||
echo " guests: export DOCKER_HOST=unix://$SHARED_SOCK"
|
||||
echo " undo: sudo bash ctrl/dockerhost.sh unshare"
|
||||
echo
|
||||
echo "NOTE: /mnt/wsl is tmpfs and is cleared when the WSL VM shuts down."
|
||||
echo " The drop-in recreates the directory on the next Docker start."
|
||||
}
|
||||
|
||||
unshare_() {
|
||||
require_wsl
|
||||
[ "$(id -u)" -eq 0 ] || { echo "run with sudo: sudo bash ctrl/dockerhost.sh unshare" >&2; exit 1; }
|
||||
|
||||
local did=0
|
||||
|
||||
# The bind mount first: undoing it needs no restart, so a plain `share`
|
||||
# is fully reversible without disturbing anything.
|
||||
if mountpoint -q "$SHARED_SOCK" 2>/dev/null; then
|
||||
umount "$SHARED_SOCK"
|
||||
rm -f "$SHARED_SOCK"
|
||||
echo " removed the bind mount (no restart needed)"
|
||||
did=1
|
||||
fi
|
||||
rm -f "$OWNER_FILE"
|
||||
rmdir "$SHARED_DIR" 2>/dev/null || true
|
||||
|
||||
if [ -f "$DROPIN" ]; then
|
||||
rm -f "$DROPIN"
|
||||
rmdir "$(dirname "$DROPIN")" 2>/dev/null || true
|
||||
systemctl daemon-reload
|
||||
systemctl restart docker
|
||||
echo " removed the systemd drop-in and restarted Docker"
|
||||
did=1
|
||||
fi
|
||||
|
||||
if [ "$did" -eq 0 ]; then
|
||||
echo "not sharing — this WSL installation already has the main host Docker."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "restored: this WSL installation has the main host Docker again."
|
||||
echo " (nothing else was changed; the original unit was never edited)"
|
||||
}
|
||||
|
||||
# ── use (run on a guest distro) ────────────────────────────────────────────
|
||||
|
||||
use() {
|
||||
require_wsl
|
||||
if [ ! -S "$SHARED_SOCK" ]; then
|
||||
echo "no shared socket at $SHARED_SOCK" >&2
|
||||
echo "Run 'sudo bash ctrl/dockerhost.sh share' in the distro that owns Docker." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Align the local docker group GID with the owner's, or the socket is
|
||||
# unreadable here even though it is visible.
|
||||
if [ -f "$OWNER_FILE" ] && [ "$(id -u)" -eq 0 ]; then
|
||||
local gid; gid=$(awk '/docker gid:/ {print $3}' "$OWNER_FILE")
|
||||
if [ -n "$gid" ]; then
|
||||
if getent group docker >/dev/null; then
|
||||
[ "$(getent group docker | cut -d: -f3)" = "$gid" ] || groupmod -g "$gid" docker
|
||||
else
|
||||
groupadd -g "$gid" docker
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "--persist" ]; then
|
||||
[ "$(id -u)" -eq 0 ] || { echo "--persist needs root" >&2; exit 1; }
|
||||
echo "export DOCKER_HOST=unix://$SHARED_SOCK" > "$PROFILE_D"
|
||||
echo "persisted in $PROFILE_D"
|
||||
fi
|
||||
|
||||
echo "export DOCKER_HOST=unix://$SHARED_SOCK"
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
status) status ;;
|
||||
share) shift; share "${1:-}" ;;
|
||||
unshare) unshare_ ;;
|
||||
use) shift; use "${1:-}" ;;
|
||||
*) echo "usage: $0 [status|share|unshare|use [--persist]]" >&2; exit 1 ;;
|
||||
esac
|
||||
@@ -1,15 +0,0 @@
|
||||
# /etc/hosts block for this environment. Rendered by newbox.sh; ${CLUSTER} and
|
||||
# ${HTTP_PORT} are substituted.
|
||||
#
|
||||
# Hostnames are a convenience, not a requirement — every service is reachable at
|
||||
# localhost:<port> without any of this, which is why DNS is not touched by
|
||||
# default. Add entries here as the model grows.
|
||||
#
|
||||
# On Windows the same block has to go in
|
||||
# C:\Windows\System32\drivers\etc\hosts for a browser to resolve these. That
|
||||
# file does NOT support wildcards, so every name must be listed explicitly.
|
||||
# newbox.sh prints the block for you to paste rather than editing it.
|
||||
|
||||
127.0.0.1 ${CLUSTER}.local
|
||||
127.0.0.1 api.${CLUSTER}.local
|
||||
127.0.0.1 docs.${CLUSTER}.local
|
||||
@@ -1,316 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create a disposable Linux environment to validate the installer from a
|
||||
# genuinely clean slate — one that can be thrown away without touching the
|
||||
# environment you actually work in.
|
||||
#
|
||||
# This is the ONLY host-aware file in the tree. Everything else needs just a
|
||||
# Linux with Docker, which is what keeps other host types a later addition
|
||||
# rather than a rewrite.
|
||||
#
|
||||
# On WSL it creates a second distro. There is no .bat and no PowerShell script:
|
||||
# wsl.exe is callable from inside WSL, and wslpath converts the paths it wants.
|
||||
# A machine with no WSL at all needs `wsl --install` run once by hand first —
|
||||
# scripting a reboot-requiring Windows feature install is not worth it.
|
||||
#
|
||||
# Docker: borrowed by default, never installed twice
|
||||
# --------------------------------------------------
|
||||
# WSL2 distros share one kernel and one network stack, so two dockerd instances
|
||||
# contend over docker0 and iptables and can disturb the daemon you depend on.
|
||||
# (That is why Docker Desktop runs one daemon in a dedicated distro and shares
|
||||
# its socket rather than installing one per distro.)
|
||||
#
|
||||
# REUSE_DOCKER=1 (default) borrow the host distro's daemon over /mnt/wsl.
|
||||
# Nothing is installed; nothing can conflict.
|
||||
# Requires `ctrl/dockerhost.sh share` once on the
|
||||
# distro that owns Docker.
|
||||
# REUSE_DOCKER=0 install a second daemon in the new distro. Only
|
||||
# if you specifically want to test a from-scratch
|
||||
# Docker install, and not on a machine you need.
|
||||
#
|
||||
# Borrowing is also the more honest test: rig never installs Docker anyway — it
|
||||
# is the documented prerequisite — so a clean box does not need its own to
|
||||
# exercise everything rig actually does.
|
||||
#
|
||||
# Usage: newbox.sh create | destroy [--purge] | status | shell
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
source ./lib/config.sh
|
||||
load_config
|
||||
|
||||
REPO="$(cd .. && pwd)"
|
||||
|
||||
# The distro is named after this environment, and that derived name is the ONLY
|
||||
# thing this script will ever destroy. See guard_name().
|
||||
BOX="${BOX:-${CLUSTER}box}"
|
||||
BOX_USER="${BOX_USER:-dev}"
|
||||
|
||||
# Borrow the host distro's Docker rather than installing a second daemon.
|
||||
REUSE_DOCKER="${REUSE_DOCKER:-1}"
|
||||
SHARED_SOCK=/mnt/wsl/shared-docker/docker.sock
|
||||
|
||||
WSL_EXE=/mnt/c/Windows/System32/wsl.exe
|
||||
|
||||
# ── host detection ─────────────────────────────────────────────────────────
|
||||
|
||||
require_wsl() {
|
||||
if ! grep -qi microsoft /proc/version 2>/dev/null; then
|
||||
cat >&2 <<'EOF'
|
||||
newbox is WSL-only for now.
|
||||
|
||||
If WSL is not installed, run `wsl --install` from an elevated Windows prompt
|
||||
first — see "Starting from plain Windows" in README.md.
|
||||
|
||||
On native Linux you do not need it: rig already isolates environments by
|
||||
directory (own cluster, context, images and port block), so a second copy in a
|
||||
second directory is the clean slate. To validate the installer itself against a
|
||||
bare system, run ctrl/deps.sh against a stock Debian container instead.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -x "$WSL_EXE" ]; then
|
||||
echo "wsl.exe not found at $WSL_EXE" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
wsl_list() { "$WSL_EXE" -l -q 2>/dev/null | tr -d '\0\r'; }
|
||||
box_exists() { wsl_list | grep -qx "$BOX"; }
|
||||
|
||||
# `wsl --unregister` permanently deletes a distro's filesystem. The whole safety
|
||||
# story is this function: only the name derived from this directory can ever be
|
||||
# a target, so a typo or a stray argument cannot destroy the distro you work in.
|
||||
guard_name() {
|
||||
local derived="${CLUSTER}box"
|
||||
if [ "$BOX" != "$derived" ]; then
|
||||
echo "refusing: BOX='$BOX' is not the name derived from this directory ('$derived')." >&2
|
||||
echo "That guard exists because --unregister is irreversible." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$CLUSTER" ] || [ "$BOX" = "box" ]; then
|
||||
echo "refusing: empty environment name" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ── create ─────────────────────────────────────────────────────────────────
|
||||
|
||||
rootfs_path() {
|
||||
local win_home; win_home=$(wslpath "$("$WSL_EXE" -d "$(wsl_list | head -1)" -e printf '%s' "$USERPROFILE" 2>/dev/null || true)" 2>/dev/null || true)
|
||||
# Simpler and reliable: use the current user's Windows home via /mnt/c.
|
||||
ls -d /mnt/c/Users/*/ 2>/dev/null | grep -viE '/(All Users|Default|Default User|Public)/$' | head -1
|
||||
}
|
||||
|
||||
build_rootfs() {
|
||||
local tar="$1"
|
||||
if [ -f "$tar" ]; then
|
||||
echo " rootfs cached: $(basename "$tar")"
|
||||
return
|
||||
fi
|
||||
echo " exporting a stock Debian rootfs (cached for next time)"
|
||||
local cid; cid=$(docker create debian:trixie-slim)
|
||||
docker export "$cid" > "$tar"
|
||||
docker rm -f "$cid" >/dev/null
|
||||
}
|
||||
|
||||
provision() {
|
||||
echo " provisioning (root)"
|
||||
local hosts_block
|
||||
hosts_block=$(CLUSTER="$CLUSTER" HTTP_PORT="$HTTP_PORT" \
|
||||
envsubst < ./hosts.tmpl 2>/dev/null || sed "s/\${CLUSTER}/$CLUSTER/g" ./hosts.tmpl)
|
||||
|
||||
# Piped as stdin rather than a second script file, the same shape as any
|
||||
# remote provisioning heredoc. Everything here is idempotent so a failed run
|
||||
# can simply be repeated.
|
||||
"$WSL_EXE" -d "$BOX" -u root -- bash -s <<PROVISION
|
||||
set -euo pipefail
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq ca-certificates curl gnupg sudo >/dev/null
|
||||
|
||||
if [ "$REUSE_DOCKER" = "1" ]; then
|
||||
# Borrow the host distro's daemon: CLI only, no dockerd, nothing to
|
||||
# conflict with. The GID must match the owner's or the shared socket is
|
||||
# unreadable here even though it is visible.
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
if [ ! -f /etc/apt/keyrings/docker.asc ]; then
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
fi
|
||||
echo "deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \$(. /etc/os-release && echo \$VERSION_CODENAME) stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq docker-ce-cli >/dev/null
|
||||
|
||||
echo "export DOCKER_HOST=unix://$SHARED_SOCK" > /etc/profile.d/rig-docker-host.sh
|
||||
|
||||
if [ -f /mnt/wsl/shared-docker/OWNER ]; then
|
||||
gid=\$(awk '/docker gid:/ {print \$3}' /mnt/wsl/shared-docker/OWNER)
|
||||
if [ -n "\$gid" ]; then
|
||||
getent group docker >/dev/null && groupmod -g "\$gid" docker || groupadd -g "\$gid" docker
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# A second daemon. Only when deliberately testing a from-scratch install.
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
if [ ! -f /etc/apt/keyrings/docker.asc ]; then
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
fi
|
||||
echo "deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \$(. /etc/os-release && echo \$VERSION_CODENAME) stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq docker-ce docker-ce-cli containerd.io >/dev/null
|
||||
fi
|
||||
|
||||
id -u "$BOX_USER" >/dev/null 2>&1 || useradd -m -s /bin/bash "$BOX_USER"
|
||||
usermod -aG sudo,docker "$BOX_USER"
|
||||
echo "$BOX_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-$BOX_USER
|
||||
chmod 0440 /etc/sudoers.d/90-$BOX_USER
|
||||
|
||||
# systemd is off by default in WSL, and Docker needs it. Takes effect on the
|
||||
# next start of this distro, which is why create() terminates it below.
|
||||
cat > /etc/wsl.conf <<WSLCONF
|
||||
[boot]
|
||||
systemd=true
|
||||
|
||||
[user]
|
||||
default=$BOX_USER
|
||||
WSLCONF
|
||||
|
||||
# The default inotify limits are low enough that file watching silently stops
|
||||
# working — no error, changes just stop being noticed. Fix it before it bites.
|
||||
cat > /etc/sysctl.d/99-rig.conf <<SYSCTL
|
||||
fs.inotify.max_user_watches=524288
|
||||
fs.inotify.max_user_instances=512
|
||||
SYSCTL
|
||||
|
||||
if ! grep -q 'rig environment' /etc/hosts 2>/dev/null; then
|
||||
{ echo ""; echo "# rig environment"; cat <<'HOSTS'
|
||||
$hosts_block
|
||||
HOSTS
|
||||
} >> /etc/hosts
|
||||
fi
|
||||
|
||||
touch /etc/rig-provisioned
|
||||
PROVISION
|
||||
}
|
||||
|
||||
create() {
|
||||
require_wsl
|
||||
guard_name
|
||||
|
||||
local winhome; winhome=$(rootfs_path)
|
||||
[ -n "$winhome" ] || { echo "could not locate the Windows user directory" >&2; exit 1; }
|
||||
local tar="${winhome}rig-rootfs.tar"
|
||||
local installdir="${winhome}WSL/${BOX}"
|
||||
|
||||
echo "creating '$BOX'"
|
||||
if [ "$REUSE_DOCKER" = "1" ]; then
|
||||
echo " docker: borrowing the host distro's daemon (nothing installed)"
|
||||
if [ ! -S "$SHARED_SOCK" ]; then
|
||||
echo
|
||||
echo " No shared socket yet. In the distro that owns Docker, run once:"
|
||||
echo " sudo bash ctrl/dockerhost.sh share"
|
||||
echo " That adds one systemd drop-in and nothing else; undo with 'unshare'."
|
||||
echo " Continuing — the box will be created, but Docker won't work in it"
|
||||
echo " until you do that."
|
||||
fi
|
||||
else
|
||||
echo
|
||||
echo " REUSE_DOCKER=0: installing a SECOND Docker daemon."
|
||||
echo " WSL distros share a network stack, so this can disturb Docker in"
|
||||
echo " the distro you work in. Ctrl-C now if that is a bad trade today."
|
||||
echo
|
||||
sleep 4
|
||||
fi
|
||||
echo
|
||||
|
||||
if box_exists; then
|
||||
echo " distro already registered"
|
||||
else
|
||||
build_rootfs "$tar"
|
||||
mkdir -p "$installdir"
|
||||
"$WSL_EXE" --import "$BOX" "$(wslpath -w "$installdir")" "$(wslpath -w "$tar")" --version 2
|
||||
fi
|
||||
|
||||
# Resumable: a partially-created box is finished rather than restarted.
|
||||
if "$WSL_EXE" -d "$BOX" -u root -- test -f /etc/rig-provisioned 2>/dev/null; then
|
||||
echo " already provisioned"
|
||||
else
|
||||
provision
|
||||
echo " restarting the distro so systemd and group membership apply"
|
||||
"$WSL_EXE" --terminate "$BOX" # ONLY this distro; never --shutdown
|
||||
fi
|
||||
|
||||
echo " copying rig in"
|
||||
tar c -C "$REPO" --exclude=def --exclude=.git --exclude=ctrl/.env . \
|
||||
| "$WSL_EXE" -d "$BOX" -u "$BOX_USER" -- bash -lc "mkdir -p ~/rig && tar x -C ~/rig"
|
||||
|
||||
echo
|
||||
echo " docker: $("$WSL_EXE" -d "$BOX" -u "$BOX_USER" -- bash -lc 'systemctl is-active docker 2>/dev/null || echo inactive')"
|
||||
echo
|
||||
echo "next:"
|
||||
echo " make newbox shell # a shell inside it"
|
||||
echo " then: cd ~/rig && make check && make deps && make cluster up"
|
||||
echo
|
||||
echo "For a browser on Windows to resolve the hostnames, paste this into"
|
||||
echo "C:\\Windows\\System32\\drivers\\etc\\hosts (it has no wildcard support):"
|
||||
CLUSTER="$CLUSTER" envsubst < ./hosts.tmpl 2>/dev/null | grep -v '^#' | grep -v '^$' | sed 's/^/ /'
|
||||
}
|
||||
|
||||
# ── the rest ───────────────────────────────────────────────────────────────
|
||||
|
||||
destroy() {
|
||||
require_wsl
|
||||
guard_name
|
||||
|
||||
if ! box_exists; then
|
||||
echo "no distro '$BOX' to remove"
|
||||
else
|
||||
echo "about to PERMANENTLY delete the distro '$BOX' and its filesystem."
|
||||
"$WSL_EXE" --terminate "$BOX" 2>/dev/null || true
|
||||
"$WSL_EXE" --unregister "$BOX"
|
||||
echo " unregistered"
|
||||
fi
|
||||
|
||||
local winhome; winhome=$(rootfs_path)
|
||||
rm -rf "${winhome}WSL/${BOX}" 2>/dev/null || true
|
||||
|
||||
if [ "${1:-}" = "--purge" ]; then
|
||||
rm -f "${winhome}rig-rootfs.tar"
|
||||
echo " cached rootfs removed"
|
||||
fi
|
||||
}
|
||||
|
||||
status() {
|
||||
require_wsl
|
||||
echo "environment $CLUSTER"
|
||||
echo "distro $BOX"
|
||||
if box_exists; then
|
||||
echo "registered yes"
|
||||
echo "provisioned $("$WSL_EXE" -d "$BOX" -u root -- test -f /etc/rig-provisioned 2>/dev/null && echo yes || echo no)"
|
||||
echo "docker $("$WSL_EXE" -d "$BOX" -u root -- bash -lc 'systemctl is-active docker 2>/dev/null' || echo unknown)"
|
||||
echo "rig copied $("$WSL_EXE" -d "$BOX" -u "$BOX_USER" -- bash -lc 'test -f ~/rig/Makefile && echo yes || echo no' 2>/dev/null)"
|
||||
else
|
||||
echo "registered no"
|
||||
fi
|
||||
echo
|
||||
echo "all distros (this one is never touched unless it is '$BOX'):"
|
||||
wsl_list | sed 's/^/ /'
|
||||
}
|
||||
|
||||
shell() {
|
||||
require_wsl
|
||||
box_exists || { echo "no distro '$BOX' — run 'make newbox' first" >&2; exit 1; }
|
||||
"$WSL_EXE" -d "$BOX" -u "$BOX_USER" --cd '~'
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
create) create ;;
|
||||
destroy) shift; destroy "${1:-}" ;;
|
||||
status) status ;;
|
||||
shell) shell ;;
|
||||
*) echo "usage: $0 [create|destroy [--purge]|status|shell]" >&2; exit 1 ;;
|
||||
esac
|
||||
@@ -168,6 +168,26 @@ check "guards on the variable" "1" "$(grep -c 'allow_k8s_contexts(CTX)' Tiltfil
|
||||
check "asks ports.sh for facts" "1" "$(grep -c "local('bash ports.sh active'" Tiltfile)"
|
||||
|
||||
|
||||
note "the standalone Makefile only calls real verbs"
|
||||
# That file exists so nothing wrapping these scripts has to GUESS how to call
|
||||
# them. A generated Makefile once did guess: `rigmini.sh on`, which is not a
|
||||
# verb, and a bare `rigdeps.sh` for "check and report", which installs. So each
|
||||
# target's default verb must be one the script's own dispatch accepts — read
|
||||
# from the script's `case`, not from a list here that could drift from it.
|
||||
verbs_of() {
|
||||
sed -n '/^case "\$cmd" in/,/^esac/p' "$1" | grep -oE '^ [a-z]+\)' | tr -d ' )'
|
||||
}
|
||||
for pair in deps:rigdeps.sh mem:rigmini.sh; do
|
||||
target=${pair%%:*}; script=${pair##*:}
|
||||
verb="$(make --no-print-directory -s -n -f ../standalone/Makefile "$target" 2>/dev/null \
|
||||
| awk -v s="$script" 'index($0, s) { print $NF; exit }')"
|
||||
check "make $target -> $script ${verb:-?}, a verb it accepts" "yes" \
|
||||
"$(verbs_of "../standalone/$script" | grep -qx "$verb" && echo yes || echo "no: '$verb'")"
|
||||
done
|
||||
check "no \`mini\` target, which already means minimal footprint" "0" \
|
||||
"$(grep -cE '^mini:' ../standalone/Makefile)"
|
||||
|
||||
|
||||
note "optional — needs tilt and this rig's cluster"
|
||||
# Parsing the Tiltfile for real is the only way to know it still evaluates, but
|
||||
# Tilt snapshots a kubectl context before parsing, so it cannot run without a
|
||||
|
||||
@@ -11,13 +11,9 @@
|
||||
# an unfamiliar machine, the full picture is the whole point. Failures are
|
||||
# collected and reported together, and the exit code reflects the worst outcome.
|
||||
#
|
||||
# The same script runs inside a fresh throwaway distro (newbox), so the
|
||||
# provisioning path and the everyday path cannot drift apart.
|
||||
#
|
||||
# Usage:
|
||||
# setup.sh # host checks + the dev toolchain
|
||||
# setup.sh core # kubectl and jq only — no cluster tooling
|
||||
# setup.sh --share-docker # ...and offer this distro's Docker to others
|
||||
# setup.sh --cluster # ...and bring the cluster up
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
@@ -25,7 +21,6 @@ cd "$(dirname "$0")"
|
||||
source ./lib/config.sh
|
||||
load_config
|
||||
|
||||
WITH_SHARE=0
|
||||
WITH_CLUSTER=0
|
||||
# Cluster tooling is not wanted everywhere: a managed or corporate-issued
|
||||
# machine may legitimately want kubectl and nothing that builds clusters.
|
||||
@@ -33,7 +28,6 @@ TIER=dev
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
core|dev) TIER="$a" ;;
|
||||
--share-docker) WITH_SHARE=1 ;;
|
||||
--cluster) WITH_CLUSTER=1 ;;
|
||||
*) echo "unknown option: $a" >&2; exit 1 ;;
|
||||
esac
|
||||
@@ -143,33 +137,6 @@ step_docker() {
|
||||
fi
|
||||
}
|
||||
|
||||
step_share_docker() {
|
||||
if [ "$WITH_SHARE" -ne 1 ]; then
|
||||
record docker-share skip "not requested (--share-docker)"
|
||||
return
|
||||
fi
|
||||
if ! grep -qi microsoft /proc/version 2>/dev/null; then
|
||||
record docker-share skip "not WSL — sharing only applies between WSL distros"
|
||||
return
|
||||
fi
|
||||
if [ -f /etc/systemd/system/docker.service.d/10-rig-shared-socket.conf ]; then
|
||||
record docker-share already "this distro is offering its Docker to others"
|
||||
return
|
||||
fi
|
||||
# Needs root, and asking mid-script is worse than telling the user the
|
||||
# single command to run.
|
||||
if [ "$(id -u)" -ne 0 ] && ! sudo -n true 2>/dev/null; then
|
||||
record docker-share manual "run: sudo bash ctrl/dockerhost.sh share"
|
||||
return
|
||||
fi
|
||||
if sudo bash ./dockerhost.sh share >/tmp/rig-share.$$ 2>&1; then
|
||||
record docker-share done "this distro now owns the shared Docker"
|
||||
rm -f "/tmp/rig-share.$$"
|
||||
else
|
||||
record docker-share fail "see /tmp/rig-share.$$"
|
||||
fi
|
||||
}
|
||||
|
||||
step_ports() {
|
||||
local busy=""
|
||||
for entry in "HTTP:$HTTP_PORT" "HTTPS:$HTTPS_PORT" "TILT:$TILT_PORT" "REGISTRY:$REGISTRY_PORT"; do
|
||||
@@ -215,7 +182,6 @@ step_host
|
||||
step_toolchain
|
||||
step_path
|
||||
step_docker
|
||||
step_share_docker
|
||||
step_ports
|
||||
step_cluster
|
||||
|
||||
|
||||
@@ -298,7 +298,6 @@ make cluster up <span class="c"># build the cluster for the active profile</spa
|
||||
unfamiliar machine the complete list is the point. The tail of the output is
|
||||
a to-do list of only what is outstanding.</p>
|
||||
<pre><code>make setup <span class="c"># host + toolchain</span>
|
||||
make setup --share-docker <span class="c"># ...and offer this machine's Docker to other distros</span>
|
||||
</code></pre>
|
||||
|
||||
<h3>3 · make cluster up</h3>
|
||||
@@ -325,7 +324,6 @@ make cluster reset <span class="c"># destroy and rebuild — the on
|
||||
<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 ports</dt><dd>This environment's port block, and whether each is derived or overridden.</dd>
|
||||
<dt>make registry</dt><dd>Which of the four registry modes is active, and where it points.</dd>
|
||||
<dt>make dockerhost</dt><dd>Which WSL distro owns Docker and what this one is using.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Running more than one</h3>
|
||||
|
||||
43
rig/standalone/Makefile
Normal file
43
rig/standalone/Makefile
Normal file
@@ -0,0 +1,43 @@
|
||||
# Optional shorthand for the two standalone scripts. They run without it:
|
||||
# `bash rigdeps.sh detect` and `bash rigmini.sh status` are the whole interface,
|
||||
# and this file only spells those out so nobody has to guess them.
|
||||
#
|
||||
# Guessing is what went wrong before. A Makefile generated around these scripts
|
||||
# invented `make mini` -> `rigmini.sh on`, a verb that does not exist, and a bare
|
||||
# `rigdeps.sh` for "check and report", which actually installs. So every target
|
||||
# here calls only a verb the script accepts, and ctrl/selftest.sh checks that.
|
||||
#
|
||||
# Works wherever the three files sit together — the scripts are found beside
|
||||
# this Makefile, not in the current directory, so `make -f path/Makefile` works.
|
||||
#
|
||||
# Usage:
|
||||
# make deps report the host and toolchain; changes nothing
|
||||
# make deps install [dev] download, verify and install into ~/.local/bin
|
||||
# make mem advertised memory and what caps it; safe
|
||||
# make mem push allocates until it stops — not on a box you need
|
||||
|
||||
HERE := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
# Words after the target become the script's verb and arguments.
|
||||
ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
|
||||
ifneq ($(ARGS),)
|
||||
$(eval $(ARGS):;@:)
|
||||
.PHONY: $(ARGS)
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: help deps mem
|
||||
|
||||
help: ## list targets
|
||||
@grep -hE '^[a-z][a-z-]*:.*?##' $(MAKEFILE_LIST) | sed 's/:.*##/\t/' | expand -t16
|
||||
|
||||
# Defaults to `detect`, not `install`: on a machine you are still evaluating,
|
||||
# the bare command should report. Installing is a word you type.
|
||||
deps: ## toolchain [detect|list|verify|install|fetch] (default detect)
|
||||
bash $(HERE)rigdeps.sh $(or $(ARGS),detect)
|
||||
|
||||
# `mem`, matching the full rig's `make mem`, and deliberately NOT `mini`: that
|
||||
# word already means "minimal footprint" in the projects that use this, and one
|
||||
# name pointing at two jobs is how the invented target happened.
|
||||
mem: ## memory and its caps [status|push|all] (default status)
|
||||
bash $(HERE)rigmini.sh $(or $(ARGS),status)
|
||||
@@ -39,6 +39,20 @@ bash rigmini.sh status # advertised memory and caps; safe
|
||||
bash rigmini.sh push # allocates until it stops — not on a machine you need
|
||||
```
|
||||
|
||||
The `Makefile` beside them is **optional shorthand** for exactly those calls —
|
||||
copy it along or don't; the scripts do not need it:
|
||||
|
||||
```bash
|
||||
make deps # = rigdeps.sh detect (reports; installing is `make deps install`)
|
||||
make mem # = rigmini.sh status
|
||||
```
|
||||
|
||||
If you wrap these scripts in a Makefile of your own, copy the calls from that
|
||||
file rather than guessing them. `rigmini.sh` measures memory; it has no `on` or
|
||||
`off`, and toggling a heavy service off for a smaller footprint is a job for the
|
||||
project's own manifests, not for this script. `make selftest` in the full rig
|
||||
fails if this Makefile ever calls a verb its script does not accept.
|
||||
|
||||
If an earlier setup already put these tools in some other directory on PATH,
|
||||
remove that directory and the line that added it — do not rely on `install` to
|
||||
notice. It only reports shadowing once `~/.local/bin` is itself on PATH, which on
|
||||
|
||||
Reference in New Issue
Block a user