diff --git a/README.md b/README.md index 23df411..9e9b5c6 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,11 @@ Already have a clone? Just run it directly: - `git/` — sets git `user.name`/`user.email` and configures `credential.helper store` (plaintext `~/.git-credentials`, 0600) for HTTPS auth. -- `docker/` — installs full (rootful) Docker Engine via get.docker.com, - enables the service (systemd hosts), and adds the invoking user to the +- `docker/` — installs full (rootful) Docker Engine from Docker's upstream + package repo (get.docker.com on Debian-family; the docker-ce repo wired up + directly on RHEL-family, since the convenience script rejects EL rebuilds + like Oracle Linux by name), enables the service (systemd hosts), and adds + the invoking user to the `docker` group (takes effect at next login). No-ops where a rootless Docker daemon is already running (e.g. Kasm) so it never fights that setup. Uses sudo. diff --git a/bootstrap.sh b/bootstrap.sh index cc4f207..154a51e 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -43,15 +43,38 @@ if ! command -v python3 >/dev/null 2>&1 && ! command -v unzip >/dev/null 2>&1; t pkg_install python3 || echo "bootstrap: warning: could not install python3" >&2 fi -"$SCRIPT_DIR/zsh/install.sh" -"$SCRIPT_DIR/git/install.sh" +# run_module — run one module; on failure, warn and keep going. +# +# WHY: these used to be bare calls, so under `set -eu` the FIRST failing module +# aborted bootstrap and every later module silently never ran — one unsupported +# distro in docker/ was enough to leave a box with no CLIs, no npx and no +# ansible. The modules are independent, so a failure in one is not a reason to +# skip the rest. (bin/install.sh does the same for individual tools; this is +# that guard one level up.) +FAILED="" +run_module() { + if ( set -e; "$SCRIPT_DIR/$1/install.sh" ); then return 0; fi + FAILED="$FAILED $1" + echo "bootstrap: WARNING: $1 module failed; continuing." >&2 +} + +run_module zsh +run_module git # Runs before bin/ so the kind install gate can see the rootful daemon. -"$SCRIPT_DIR/docker/install.sh" -"$SCRIPT_DIR/bin/install.sh" +run_module docker +run_module bin # Provides npx, which opencode needs to spawn the npm-based MCP servers in # mcp-config/. Without it every MCP server silently fails to start. -"$SCRIPT_DIR/node/install.sh" -"$SCRIPT_DIR/ansible/install.sh" +run_module node +run_module ansible # Runs last: needs rootless Docker already up (started at boot) so it can # delegate cgroups to it. No-ops where that does not apply. -"$SCRIPT_DIR/docker-rootless-cgroup/install.sh" +run_module docker-rootless-cgroup + +if [ -n "$FAILED" ]; then + echo "" >&2 + echo "bootstrap: WARNING: these modules did NOT complete:$FAILED" >&2 + echo "bootstrap: everything else was applied. Fix the cause and re-run." >&2 + exit 1 +fi +echo "bootstrap: all modules applied." >&2 diff --git a/docker/install.sh b/docker/install.sh index c2bcc5f..461df0b 100755 --- a/docker/install.sh +++ b/docker/install.sh @@ -3,10 +3,10 @@ # invoking user talk to it without sudo. # # What it does: -# 1. Installs Docker Engine via Docker's official convenience script -# (get.docker.com — supports both RHEL- and Debian-family distros and -# sets up the upstream package repo). Skipped when `docker` is already -# installed. +# 1. Installs Docker Engine from Docker's upstream package repo. Skipped when +# `docker` is already installed. Debian-family hosts go through Docker's +# convenience script (get.docker.com); RHEL-family hosts wire up the +# docker-ce repo directly — see install_rhel() for why. # 2. Enables and starts the service (systemd hosts only). # 3. Adds the invoking user to the `docker` group so the daemon socket is # usable without sudo. Takes effect at next login. @@ -18,8 +18,66 @@ # Idempotent and safe to re-run. Uses sudo. set -eu +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +REPO_ROOT="$(dirname -- "$SCRIPT_DIR")" + +# shellcheck source=../lib/os-detect.sh +. "$REPO_ROOT/lib/os-detect.sh" + say() { printf '%s\n' "$*" >&2; } +DOCKER_PKGS="docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin" + +# install_rhel — install docker-ce from Docker's EL repo. +# +# WHY NOT get.docker.com: the convenience script allowlists distros by +# /etc/os-release ID and aborts on anything it does not recognize — +# "ERROR: Unsupported distribution 'ol'" on Oracle Linux, and the same for +# other EL rebuilds it has not enumerated. On a match it does nothing more +# than what this function does: add the repo, then install the packages. +# Wiring the repo up directly drops the allowlist, so every EL rebuild +# (Oracle, Rocky, Alma, CentOS, RHEL) installs from one code path. +# +# The repo URL is keyed on $releasever/$basearch, which dnf expands per host, +# so EL9 and EL10 both resolve without a version table here. +install_rhel() { + say "docker: adding Docker CE repo (EL \$releasever/\$basearch)..." + sudo tee /etc/yum.repos.d/docker-ce.repo >/dev/null <<'REPO' +[docker-ce-stable] +name=Docker CE Stable - $basearch +baseurl=https://download.docker.com/linux/centos/$releasever/$basearch/stable +enabled=1 +gpgcheck=1 +gpgkey=https://download.docker.com/linux/centos/gpg +REPO + # --allowerasing: containerd.io conflicts with the distro's own runc/containerd + # on EL rebuilds that ship them (Oracle Linux does). Let dnf swap them out + # rather than dead-end on the conflict. + if command -v dnf >/dev/null 2>&1; then + # shellcheck disable=SC2086 + sudo dnf install -y --allowerasing $DOCKER_PKGS + else + # shellcheck disable=SC2086 + sudo yum install -y --allowerasing $DOCKER_PKGS + fi +} + +# install_debian — Docker's convenience script; it supports every Debian-family +# distro this repo targets and sets up the apt repo and keyring. +install_debian() { + say "docker: installing Docker Engine via get.docker.com..." + tmp="$(mktemp)" + if command -v curl >/dev/null 2>&1; then + curl -fsSL https://get.docker.com -o "$tmp" + elif command -v wget >/dev/null 2>&1; then + wget -qO "$tmp" https://get.docker.com + else + say "docker: need curl or wget"; rm -f "$tmp"; return 1 + fi + sudo sh "$tmp" + rm -f "$tmp" +} + # --- skip on rootless-Docker hosts ------------------------------------------- if command -v docker >/dev/null 2>&1 && docker info -f '{{println .SecurityOptions}}' 2>/dev/null | grep -q 'name=rootless'; then @@ -31,17 +89,11 @@ fi if command -v docker >/dev/null 2>&1; then say "docker: engine already installed ($(docker --version 2>/dev/null || echo 'version unknown'))." else - say "docker: installing Docker Engine via get.docker.com..." - tmp="$(mktemp)" - if command -v curl >/dev/null 2>&1; then - curl -fsSL https://get.docker.com -o "$tmp" - elif command -v wget >/dev/null 2>&1; then - wget -qO "$tmp" https://get.docker.com - else - say "docker: need curl or wget"; exit 1 - fi - sudo sh "$tmp" - rm -f "$tmp" + case "$(detect_pkg_family)" in + rhel) install_rhel ;; + debian) install_debian ;; + *) say "docker: unsupported/undetected OS family; install Docker manually."; exit 1 ;; + esac fi # --- 2. enable + start the service (systemd only) ----------------------------