|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# Shared common Docker helpers for Heads dev scripts |
| 4 | +# Meant to be sourced from docker_latest.sh / docker_local_dev.sh / docker_repro.sh |
| 5 | + |
| 6 | +set -euo pipefail |
| 7 | + |
| 8 | +usage() { |
| 9 | + cat <<'USAGE' |
| 10 | +Usage: $0 [OPTIONS] -- [COMMAND] |
| 11 | +Options: |
| 12 | +Environment variables (opt-ins / opt-outs): |
| 13 | + HEADS_DISABLE_USB=1 Disable automatic USB passthrough (default: enabled when /dev/bus/usb exists) |
| 14 | + HEADS_X11_XAUTH=1 Explicitly mount $HOME/.Xauthority into the container for X11 auth |
| 15 | +Command: |
| 16 | + The command to run inside the Docker container, e.g., make BOARD=BOARD_NAME |
| 17 | +USAGE |
| 18 | +} |
| 19 | + |
| 20 | +# Track whether we supply Xauthority into the container |
| 21 | +DOCKER_XAUTH_USED=0 |
| 22 | + |
| 23 | +# Kill scdaemon/pcscd when USB passthrough is present (minimal, automatic) |
| 24 | +kill_usb_processes() { |
| 25 | + [ -d /dev/bus/usb ] || return 0 |
| 26 | + [ "${HEADS_DISABLE_USB:-0}" = "1" ] && { echo "HEADS_DISABLE_USB=1: skipping USB cleanup" >&2; return 0; } |
| 27 | + |
| 28 | + # Collect scdaemon and pcscd PIDs (simple, per-origin/master behavior) |
| 29 | + mapfile -t pids_array < <(pgrep -x scdaemon || true; pgrep -x pcscd || true) |
| 30 | + [ ${#pids_array[@]} -eq 0 ] && { [ "${HEADS_USB_VERBOSE:-0}" = "1" ] && echo "No scdaemon/pcscd running." >&2; return 0; } |
| 31 | + |
| 32 | + echo "Detected scdaemon/pcscd processes: ${pids_array[*]}" >&2 |
| 33 | + |
| 34 | + # Warn before attempting kills. Non-interactive shells continue; interactive shells get a short abort window. |
| 35 | + echo "WARNING: About to kill the above processes to free USB devices for passthrough. To skip this automatic action set HEADS_DISABLE_USB=1 in your environment." >&2 |
| 36 | + if [ -t 1 ]; then |
| 37 | + echo "Press Ctrl-C to abort within 3 seconds if you do NOT want these processes killed." >&2 |
| 38 | + sleep 3 |
| 39 | + fi |
| 40 | + |
| 41 | + # Try to kill: prefer running as root, else try sudo without prompting in non-interactive shells |
| 42 | + if [ "$(id -u)" = "0" ]; then |
| 43 | + kill -9 "${pids_array[@]}" && echo "Killed PIDs: ${pids_array[*]}" >&2 || echo "Failed to kill some PIDs: ${pids_array[*]}" >&2 |
| 44 | + elif sudo -n true 2>/dev/null; then |
| 45 | + sudo kill -9 "${pids_array[@]}" && echo "Killed PIDs: ${pids_array[*]}" >&2 || echo "Failed to kill some PIDs: ${pids_array[*]}" >&2 |
| 46 | + elif [ -t 1 ]; then |
| 47 | + sudo kill -9 "${pids_array[@]}" && echo "Killed PIDs: ${pids_array[*]}" >&2 || echo "Failed to kill some PIDs: ${pids_array[*]}" >&2 |
| 48 | + else |
| 49 | + echo "Non-interactive: sudo would prompt but cannot proceed; please run: sudo kill -9 ${pids_array[*]}" >&2 |
| 50 | + fi |
| 51 | +} |
| 52 | + |
| 53 | +# Build docker options (returns single string on stdout) |
| 54 | +build_docker_opts() { |
| 55 | + local opts=( -e "DISPLAY=${DISPLAY:-}" --network host --rm -ti ) |
| 56 | + |
| 57 | + # USB passthrough |
| 58 | + if [ -d "/dev/bus/usb" ] && [ "${HEADS_DISABLE_USB:-0}" != "1" ]; then |
| 59 | + opts+=( --device=/dev/bus/usb:/dev/bus/usb ) |
| 60 | + echo "--->USB passthrough enabled; to disable set HEADS_DISABLE_USB=1" >&2 |
| 61 | + elif [ -d "/dev/bus/usb" ]; then |
| 62 | + echo "--->Host USB present; USB passthrough disabled by HEADS_DISABLE_USB=1" >&2 |
| 63 | + fi |
| 64 | + |
| 65 | + # KVM passthrough |
| 66 | + if [ -e /dev/kvm ]; then |
| 67 | + opts+=( --device=/dev/kvm:/dev/kvm ) |
| 68 | + echo "--->Host KVM device found; enabling /dev/kvm passthrough" >&2 |
| 69 | + elif [ -e /proc/kvm ]; then |
| 70 | + echo "--->Host reports KVM available but /dev/kvm is missing; load kvm module" >&2 |
| 71 | + fi |
| 72 | + |
| 73 | + # X11 forwarding: mount socket and try programmatic Xauthority when possible |
| 74 | + if [ -d "/tmp/.X11-unix" ]; then |
| 75 | + opts+=( -v /tmp/.X11-unix:/tmp/.X11-unix ) |
| 76 | + if command -v xauth >/dev/null 2>&1; then |
| 77 | + local XAUTH_HOST |
| 78 | + XAUTH_HOST="/tmp/.docker.xauth-$(id -u)" |
| 79 | + : >"$XAUTH_HOST" 2>/dev/null || true |
| 80 | + xauth nlist "${DISPLAY}" 2>/dev/null | sed -e 's/^..../ffff/' | xauth -f "$XAUTH_HOST" nmerge - 2>/dev/null || true |
| 81 | + if [ -s "$XAUTH_HOST" ]; then |
| 82 | + DOCKER_XAUTH_USED=1 |
| 83 | + opts+=( -v "$XAUTH_HOST:$XAUTH_HOST:ro" -e "XAUTHORITY=$XAUTH_HOST" ) |
| 84 | + echo "--->Using programmatic Xauthority $XAUTH_HOST for X11 auth" >&2 |
| 85 | + elif [ -f "${HOME}/.Xauthority" ]; then |
| 86 | + DOCKER_XAUTH_USED=1 |
| 87 | + opts+=( -v "${HOME}/.Xauthority:/root/.Xauthority:ro" -e "XAUTHORITY=/root/.Xauthority" ) |
| 88 | + echo "--->Falling back to mounting ${HOME}/.Xauthority into container" >&2 |
| 89 | + else |
| 90 | + echo "--->X11 socket present but no Xauthority found; GUI may fail" >&2 |
| 91 | + fi |
| 92 | + else |
| 93 | + if [ -f "${HOME}/.Xauthority" ]; then |
| 94 | + opts+=( -v "${HOME}/.Xauthority:/root/.Xauthority:ro" -e "XAUTHORITY=/root/.Xauthority" ) |
| 95 | + echo "--->Mounting ${HOME}/.Xauthority into container for X11 auth (xauth missing)" >&2 |
| 96 | + fi |
| 97 | + fi |
| 98 | + elif [ "${HEADS_X11_XAUTH:-0}" != "0" ] && [ -f "${HOME}/.Xauthority" ]; then |
| 99 | + opts+=( -v "${HOME}/.Xauthority:/root/.Xauthority:ro" -e "XAUTHORITY=/root/.Xauthority" ) |
| 100 | + echo "--->HEADS_X11_XAUTH=1: mounting ${HOME}/.Xauthority into container" >&2 |
| 101 | + fi |
| 102 | + |
| 103 | + # If host xhost does not list LOCAL, warn the user about enabling access only when |
| 104 | + # we did NOT supply an Xauthority cookie. We do NOT modify xhost automatically (security). |
| 105 | + if [ "${DOCKER_XAUTH_USED:-0}" = "0" ] && command -v xhost >/dev/null 2>&1 && ! xhost | grep -q "LOCAL:"; then |
| 106 | + echo "--->X11 auth may be strict; no automatic 'xhost' changes are performed. Provide Xauthority (install xauth) or run 'xhost +SI:localuser:root' manually if you accept the security risk." >&2 |
| 107 | + fi |
| 108 | + |
| 109 | + local joined |
| 110 | + printf -v joined "%s " "${opts[@]}" |
| 111 | + echo "${joined}" |
| 112 | +} |
| 113 | + |
| 114 | +# Common run helper |
| 115 | +run_docker() { |
| 116 | + local image="$1"; shift |
| 117 | + local opts host_workdir container_workdir DOCKER_OPTS_ARRAY |
| 118 | + opts=$(build_docker_opts) |
| 119 | + # Convert the single-line opts string into an array for safe expansion |
| 120 | + read -r -a DOCKER_OPTS_ARRAY <<< "$opts" |
| 121 | + host_workdir="$(pwd)" |
| 122 | + container_workdir="${host_workdir}" |
| 123 | + |
| 124 | + parts=() |
| 125 | + case "${opts}" in *"/dev/kvm"*) parts+=(KVM=on) ;; *) parts+=(KVM=off) ;; esac |
| 126 | + case "${opts}" in *"/dev/bus/usb"*) parts+=(USB=on) ;; *) parts+=(USB=off) ;; esac |
| 127 | + case "${opts}" in *"/tmp/.X11-unix"*) parts+=(X11=on) ;; *) parts+=(X11=off) ;; esac |
| 128 | + |
| 129 | + echo "---> Running container with: ${parts[*]} ; mount ${host_workdir} -> ${container_workdir}" >&2 |
| 130 | + echo "---> Full docker command: docker run ${DOCKER_OPTS_ARRAY[*]} -v ${host_workdir}:${container_workdir} -w ${container_workdir} ${image} -- $*" >&2 |
| 131 | + |
| 132 | + exec docker run "${DOCKER_OPTS_ARRAY[@]}" -v "${host_workdir}:${container_workdir}" -w "${container_workdir}" "${image}" -- "$@" |
| 133 | +} |
| 134 | + |
| 135 | +trap "echo 'Script interrupted. Exiting...'; exit 1" SIGINT |
| 136 | +for arg in "$@"; do |
| 137 | + case "$arg" in -h|--help) usage; exit 0 ;; esac |
| 138 | +done |
| 139 | + |
| 140 | +# Run the USB cleanup common action |
| 141 | +kill_usb_processes |
| 142 | + |
| 143 | +# Informational reminder printed by each docker wrapper |
| 144 | +echo "----" |
| 145 | +echo "Usage reminder: The minimal command is 'make BOARD=XYZ', where additional options, including 'V=1' or 'CPUS=N' are optional." |
| 146 | +echo "For more advanced QEMU testing options, refer to targets/qemu.md and boards/qemu-*/*.config." |
| 147 | +echo |
| 148 | +echo "Type exit within docker image to get back to host if launched interactively!" |
| 149 | +echo "----" |
| 150 | +echo |
0 commit comments