#!/usr/bin/env bash # Start the Claude Desktop container, forwarding the host's X server. # Before launching, verify that a window from the container can actually be # drawn on the host display. # # Usage: ./run.sh [--build] [--check-only] # --build rebuild the image even if it exists # --check-only run the display check and exit without starting Claude set -euo pipefail IMAGE="${IMAGE:-claude-re-setup:latest}" CONTAINER="${CONTAINER:-claude-desktop}" RUNTIME="${RUNTIME:-docker}" build=0 check_only=0 for arg in "$@"; do case "$arg" in --build) build=1 ;; --check-only) check_only=1 ;; -h|--help) sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "Unknown argument: $arg" >&2; exit 2 ;; esac done die() { echo "error: $*" >&2; exit 1; } info() { echo "==> $*"; } command -v "$RUNTIME" >/dev/null || die "'$RUNTIME' not found (set RUNTIME=podman to use podman)" command -v xauth >/dev/null || die "xauth not found on the host (dnf install xorg-x11-xauth)" # --- Display ----------------------------------------------------------------- # On Wayland sessions this is XWayland, which GNOME/KDE start on demand. [[ -n "${DISPLAY:-}" ]] || die "DISPLAY is not set; run this from a graphical session" display_num="${DISPLAY#*:}" display_num="${display_num%%.*}" [[ -S "/tmp/.X11-unix/X${display_num}" ]] \ || die "X socket /tmp/.X11-unix/X${display_num} not found for DISPLAY=$DISPLAY" # Hand the container a copy of this display's cookie, rewritten to match any # hostname (family ffff). This avoids opening the server up with 'xhost +'. xauth_dir="${XDG_RUNTIME_DIR:-/tmp}/claude-re-setup" xauth_file="$xauth_dir/Xauthority" mkdir -p "$xauth_dir" chmod 700 "$xauth_dir" : > "$xauth_file" xauth nlist "$DISPLAY" | sed -e 's/^..../ffff/' | xauth -f "$xauth_file" nmerge - \ || die "could not export the X cookie for $DISPLAY" [[ -s "$xauth_file" ]] || die "no X cookie found for $DISPLAY (is XAUTHORITY set?)" # The container user has the same UID as us, so 0600 is enough. chmod 600 "$xauth_file" # --- Image ------------------------------------------------------------------- if (( build )) || ! "$RUNTIME" image inspect "$IMAGE" >/dev/null 2>&1; then info "Building $IMAGE" "$RUNTIME" build \ --build-arg USER_UID="$(id -u)" \ --build-arg USER_GID="$(id -g)" \ -t "$IMAGE" "$(dirname "$(readlink -f "$0")")" fi # label=disable: the X socket and cookie are host files that SELinux would # otherwise block, and relabeling /tmp/.X11-unix with :z would break the host. run_args=( --rm --security-opt label=disable --shm-size 1g -e DISPLAY=":${display_num}" -e XAUTHORITY=/tmp/.Xauthority -v /tmp/.X11-unix:/tmp/.X11-unix:ro -v "$xauth_file:/tmp/.Xauthority:ro" ) # --- Display check ----------------------------------------------------------- # 1. xdpyinfo proves the container can connect and authenticate to the server. # 2. xmessage opens a real window; xwininfo confirms the server mapped it. info "Checking that the container can draw on DISPLAY=$DISPLAY" check_script=' set -e xdpyinfo >/dev/null 2>&1 || { echo "FAIL: cannot connect to X server at $DISPLAY"; exit 1; } echo "ok: connected to X server ($(xdpyinfo | sed -n "s/^vendor string: *//p"))" title="claude-re-setup-x11-check" xmessage -title "$title" -center -timeout 3 "Display check from the container: OK" & for _ in $(seq 1 30); do if xwininfo -name "$title" 2>/dev/null | grep -q "Map State: IsViewable"; then echo "ok: test window is mapped and viewable" wait || true exit 0 fi sleep 0.1 done kill %1 2>/dev/null || true echo "FAIL: test window was created but never became viewable" exit 1 ' if ! "$RUNTIME" run "${run_args[@]}" "$IMAGE" bash -c "$check_script"; then die "display check failed; see the output above" fi (( check_only )) && { info "Display check passed"; exit 0; } # --- Claude Desktop ---------------------------------------------------------- if "$RUNTIME" container inspect "$CONTAINER" >/dev/null 2>&1; then die "container '$CONTAINER' already exists; stop it with: $RUNTIME stop $CONTAINER" fi # No config volume: the container is removed on stop (--rm), so the sign-in # is discarded and Claude asks for a login on every start. info "Starting Claude Desktop in container '$CONTAINER'" "$RUNTIME" run -d "${run_args[@]}" \ --name "$CONTAINER" \ "$IMAGE" >/dev/null info "Running. Logs: $RUNTIME logs -f $CONTAINER Stop: $RUNTIME stop $CONTAINER"