#!/usr/bin/env sh # shellcheck disable=SC2034 pi_web_docker_host_yaml_quote() { value=$1 escaped=$(printf '%s' "$value" | sed "s/'/''/g") printf "'%s'" "$escaped" } pi_web_docker_host_socket_path_from_endpoint() { endpoint=$1 case "$endpoint" in unix://*) printf '%s\n' "${endpoint#unix://}" ;; *) return 1 ;; esac } pi_web_docker_host_mac_desktop_socket_path() { [ -n "${HOME:-}" ] || return 1 printf '%s/.docker/run/docker.sock\n' "$HOME" } pi_web_docker_host_endpoint_is_linux_expected() { endpoint=$1 [ "$endpoint" = unix:///var/run/docker.sock ] } pi_web_docker_host_endpoint_is_mac_expected() { endpoint=$1 if ! socket_path=$(pi_web_docker_host_socket_path_from_endpoint "$endpoint" 2>/dev/null); then return 1 fi case "$socket_path" in /var/run/docker.sock) return 0 ;; esac if mac_socket_path=$(pi_web_docker_host_mac_desktop_socket_path 2>/dev/null); then [ "$socket_path" = "$mac_socket_path" ] && return 0 fi return 1 } pi_web_docker_host_socket_source_for_endpoint() { endpoint=$1 pi_web_docker_host_socket_path_from_endpoint "$endpoint" } pi_web_docker_host_detect_docker_gid() { case "${PI_WEB_DETECTED_DOCKER_HOST_PROFILE:-}" in mac-docker-desktop) printf '0\n' return 0 ;; esac socket_path=/var/run/docker.sock if [ -n "${PI_WEB_DETECTED_DOCKER_ENDPOINT:-}" ]; then if detected_socket_path=$(pi_web_docker_host_socket_path_from_endpoint "$PI_WEB_DETECTED_DOCKER_ENDPOINT" 2>/dev/null); then socket_path=$detected_socket_path fi fi if [ -S "$socket_path" ]; then if gid=$(stat -c '%g' "$socket_path" 2>/dev/null); then printf '%s\n' "$gid" return 0 fi if gid=$(stat -f '%g' "$socket_path" 2>/dev/null); then printf '%s\n' "$gid" return 0 fi fi if [ -S /var/run/docker.sock ]; then if gid=$(stat -c '%g' /var/run/docker.sock 2>/dev/null); then printf '%s\n' "$gid" return 0 fi if gid=$(stat -f '%g' /var/run/docker.sock 2>/dev/null); then printf '%s\n' "$gid" return 0 fi fi if command -v getent >/dev/null 2>&1; then if gid=$(getent group docker | awk -F: 'NR == 1 { print $3 }'); then if [ -n "$gid" ]; then printf '%s\n' "$gid" return 0 fi fi fi printf '0\n' } pi_web_docker_host_detect_profile() { PI_WEB_DETECTED_HOST_OS=$(uname -s 2>/dev/null || printf 'unknown') PI_WEB_DETECTED_DOCKER_CONTEXT= PI_WEB_DETECTED_DOCKER_ENDPOINT= PI_WEB_DETECTED_DOCKER_HOST_ENV=${DOCKER_HOST:-} PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT= PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE= PI_WEB_DETECTED_DOCKER_OS= PI_WEB_DETECTED_DOCKER_HOST_PROFILE= PI_WEB_DETECTED_HOSTEXEC_MODE=disabled PI_WEB_DOCKER_HOST_PROFILE_ERROR= if ! command -v docker >/dev/null 2>&1; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="docker CLI is required" return 1 fi PI_WEB_DETECTED_DOCKER_CONTEXT=$(docker context show 2>/dev/null || printf 'unknown') if [ -n "$PI_WEB_DETECTED_DOCKER_CONTEXT" ] && [ "$PI_WEB_DETECTED_DOCKER_CONTEXT" != unknown ]; then PI_WEB_DETECTED_DOCKER_ENDPOINT=$(docker context inspect "$PI_WEB_DETECTED_DOCKER_CONTEXT" --format '{{if .Endpoints.docker}}{{.Endpoints.docker.Host}}{{end}}' 2>/dev/null || printf '') fi case "$PI_WEB_DETECTED_HOST_OS" in Linux) if [ -n "$PI_WEB_DETECTED_DOCKER_HOST_ENV" ] && ! pi_web_docker_host_endpoint_is_linux_expected "$PI_WEB_DETECTED_DOCKER_HOST_ENV"; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="native Linux installs require DOCKER_HOST to be unset or exactly unix:///var/run/docker.sock, not $PI_WEB_DETECTED_DOCKER_HOST_ENV" return 1 fi if [ -n "$PI_WEB_DETECTED_DOCKER_ENDPOINT" ] && ! pi_web_docker_host_endpoint_is_linux_expected "$PI_WEB_DETECTED_DOCKER_ENDPOINT"; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="native Linux installs require the local /var/run/docker.sock Docker context, not $PI_WEB_DETECTED_DOCKER_ENDPOINT" return 1 fi PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT=${PI_WEB_DETECTED_DOCKER_HOST_ENV:-$PI_WEB_DETECTED_DOCKER_ENDPOINT} PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE=/var/run/docker.sock if [ ! -S "$PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE" ]; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="native Linux installs require a local Docker socket at /var/run/docker.sock" return 1 fi ;; Darwin) if [ -n "$PI_WEB_DETECTED_DOCKER_ENDPOINT" ] && ! pi_web_docker_host_endpoint_is_mac_expected "$PI_WEB_DETECTED_DOCKER_ENDPOINT"; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="macOS installs require a Docker Desktop local Unix socket context, not $PI_WEB_DETECTED_DOCKER_ENDPOINT" return 1 fi if [ -n "$PI_WEB_DETECTED_DOCKER_HOST_ENV" ]; then if ! pi_web_docker_host_endpoint_is_mac_expected "$PI_WEB_DETECTED_DOCKER_HOST_ENV"; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="macOS installs require DOCKER_HOST to be unset or a Docker Desktop local Unix socket, not $PI_WEB_DETECTED_DOCKER_HOST_ENV" return 1 fi PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT=$PI_WEB_DETECTED_DOCKER_HOST_ENV else PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT=$PI_WEB_DETECTED_DOCKER_ENDPOINT fi if [ -n "$PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT" ]; then if ! pi_web_docker_host_endpoint_is_mac_expected "$PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT"; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="macOS installs require a Docker Desktop local Unix socket, not ${PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT:-unknown}" return 1 fi PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE=$(pi_web_docker_host_socket_source_for_endpoint "$PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT") || return 1 elif mac_socket_path=$(pi_web_docker_host_mac_desktop_socket_path 2>/dev/null) && [ -S "$mac_socket_path" ]; then PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE=$mac_socket_path else PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE=/var/run/docker.sock fi if [ ! -S "$PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE" ]; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="Docker Desktop socket is not accessible at $PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE" return 1 fi ;; *) PI_WEB_DOCKER_HOST_PROFILE_ERROR="unsupported host OS: $PI_WEB_DETECTED_HOST_OS" return 1 ;; esac if ! docker info >/dev/null 2>&1; then PI_WEB_DOCKER_HOST_PROFILE_ERROR="docker daemon is not reachable by this user" return 1 fi PI_WEB_DETECTED_DOCKER_OS=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null || printf '') case "$PI_WEB_DETECTED_HOST_OS" in Linux) case "$PI_WEB_DETECTED_DOCKER_CONTEXT:$PI_WEB_DETECTED_DOCKER_OS" in *desktop-linux*|*"Docker Desktop"*) PI_WEB_DOCKER_HOST_PROFILE_ERROR="Docker Desktop on Linux is not supported by this installer because it runs containers inside a VM instead of the native Linux host" return 1 ;; esac PI_WEB_DETECTED_DOCKER_HOST_PROFILE=linux-native-docker PI_WEB_DETECTED_HOSTEXEC_MODE=nsenter ;; Darwin) case "$PI_WEB_DETECTED_DOCKER_CONTEXT:$PI_WEB_DETECTED_DOCKER_OS:$PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT" in *desktop-linux*|*"Docker Desktop"*|*"/.docker/run/docker.sock"*) PI_WEB_DETECTED_DOCKER_HOST_PROFILE=mac-docker-desktop PI_WEB_DETECTED_HOSTEXEC_MODE=disabled ;; *) PI_WEB_DOCKER_HOST_PROFILE_ERROR="macOS installs currently require Docker Desktop; detected context '$PI_WEB_DETECTED_DOCKER_CONTEXT' endpoint '${PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT:-unknown}'" return 1 ;; esac ;; esac return 0 } pi_web_docker_host_write_volume() { source_path=$1 target_path=$2 read_only=${3:-false} { printf ' - type: bind\n' printf ' source: %s\n' "$(pi_web_docker_host_yaml_quote "$source_path")" printf ' target: %s\n' "$(pi_web_docker_host_yaml_quote "$target_path")" if [ "$read_only" = true ]; then printf ' read_only: true\n' fi } >>"$PI_WEB_DOCKER_HOST_OVERRIDE_TEMP" } pi_web_docker_host_write_existing_volume() { source_path=$1 target_path=$2 read_only=${3:-false} if [ -e "$source_path" ]; then pi_web_docker_host_write_volume "$source_path" "$target_path" "$read_only" fi } pi_web_docker_host_write_extra_volumes() { extra_paths=$1 for extra_path in $extra_paths; do case "$extra_path" in /*) ;; *) printf '%s\n' "PI_WEB_DOCKER_EXTRA_HOST_PATHS entries must be absolute paths: $extra_path" >&2 return 1 ;; esac if [ ! -e "$extra_path" ]; then printf '%s\n' "PI_WEB_DOCKER_EXTRA_HOST_PATHS entry does not exist: $extra_path" >&2 return 1 fi pi_web_docker_host_write_volume "$extra_path" "$extra_path" false done } pi_web_docker_host_write_compose_override() { target_file=$1 host_profile=$2 extra_paths=${3:-} target_dir=$(dirname "$target_file") mkdir -p "$target_dir" || return 1 PI_WEB_DOCKER_HOST_OVERRIDE_TEMP=$target_file.$$ case "$host_profile" in linux-native-docker) hostexec_mode=nsenter ;; mac-docker-desktop) hostexec_mode=disabled ;; *) printf '%s\n' "unsupported PI WEB Docker host profile: $host_profile" >&2 return 1 ;; esac cat >"$PI_WEB_DOCKER_HOST_OVERRIDE_TEMP" <>"$PI_WEB_DOCKER_HOST_OVERRIDE_TEMP" <&2 printf '%s\n' "" >&2 printf '%s\n' "Detected:" >&2 printf ' host OS: %s\n' "${PI_WEB_DETECTED_HOST_OS:-unknown}" >&2 printf ' docker context: %s\n' "${PI_WEB_DETECTED_DOCKER_CONTEXT:-unknown}" >&2 printf ' docker endpoint: %s\n' "${PI_WEB_DETECTED_DOCKER_ENDPOINT:-unknown}" >&2 printf ' DOCKER_HOST: %s\n' "${PI_WEB_DETECTED_DOCKER_HOST_ENV:-unset}" >&2 printf ' effective endpoint: %s\n' "${PI_WEB_DETECTED_DOCKER_EFFECTIVE_ENDPOINT:-unknown}" >&2 printf ' docker socket source: %s\n' "${PI_WEB_DETECTED_DOCKER_SOCKET_SOURCE:-unknown}" >&2 printf ' docker OS: %s\n' "${PI_WEB_DETECTED_DOCKER_OS:-unknown}" >&2 printf '%s\n' "" >&2 printf '%s\n' "Supported profiles:" >&2 printf '%s\n' " - native Linux Docker Engine using /var/run/docker.sock" >&2 printf '%s\n' " - Docker Desktop for Mac" >&2 if [ -n "${PI_WEB_DOCKER_HOST_PROFILE_ERROR:-}" ]; then printf '%s\n' "" >&2 printf 'Reason: %s\n' "$PI_WEB_DOCKER_HOST_PROFILE_ERROR" >&2 fi } pi_web_docker_compose() { if docker compose version >/dev/null 2>&1; then docker compose "$@" elif command -v docker-compose >/dev/null 2>&1; then docker-compose "$@" else printf '%s\n' "Docker Compose is required (docker compose plugin or docker-compose)" >&2 return 1 fi }