Files
hack-house/hh/scripts/ensure-docker.sh
leetcrypt eec1714735 feat(layout): input-bar + chat/clergy resize, masked sudo-for-docker, edit-mode unlock
Make the message-input bar part of the F5 resize cycle so its height is
adjustable without a sandbox (multi-line/wrapped inputs are now readable),
and let chat/clergy borrow height from the compose box when no terminal is
present. Add Option C masked sudo prompt that feeds `sudo -S` over stdin to
install/start Docker — the password never reaches chat, the PTY, or outbound
frames, and Docker Desktop on Linux is detected so no sudo is requested.

Fix a freeze where clicking the compose box entered layout-edit mode and
silently swallowed every keystroke: clicking the input bar no longer enters
edit mode, and typing any printable char now drops out of edit mode and types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 16:10:27 -07:00

203 lines
8.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# ensure-docker.sh — make sure Docker is installed AND its daemon is up before
# /sbx launch docker.
#
# Two jobs, detect-first and never silent:
# 1. If the docker binary is missing, optionally INSTALL it (--install) from
# Docker's official, GPG-verified apt repo on Debian/Ubuntu (docker-ce),
# with dnf (Fedora/RHEL) and pacman (Arch) fallbacks. We deliberately do
# NOT pipe get.docker.com into a root shell.
# 2. If the daemon is down, start it (and wait until it accepts connections).
#
# Anything already in place is left untouched (idempotent).
#
# usage:
# ./ensure-docker.sh # interactive: prompt before starting the daemon
# ./ensure-docker.sh --yes # start (and install, if --install) without prompting
# ./ensure-docker.sh --install # install Docker if the binary is missing, then start
# ./ensure-docker.sh --check # test only; exit 0 if daemon up, 1 if not (no changes)
# ./ensure-docker.sh --plan # show the install plan; change nothing
set -uo pipefail
ASSUME_YES=0
CHECK_ONLY=0
DO_INSTALL=0
PLAN_ONLY=0
STDIN_PASS=0
for arg in "$@"; do
case "$arg" in
-y|--yes) ASSUME_YES=1 ;;
--check) CHECK_ONLY=1 ;;
--install) DO_INSTALL=1 ;;
--plan|--dry-run) PLAN_ONLY=1; DO_INSTALL=1 ;;
# A sudo password is waiting on stdin (the hack-house TUI feeds it). Use
# `sudo -S` so escalation reads that, never the controlling tty.
--stdin-pass) STDIN_PASS=1 ;;
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "✖ unknown arg: $arg" >&2; exit 2 ;;
esac
done
daemon_up() { docker info >/dev/null 2>&1; }
have_docker() { command -v docker >/dev/null 2>&1; }
# How to escalate. Three cases:
# * --stdin-pass: a password is waiting on stdin (the hack-house TUI prompted
# for it). Use `sudo -S -p ''` so sudo reads it from stdin with no prompt —
# never the controlling tty (a raw-mode TUI would corrupt a tty prompt). The
# first sudo caches the credential; the rest authenticate from cache.
# * --yes alone: non-interactive `sudo -n` — fails fast if creds aren't cached
# rather than hanging on a tty prompt the TUI can't host.
# * interactive shell: plain `sudo` (a real terminal can prompt normally).
SUDO="sudo"
[[ $ASSUME_YES -eq 1 ]] && SUDO="sudo -n"
[[ $STDIN_PASS -eq 1 ]] && SUDO="sudo -S -p ''"
# ── Work out how to install Docker on this platform ──────────────────────────
# Emits the ordered list of commands (one per line) to PLAN_LINES; empty if we
# don't know how. Uses Docker's official repo so packages are GPG-verified and
# current (distro packages like docker.io are often stale).
build_install_plan() {
PLAN_LINES=""
[[ "$(uname -s)" == "Linux" ]] || { PLAN_LINES=""; return; }
# shellcheck disable=SC1091
local id="" id_like=""
if [[ -r /etc/os-release ]]; then
id="$(. /etc/os-release; echo "${ID:-}")"
id_like="$(. /etc/os-release; echo "${ID_LIKE:-}")"
fi
case "$id $id_like" in
*debian*|*ubuntu*)
local repo="ubuntu"; [[ "$id" == "debian" ]] && repo="debian"
PLAN_LINES=$(cat <<EOF
$SUDO apt-get update
$SUDO apt-get install -y ca-certificates curl
$SUDO install -m 0755 -d /etc/apt/keyrings
$SUDO curl -fsSL https://download.docker.com/linux/$repo/gpg -o /etc/apt/keyrings/docker.asc
$SUDO chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$repo \$(. /etc/os-release && echo \${VERSION_CODENAME}) stable" | $SUDO tee /etc/apt/sources.list.d/docker.list >/dev/null
$SUDO apt-get update
$SUDO apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
EOF
)
;;
*fedora*|*rhel*|*centos*)
local repo="fedora"; case " $id $id_like " in *rhel*|*centos*) repo="centos";; esac
PLAN_LINES=$(cat <<EOF
$SUDO dnf -y install dnf-plugins-core
$SUDO dnf config-manager --add-repo https://download.docker.com/linux/$repo/docker-ce.repo
$SUDO dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
EOF
)
;;
*arch*)
PLAN_LINES="$SUDO pacman -S --noconfirm docker"
;;
esac
}
# ── --plan: show the install plan and change nothing ─────────────────────────
if [[ $PLAN_ONLY -eq 1 ]]; then
if have_docker; then
echo "Docker already installed ($(docker --version 2>/dev/null)) — nothing to install" >&2
exit 0
fi
build_install_plan
if [[ -z "$PLAN_LINES" ]]; then
echo "✖ don't know how to install Docker here — see https://docs.docker.com/engine/install/" >&2
exit 1
fi
echo "plan (no changes will be made) — would run:" >&2
printf ' %s\n' "$PLAN_LINES" >&2
exit 0
fi
if daemon_up; then
[[ $CHECK_ONLY -eq 1 ]] || echo "docker daemon already running" >&2
exit 0
fi
[[ $CHECK_ONLY -eq 1 ]] && exit 1
if ! have_docker; then
if [[ $DO_INSTALL -ne 1 ]]; then
echo "✖ docker is not installed — re-run with --install to install it" >&2
exit 127
fi
build_install_plan
if [[ -z "$PLAN_LINES" ]]; then
echo "✖ don't know how to install Docker here — see https://docs.docker.com/engine/install/" >&2
exit 1
fi
echo "Docker is not installed. The following will run (Docker's official repo):" >&2
printf ' %s\n' "$PLAN_LINES" >&2
if [[ $ASSUME_YES -ne 1 ]]; then
printf 'Proceed with install? [y/N] ' >&2
read -r reply
case "$reply" in
y|Y|yes|YES) ;;
*) echo "✖ aborted — Docker left uninstalled" >&2; exit 1 ;;
esac
fi
while IFS= read -r line; do
[[ -z "$line" ]] && continue
echo "+ $line" >&2
eval "$line" || { echo "✖ install step failed: $line" >&2; exit 1; }
done <<< "$PLAN_LINES"
have_docker || { echo "✖ install ran but docker is still not callable — check the install log" >&2; exit 1; }
echo "Docker installed ($(docker --version 2>/dev/null))" >&2
# On Linux a fresh install usually needs the daemon started below; fall through.
fi
# Work out how to start the daemon on this platform.
start_cmd=""
need_sudo=0
case "$(uname -s)" in
Linux)
# Docker Desktop on Linux runs the engine inside a per-user VM, started
# by the *user* unit `docker-desktop.service` — there is no root
# `docker.service`, and no sudo is needed. Detect it first so we don't
# `sudo systemctl start docker` and fail with "Unit docker.service could
# not be found" (which is exactly what the classic-engine path does here).
if command -v systemctl >/dev/null 2>&1 \
&& systemctl --user cat docker-desktop.service >/dev/null 2>&1; then
start_cmd="systemctl --user start docker-desktop"; need_sudo=0
elif command -v systemctl >/dev/null 2>&1; then
start_cmd="systemctl start docker"; need_sudo=1
elif command -v service >/dev/null 2>&1; then
start_cmd="service docker start"; need_sudo=1
fi
;;
Darwin)
# Docker Desktop: opening the app boots the daemon VM.
start_cmd="open -a Docker"
;;
esac
if [[ -z "$start_cmd" ]]; then
echo "✖ don't know how to start the docker daemon here — start it manually" >&2
exit 1
fi
[[ $need_sudo -eq 1 ]] && start_cmd="$SUDO $start_cmd"
# Confirmation (skipped with --yes).
if [[ $ASSUME_YES -ne 1 ]]; then
printf 'docker daemon is not running. Start it with "%s"? [y/N] ' "$start_cmd" >&2
read -r reply
case "$reply" in
y|Y|yes|YES) ;;
*) echo "✖ aborted — docker daemon left stopped" >&2; exit 1 ;;
esac
fi
echo "starting docker daemon: $start_cmd" >&2
eval "$start_cmd" || { echo "✖ failed to start docker daemon — needs sudo. Run 'sudo -v' in a terminal first (caches your password), then retry" >&2; exit 1; }
# Wait for it to accept connections (Desktop / a fresh VM can take a while).
for _ in $(seq 1 60); do
daemon_up && { echo "docker daemon is up" >&2; exit 0; }
sleep 1
done
echo "✖ docker daemon did not come up in time" >&2
exit 1