#!/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 # ── The rootless prerequisites go in HERE, not in the rootless branch ── # # They used to be installed only when the owner picked "[2] rootless Docker for # me" in section 22. But the OWNER's choice is not the only one that matters: # every Developer account the platform provisions gets its own rootless daemon, # whatever the owner picked for themselves. So on a machine where the owner chose # the docker group, the host never got these and every member's daemon failed # with `rootless Docker needs these packages on the host: uidmap`. # # `src/servers/os-user-docker.ts` → checkDockerPrerequisites is the authority on # this list, and it wants both: # # uidmap /usr/bin/newuidmap, /usr/bin/newgidmap # docker-ce-rootless-extras /usr/bin/dockerd-rootless-setuptool.sh # # docker-ce only RECOMMENDS rootless-extras. That is installed by default, so it # is usually there by luck — and is not on a host configured with # --no-install-recommends. Named explicitly so it does not depend on that. # # dbus-user-session is what lets a member's systemd --user survive without a # login session, which is how the daemon stays up. pkg_install_now docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \ docker-ce-rootless-extras uidmap dbus-user-session } # 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 }