#!/usr/bin/env bash set -Eeuo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" INSTALL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" # shellcheck source=install/lib/common.sh source "${INSTALL_DIR}/lib/common.sh" # shellcheck source=install/lib/logging.sh source "${INSTALL_DIR}/lib/logging.sh" # shellcheck source=install/lib/packages.sh source "${INSTALL_DIR}/lib/packages.sh" atlas_set_script_name "30-docker.sh" atlas_install_error_trap DOCKER_KEYRING="/etc/apt/keyrings/docker.asc" DOCKER_SOURCE="/etc/apt/sources.list.d/docker.list" DOCKER_PACKAGES=(docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin) DOCKER_CONFLICTS=(docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc) DOCKER_REPO_BASE="https://download.docker.com/linux/ubuntu" DOCKER_USER="nick" usage() { cat <<'USAGE' Usage: install/phases/30-docker.sh [options] Install Docker Engine from Docker's official signed Ubuntu APT repository. Options: --help Show this help text. --version Show script/framework version. --dry-run Show intended actions without making changes. --verbose Print additional diagnostic output. USAGE } while [[ "$#" -gt 0 ]]; do case "$1" in --help) usage exit "${ATLAS_EXIT_OK}" ;; --version) atlas_print_version exit "${ATLAS_EXIT_OK}" ;; --dry-run) atlas_enable_dry_run ;; --verbose) atlas_enable_verbose ;; *) atlas_usage_error "Unknown option: $1" exit "${ATLAS_EXIT_USAGE}" ;; esac shift done load_os_release() { # shellcheck disable=SC1091 . /etc/os-release if [[ "${ID:-}" != "ubuntu" || "${VERSION_CODENAME:-}" != "resolute" ]]; then atlas_log_error "Unsupported OS for this Docker phase: ${PRETTY_NAME:-unknown}" exit "${ATLAS_EXIT_PREREQ}" fi } docker_repo_available() { curl -fsSL "${DOCKER_REPO_BASE}/dists/${VERSION_CODENAME}/Release" >/dev/null } install_prerequisites() { local packages=(ca-certificates curl) local missing=() local package_name for package_name in "${packages[@]}"; do atlas_package_installed "$package_name" || missing+=("$package_name") done if [[ "${#missing[@]}" -gt 0 ]]; then atlas_install_packages "${missing[@]}" else atlas_log_ok "Docker repository prerequisites are already installed" fi } remove_conflicts() { local installed=() local package_name for package_name in "${DOCKER_CONFLICTS[@]}"; do atlas_package_installed "$package_name" && installed+=("$package_name") done if [[ "${#installed[@]}" -eq 0 ]]; then atlas_log_ok "No conflicting Docker packages are installed" return 0 fi if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then atlas_log_info "Dry-run mode: would remove conflicting packages: ${installed[*]}" return 0 fi atlas_log_info "Removing conflicting Docker packages: ${installed[*]}" atlas_sudo_run apt-get remove -y "${installed[@]}" } configure_repository() { local arch repo_line arch="$(dpkg --print-architecture)" repo_line="deb [arch=${arch} signed-by=${DOCKER_KEYRING}] ${DOCKER_REPO_BASE} ${VERSION_CODENAME} stable" if [[ -r "$DOCKER_SOURCE" ]] && grep -Fqx "$repo_line" "$DOCKER_SOURCE" && [[ -r "$DOCKER_KEYRING" ]]; then atlas_log_ok "Docker APT repository is already configured" return 0 fi if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then atlas_log_info "Dry-run mode: would install Docker GPG key to ${DOCKER_KEYRING}" atlas_log_info "Dry-run mode: would write Docker APT source ${DOCKER_SOURCE}" atlas_log_info "Dry-run mode: would run apt-get update" return 0 fi atlas_sudo_run install -m 0755 -d /etc/apt/keyrings tmp_key="$(mktemp)" curl -fsSL "${DOCKER_REPO_BASE}/gpg" -o "$tmp_key" atlas_sudo_run install -m 0644 "$tmp_key" "$DOCKER_KEYRING" rm -f "$tmp_key" printf '%s\n' "$repo_line" | sudo -n tee "$DOCKER_SOURCE" >/dev/null atlas_sudo_run apt-get update } install_docker_packages() { local missing=() local package_name for package_name in "${DOCKER_PACKAGES[@]}"; do atlas_package_installed "$package_name" || missing+=("$package_name") done if [[ "${#missing[@]}" -eq 0 ]]; then atlas_log_ok "Docker packages are already installed" else atlas_install_packages "${missing[@]}" fi } enable_docker_service() { atlas_log_info "Ensuring Docker service is enabled and active" atlas_sudo_run systemctl enable docker atlas_sudo_run systemctl start docker } ensure_docker_group() { if id -nG "$DOCKER_USER" | tr ' ' '\n' | grep -qx docker; then atlas_log_ok "User ${DOCKER_USER} is already in the docker group" return 0 fi if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then atlas_log_info "Dry-run mode: would add ${DOCKER_USER} to docker group" return 0 fi atlas_sudo_run usermod -aG docker "$DOCKER_USER" atlas_log_warn "User ${DOCKER_USER} was added to docker group; a new login may be required" } verify_docker() { if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then atlas_log_info "Dry-run mode: skipping Docker runtime verification" return 0 fi docker --version docker compose version docker buildx version systemctl is-enabled docker >/dev/null systemctl is-active docker >/dev/null if docker info >/dev/null 2>&1; then atlas_log_ok "Docker is available to the current shell without sudo" else atlas_log_warn "Current shell cannot use Docker without sudo; a new login or sg docker may be required" sudo -n docker info >/dev/null fi atlas_log_ok "Docker verification passed" } main() { load_os_release atlas_require_sudo_noninteractive || { atlas_log_error "Passwordless non-interactive sudo is required" exit "${ATLAS_EXIT_PREREQ}" } atlas_require_command curl atlas_require_command dpkg atlas_require_command apt-get atlas_require_command systemctl docker_repo_available || { atlas_log_error "Docker official repository is not available for ${VERSION_CODENAME}" exit "${ATLAS_EXIT_PREREQ}" } install_prerequisites remove_conflicts configure_repository install_docker_packages enable_docker_service ensure_docker_group verify_docker } main