diff --git a/distrobox b/distrobox index 73a3a6706a..26ff6d6926 100755 --- a/distrobox +++ b/distrobox @@ -62,6 +62,31 @@ distrobox_path="$(dirname "${0}")" distrobox_command="${1}" shift +# On macOS with Docker Desktop, only paths under $HOME are shared with the +# Docker Linux VM via VirtioFS. Scripts installed to /usr/local/bin (e.g. +# via Homebrew) are not accessible, so Docker creates an empty directory +# placeholder in the VM, causing "exec: /usr/bin/entrypoint: is a directory". +# +# Maintain a managed cache in ~/.local/share/distrobox/ (always under $HOME, +# always VirtioFS-shared). Copy each script only when the installed version +# is newer than the cached copy, so upgrades are picked up automatically. +if [ "$(uname -s)" = "Darwin" ]; then + _dd_data_dir="${XDG_DATA_HOME:-"${HOME}/.local/share"}/distrobox" + mkdir -p "${_dd_data_dir}" + for _dd_script in distrobox-init distrobox-export distrobox-host-exec; do + _dd_src="${distrobox_path}/${_dd_script}" + [ ! -f "${_dd_src}" ] && _dd_src="$(command -v "${_dd_script}" 2> /dev/null || true)" + if [ -f "${_dd_src}" ]; then + _dd_dst="${_dd_data_dir}/${_dd_script}" + if [ ! -f "${_dd_dst}" ] || [ "${_dd_src}" -nt "${_dd_dst}" ]; then + cp -f "${_dd_src}" "${_dd_dst}" + chmod +x "${_dd_dst}" + fi + fi + done + unset _dd_script _dd_src _dd_dst _dd_data_dir +fi + # Simple wrapper to the distrobox utilities. # We just detect the 1st argument and launch the matching distrobox utility. case "${distrobox_command}" in diff --git a/distrobox-create b/distrobox-create index 1731bebcd4..bbd30231cb 100755 --- a/distrobox-create +++ b/distrobox-create @@ -102,6 +102,26 @@ distrobox_hostexec_path="$(cd "$(dirname "${0}")" && pwd)/distrobox-host-exec" [ ! -e "${distrobox_export_path}" ] && distrobox_export_path="$(command -v distrobox-export)" [ ! -e "${distrobox_genentry_path}" ] && distrobox_genentry_path="$(command -v distrobox-generate-entry)" [ ! -e "${distrobox_hostexec_path}" ] && distrobox_hostexec_path="$(command -v distrobox-host-exec)" +# On macOS with Docker Desktop, prefer the managed cache under +# ~/.local/share/distrobox/ (always VirtioFS-shared) over the installed +# paths. The main distrobox entry point keeps the cache up-to-date; when +# distrobox-create is invoked directly the cache may also exist from a prior +# run via the main entry point. +if [ "$(uname -s)" = "Darwin" ]; then + _dd_data_dir="${XDG_DATA_HOME:-"${HOME}/.local/share"}/distrobox" + for _dd_script in distrobox-init distrobox-export distrobox-host-exec; do + _dd_cached="${_dd_data_dir}/${_dd_script}" + if [ -f "${_dd_cached}" ]; then + case "${_dd_script}" in + distrobox-init) distrobox_entrypoint_path="${_dd_cached}" ;; + distrobox-export) distrobox_export_path="${_dd_cached}" ;; + distrobox-host-exec) distrobox_hostexec_path="${_dd_cached}" ;; + *) ;; + esac + fi + done + unset _dd_script _dd_cached _dd_data_dir +fi # If the user runs this script as root in a login shell, set rootful=1. # There's no need for them to pass the --root flag option in such cases. [ "${container_user_uid}" -eq 0 ] && rootful=1 || rootful=0 diff --git a/distrobox-enter b/distrobox-enter index 93f47761c3..4a59febb56 100755 --- a/distrobox-enter +++ b/distrobox-enter @@ -689,6 +689,45 @@ if [ "${container_status}" != "running" ]; then # Here, we save the timestamp before launching the start command, so we can # be sure we're working with this very same session of logs later. log_timestamp="$(date -u +%FT%T).000000000+00:00" + # On macOS with Docker Desktop + VirtioFS, only paths under $HOME and a + # few system directories (/private, /Volumes) are shared with the Docker + # VM. If distrobox-init lives outside those paths (e.g. /usr/local/bin + # from Homebrew), Docker cannot find the bind-mount source in the VM and + # creates an empty directory placeholder instead, causing the container + # to fail with "exec: /usr/bin/entrypoint: is a directory". + # + # Detect this by reading the stored bind-mount source from + # .HostConfig.Binds (the container config, not .Mounts which is empty + # for stopped containers) and checking whether it is accessible as a + # file. If it is not, copy the current distrobox-init to + # ~/.local/share/distrobox/ (always under $HOME, always shared) and + # write it to the stored source path so the bind-mount resolves to a + # file when the container starts. + entrypoint_source="$(${container_manager} inspect --type container \ + --format '{{range .HostConfig.Binds}}{{printf "%s\n" .}}{{end}}' \ + "${container_name}" 2> /dev/null | + grep ':/usr/bin/entrypoint' | cut -d: -f1)" + # On macOS with Docker Desktop, .HostConfig.Binds may store paths with a + # /host_mnt prefix (Docker Desktop's VirtioFS mount prefix in the Linux VM). + # Strip the prefix to get the actual macOS path for the file-existence check. + entrypoint_source_check="${entrypoint_source}" + if [ "$(uname -s)" = "Darwin" ]; then + entrypoint_source_check="${entrypoint_source_check#/host_mnt}" + fi + if [ -n "${entrypoint_source}" ] && [ ! -f "${entrypoint_source_check}" ]; then + distrobox_entrypoint_path="$(cd "$(dirname "${0}")" && pwd)/distrobox-init" + [ ! -f "${distrobox_entrypoint_path}" ] && + distrobox_entrypoint_path="$(command -v distrobox-init 2> /dev/null)" + if [ -f "${distrobox_entrypoint_path}" ]; then + # Remove Docker's empty-directory placeholder (if present) and + # replace it with the actual script so the bind-mount works. + [ -d "${entrypoint_source}" ] && rmdir "${entrypoint_source}" 2> /dev/null + mkdir -p "$(dirname "${entrypoint_source}")" + cp "${distrobox_entrypoint_path}" "${entrypoint_source}" + chmod +x "${entrypoint_source}" + fi + fi + unset entrypoint_source entrypoint_source_check ${container_manager} start "${container_name}" > /dev/null # # Check if the container is going in error status earlier than the