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>
118 lines
4.5 KiB
Bash
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
|
|
}
|