Files
platform/scripts/setup/machine-setup/lib/docker.sh
T
pastilhasandClaude Opus 5 b075f1f882 macOS is a dev machine, and machine-setup now treats it as one
Officer on a Mac is a dev helper on a laptop somebody sits at. It is never the
homelab or VPS case, so the role is not asked for there — it is `dev`, and every
section that exists to make a machine a good server is skipped.

Seventeen of twenty-six sections skip, listed once in MACOS_SKIP in lib/base.sh
with a reason each, rather than an `if macos` threaded through each section. Most
would simply fail — no systemd, no ufw, no netplan, no useradd, no
/etc/ssh/sshd_config.d — but a few would SUCCEED and be wrong, which is worse:
stopping a laptop from sleeping, or freezing the address of a machine that moves
between networks daily.

Nine run: System update, Core utils, Tailscale, Command-line tools, Git, Docker,
Neovim, JavaScript runtimes, Agent CLIs.

The blocker was root. Linux needs it for nearly everything; Homebrew REFUSES to
run as root and says so, so the whole script under sudo would have failed at the
first brew install having already taken a password. It is now required on Linux
and refused on macOS, which works precisely because the macOS path skips
everything that needed it.

Docker is checked, not installed. Docker Desktop is a GUI app that wants opening,
permissions and a running window — not a shell script's business — and colima and
lima both cost an evening the first time something does not resolve. So the step
reports whether the daemon answers and points at the download otherwise. The
group-vs-rootless choice below it is Linux only: Desktop runs containers in a VM
owned by whoever is logged in, so there is no group to join.

Added the Xcode command line tools as a macOS-only step, before anything that
builds. node-pty ships no prebuilt binary on any platform and always falls
through to node-gyp, so `bun install` cannot finish without a compiler — and it
fails deep in a dependency tree naming neither Xcode nor node-pty. `xcode-select
--install` opens a dialogue and returns immediately, so the step says to come
back rather than pretending to have waited.

Tailscale takes the cask, not install.sh — that script is a Linux package-manager
wrapper. The cask ships a usable CLI; the Mac App Store build is sandboxed and
does not.

Not run on a Mac. There isn't one here, so this is read from the code and from
what each tool documents, not observed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 02:29:56 +00:00

118 lines
4.5 KiB
Bash

#!/bin/bash
# =============================================================================
# machine-setup — Docker
# =============================================================================
#
# Definitions only, like the other lib/ files.
[[ -n "${MACHINE_SETUP_DOCKER_LOADED:-}" ]] && return 0
MACHINE_SETUP_DOCKER_LOADED=1
DOCKER_NETWORK="${SETUP_DOCKER_NETWORK:-services}"
docker_is_installed() { command -v docker &>/dev/null; }
# The daemon, not just the binary. `docker --version` answers from the client
# alone and says nothing about whether there is anything to talk to.
docker_daemon_ok() { docker info &>/dev/null; }
user_in_docker_group() { id -nG "$USERNAME" 2>/dev/null | tr ' ' '\n' | grep -qx docker; }
docker_rootless_installed() { [[ -S "/run/user/$(id -u "$USERNAME" 2>/dev/null)/docker.sock" ]]; }
# The codename Docker's repository is actually published under.
#
# `lsb_release -cs` is what the original used, and it is wrong on every
# derivative: Mint reports "vanessa", Pop reports its own, and Docker publishes
# neither — so `apt update` fails on a repository that does not exist. os-release
# carries UBUNTU_CODENAME on exactly those systems for exactly this reason, so it
# is preferred and VERSION_CODENAME is the fallback.
docker_repo_codename() {
local c
c="$(os_release UBUNTU_CODENAME || true)"
[[ -z "$c" ]] && c="$(os_release VERSION_CODENAME || true)"
echo "$c"
}
# Which upstream to point at. A derivative is Ubuntu or Debian as far as Docker
# is concerned, and ID_LIKE is how it says which.
docker_repo_distro() {
case "$OS" in
ubuntu | debian) echo "$OS" ;;
*)
case " $(os_release ID_LIKE || true) " in
*" ubuntu "*) echo ubuntu ;;
*) echo debian ;;
esac
;;
esac
}
install_docker_engine() {
# Linux only, and never reached on macOS: the Docker step there checks for
# Docker Desktop and tells the owner to install it rather than doing it — a GUI
# app that wants opening, permissions and a running window is not a shell
# script's job, and colima/lima are not worth the evening they cost.
local distro codename
distro="$(docker_repo_distro)"
codename="$(docker_repo_codename)"
[[ -n "$codename" ]] || {
warn "could not work out this release's codename — cannot add the Docker repository"
return 1
}
install -m 0755 -d /etc/apt/keyrings
curl -fsSL "https://download.docker.com/linux/${distro}/gpg" |
gpg --batch --yes --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${distro} ${codename} stable" \
>/etc/apt/sources.list.d/docker.list
pkg_refresh >/dev/null
pkg_install_now docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
}
# A shared network so containers from different compose files can reach each
# other by name. Harmless if it is already there.
ensure_docker_network() {
docker network inspect "$DOCKER_NETWORK" &>/dev/null && return 0
docker network create "$DOCKER_NETWORK" >/dev/null 2>&1
}
# ── Rootless, for the owner ──
#
# Works, and does not work with Officer's app store as it stands. Both are true
# and the second is the one nobody would find out until a container failed to
# provision, so it is stated at the prompt rather than left here.
#
# The app store spawns `docker` with no environment of its own —
# app-store/compose.ts, app-store/preflight.ts, api/system-monitor — so it talks
# to whatever socket the `officer` pm2 process's environment points at. That is
# /var/run/docker.sock unless DOCKER_HOST says otherwise, and nothing sets
# DOCKER_HOST for the owner: os-user-docker.ts sets it only for member commands.
#
# pm2 started at boot by systemd has no session either, so exporting it in a
# shell rc does not reach the process that matters.
install_docker_rootless() {
local uid
uid="$(id -u "$USERNAME")"
# Without lingering, the user manager stops when the last session ends and
# takes the daemon with it. Officer's shells are not login sessions.
loginctl enable-linger "$USERNAME" >/dev/null 2>&1
sudo -u "$USERNAME" \
XDG_RUNTIME_DIR="/run/user/${uid}" \
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${uid}/bus" \
PATH="/usr/bin:/usr/sbin:/bin:/sbin" \
dockerd-rootless-setuptool.sh install >/dev/null 2>&1 || return 1
sudo -u "$USERNAME" \
XDG_RUNTIME_DIR="/run/user/${uid}" \
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${uid}/bus" \
systemctl --user enable --now docker >/dev/null 2>&1
}