#!/usr/bin/env zsh # Docker prerequisites for planet.sh; lifecycle and logging remain in planet.sh. DOCKER_MIN_BUILDX_VERSION="0.17.0" compose_available() { docker compose version >/dev/null 2>&1 } compose_v1_available() { command -v docker-compose >/dev/null 2>&1 } buildx_version() { docker buildx version 2>/dev/null | awk '{print $2}' | sed 's/^v//' } buildx_meets_minimum() { local current_version="$1" local required_version="$2" [ -n "$current_version" ] || return 1 [ -n "$required_version" ] || return 1 if [ "$(printf '%s\n%s\n' "$required_version" "$current_version" | sort -V | head -n 1)" = "$required_version" ]; then return 0 fi return 1 } docker_daemon_available() { command -v docker >/dev/null 2>&1 && command docker info >/dev/null 2>&1 } docker_uses_local_engine() { local endpoint="${DOCKER_HOST:-}" if [ -n "${DOCKER_CONTEXT:-}" ] || [ -z "$endpoint" ]; then if command -v docker >/dev/null 2>&1; then endpoint="$(command docker context inspect --format '{{.Endpoints.docker.Host}}' 2>/dev/null)" || return 1 elif [ -n "${DOCKER_CONTEXT:-}" ]; then return 1 else endpoint="unix:///var/run/docker.sock" fi fi case "$endpoint" in unix:///var/run/docker.sock|unix:///run/docker.sock) return 0 ;; *) return 1 ;; esac } docker_desktop_present() { local docker_path="" [ -d /mnt/wsl/docker-desktop ] && return 0 docker_path="$(command -v docker 2>/dev/null || true)" [ -n "$docker_path" ] || return 1 docker_path="$(readlink -f "$docker_path" 2>/dev/null || true)" [[ "$docker_path" == */docker-desktop/* ]] } docker_systemd_unit_exists() { command -v systemctl >/dev/null 2>&1 || return 1 [ "$(systemctl show --property=LoadState --value "$1" 2>/dev/null)" = "loaded" ] } docker_socket_needs_group_access() { [ -S /var/run/docker.sock ] && { [ ! -r /var/run/docker.sock ] || [ ! -w /var/run/docker.sock ]; } } docker_require_sudo() { [ "$(id -u)" -eq 0 ] && return 0 if ! command -v sudo >/dev/null 2>&1; then log_error "缺少 sudo,无法安装或配置 Docker;请先由管理员安装 sudo 并授予当前用户权限。" return 1 fi # Authenticate in the foreground, before command output is redirected to a log. sudo -v } docker_as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" else sudo -- "$@" fi } refresh_docker_group() { [ "$(id -u)" -ne 0 ] || return 0 command -v docker >/dev/null 2>&1 || return 0 docker_uses_local_engine || return 0 docker_socket_needs_group_access || return 0 local username="$(id -un)" local groups="$(id -nG "$username")" [[ " $groups " == *" docker "* ]] || return 0 if [ "${PLANET_DOCKER_GROUP_REEXEC:-0}" = "1" ]; then log_error "刷新组权限后仍无法访问 Docker socket,请检查 socket 的属组和权限。" return 1 fi docker_require_sudo || return 1 log_note "刷新 Docker 组权限,以当前用户继续执行。" # Preserve WSL interop/tool paths even when sudo applies secure_path. # argv stays an array: repository paths and arguments are never evaluated as shell text. exec sudo -E -u "$username" -g docker -- env PLANET_DOCKER_GROUP_REEXEC=1 "PATH=$PATH" \ "$(command -v zsh)" "$SCRIPT_DIR/planet.sh" "$@" } docker_bootstrap_os_id() { [ -r /etc/os-release ] || return 1 (. /etc/os-release; printf '%s\n' "$ID") } install_docker_prerequisites() { local log_file="$PLANET_STATE_DIR/docker_install.log" local engine_package="docker.io" local compose_package="docker-compose-v2" local buildx_package="docker-buildx" local -a packages=() if [ "$(docker_bootstrap_os_id)" != "ubuntu" ] || ! command -v apt-get >/dev/null 2>&1; then log_error "Docker 自动安装目前支持 Ubuntu / Ubuntu WSL;请先安装本系统的 Docker Engine、Compose v2 和 Buildx。" return 1 fi if command -v dpkg-query >/dev/null 2>&1 && [ "$(dpkg-query -W -f='${Status}' docker-ce-cli 2>/dev/null)" = "install ok installed" ]; then engine_package="docker-ce" compose_package="docker-compose-plugin" buildx_package="docker-buildx-plugin" fi if ! command -v docker >/dev/null 2>&1 || { ! docker_daemon_available && ! command -v dockerd >/dev/null 2>&1; }; then packages+=("$engine_package") fi compose_available || packages+=("$compose_package") buildx_meets_minimum "$(buildx_version)" "$DOCKER_MIN_BUILDX_VERSION" || packages+=("$buildx_package") [ "${#packages[@]}" -gt 0 ] || return 0 docker_require_sudo || return 1 log_step "安装 Docker 依赖: ${packages[*]}" if ! run_command_quiet_unless_verbose "$log_file" docker_as_root apt-get update --error-on=any || ! run_command_quiet_unless_verbose "$log_file" docker_as_root apt-get install -y "${packages[@]}"; then log_error "Docker 依赖安装失败,请检查软件源、网络和 apt 错误。" tail -20 "$log_file" 2>/dev/null || true return 1 fi rehash } start_local_docker_engine() { local log_file="$PLANET_STATE_DIR/docker_service.log" if ! docker_systemd_unit_exists docker.service; then log_error "没有可用的 docker.service;请确认 Docker Engine 已安装且 systemd 正常运行。" log_note "Ubuntu WSL 可在 /etc/wsl.conf 的 [boot] 下启用 systemd=true,再重启 WSL。" return 1 fi docker_require_sudo || return 1 if ! run_command_quiet_unless_verbose "$log_file" docker_as_root systemctl enable --now docker.service; then log_error "Docker Engine 启动失败。" tail -20 "$log_file" 2>/dev/null || true log_note "查看原因: journalctl -u docker.service -n 50 --no-pager" return 1 fi } ensure_docker_user_access() { [ "$(id -u)" -ne 0 ] || return 0 docker_socket_needs_group_access || return 0 docker_require_sudo || return 1 ensure_system_command usermod passwd "usermod" || return 1 log_note "将当前用户加入 docker 组(该组可管理本机 Docker)。" docker_as_root usermod -aG docker "$(id -un)" || return 1 refresh_docker_group "$@" } ensure_docker_runtime() { if docker_daemon_available && compose_available && buildx_meets_minimum "$(buildx_version)" "$DOCKER_MIN_BUILDX_VERSION"; then return 0 fi if docker_desktop_present; then log_error "检测到 Docker Desktop,但当前 WSL 的 Docker / Compose / Buildx 尚不可用。" log_note "请启动 Docker Desktop,并为当前发行版启用 WSL Integration。" return 1 fi if ! docker_uses_local_engine; then log_error "当前 Docker 使用其他 context 或远程/rootless endpoint,请先检查该环境的连接和插件。" return 1 fi install_docker_prerequisites || return 1 if ! command -v docker >/dev/null 2>&1 || ! compose_available || ! buildx_meets_minimum "$(buildx_version)" "$DOCKER_MIN_BUILDX_VERSION"; then log_error "安装后 Docker CLI、Compose v2 或 Buildx >= ${DOCKER_MIN_BUILDX_VERSION} 仍不可用。" return 1 fi if ! docker_daemon_available; then start_local_docker_engine || return 1 ensure_docker_user_access "$@" || return 1 fi if ! docker_daemon_available; then log_error "Docker 已安装,但当前用户仍无法连接 daemon。" log_docker_daemon_diagnostics return 1 fi } log_docker_daemon_diagnostics() { if ! command -v docker >/dev/null 2>&1; then log_note "未找到 Docker CLI,尚未安装 Docker;Ubuntu / WSL 请先执行 ./planet.sh init。" return 0 fi if docker_desktop_present; then log_note "请启动 Docker Desktop,并确认当前 WSL 已启用 WSL Integration。" return 0 fi if ! docker_uses_local_engine; then log_note "当前使用其他 Docker endpoint;请检查 docker context ls 和 docker info。" return 0 fi if docker_socket_needs_group_access; then log_note "Docker socket 存在,但当前用户没有读写权限;请执行 ./planet.sh init 配置 docker 组。" return 0 fi if ! docker_systemd_unit_exists docker.service; then log_note "未检测到可用的 docker.service;Ubuntu / WSL 请执行 ./planet.sh init 检查安装。" return 0 fi if [ "$(systemctl is-active docker.service 2>/dev/null)" = "active" ]; then log_note "Docker 服务已启动,但 CLI 无法连接;请检查 docker context ls 和 docker info。" return 0 fi if docker_systemd_unit_exists docker.socket; then log_note "可先执行: sudo systemctl enable --now docker.socket && sudo systemctl restart docker.service" else log_note "可先执行: sudo systemctl enable --now docker.service" fi log_note "如仍失败,查看: journalctl -u docker.service -n 50 --no-pager" }