From d7b03a935ac1aa7df1848f19647a6cbe18a45b65 Mon Sep 17 00:00:00 2001 From: icex2 Date: Sat, 27 Feb 2021 00:34:52 +0100 Subject: [PATCH] wip some tests based of references: docker image with simply docker run command works with nx2, though no keyboard input or sound using x11docker, it might be easier to get the missing features though i still need to figure out how to get glxgears to work at least --- Dockerfile | 3 +- README.md | 11 +- dist/Dockerfile | 51 + dist/piueb | 56 + dist/x11docker | 9001 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 9120 insertions(+), 2 deletions(-) create mode 100644 dist/Dockerfile create mode 100755 dist/x11docker diff --git a/Dockerfile b/Dockerfile index 0b80944..6087b27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,8 @@ RUN apt-get update && apt-get install -y \ libasound2-dev:i386 \ libconfig++-dev:i386 \ libx11-dev:i386 \ - libcurl4-gnutls-dev:i386 + libcurl4-gnutls-dev:i386 && \ + rm -rf /var/lib/apt/lists/* # Copy files for building to container RUN mkdir /pumptools diff --git a/README.md b/README.md index 86b6167..b719034 100644 --- a/README.md +++ b/README.md @@ -117,4 +117,13 @@ something for pumptools. ## License Source code license is the Unlicense; you are permitted to do with this as thou wilt. For details, please refer to the -[LICENSE file](LICENSE) included with the source code. \ No newline at end of file +[LICENSE file](LICENSE) included with the source code. + +## Docker notes +* What's the minimum driver version you need on the host? Is there also a maximum? -> offer different docker containers? +* nvidia driver in docker container needs to match host driver apparently? -> confirm this + * offer different dockerfiles with different nvidia versions + * libmesa better alternative here? +* dockerfile for sgl -> sgl repo +* for the nvidia dockerfiles you need nvidia-docker installed +* debugging container: check if GPU is accessible in container: `nvidia-smi`, `glxgears` to test 3d acceleration \ No newline at end of file diff --git a/dist/Dockerfile b/dist/Dockerfile new file mode 100644 index 0000000..b57606a --- /dev/null +++ b/dist/Dockerfile @@ -0,0 +1,51 @@ +# Reference: https://gitlab.com/nvidia/container-images/samples/-/raw/master/opengl/ubuntu16.04/glxgears/Dockerfile +# Use a rather old version of ubuntu to ensure compatibility regarding libc +FROM nvidia/opengl:1.2-glvnd-runtime-ubuntu16.04 + +LABEL description="Runtime environment for pump games with tools" + +ENV DEBIAN_FRONTEND=noninteractive + +ENV NVIDIA_DRIVER_CAPABILITIES ${NVIDIA_DRIVER_CAPABILITIES},display + +# Install build dependencies, multilib to get 32-bit versions +RUN dpkg --add-architecture i386 +RUN apt-get update +RUN apt-get install -y gcc-multilib +RUN apt-get install -y mesa-utils +RUN apt-get install -y libglu1-mesa:i386 +RUN apt-get install -y libusb-0.1-4:i386 +RUN apt-get install -y libconfig++9v5:i386 +RUN apt-get install -y libxcursor1:i386 +RUN apt-get install -y libxinerama1:i386 +RUN apt-get install -y libxi6:i386 +RUN apt-get install -y libxrandr2:i386 +RUN apt-get install -y libxxf86vm1:i386 +RUN apt-get install -y libx11-6:i386 +RUN apt-get install -y libasound2:i386 +RUN apt-get install -y libfreetype6:i386 +RUN apt-get install -y libusb-1.0-0:i386 +RUN apt-get install -y zlib1g:i386 +RUN apt-get install -y libcurl4-gnutls-dev:i386 +RUN apt-get install -y lib32tinfo5 +RUN apt-get install -y lib32ncurses5 +RUN apt-get install -y xorg +RUN apt-get install -y xinit +RUN apt-get install -y x11-xserver-utils +RUN apt-get install -y xserver-xorg-core +RUN apt-get install -y alsa-base +RUN apt-get install -y alsa-utils +RUN apt-get install -y alsa-tools +RUN apt-get install -y gdb +RUN apt-get install -y gdbserver +RUN apt-get install -y strace + +RUN rm -rf /var/lib/apt/lists/* + +RUN mkdir /piu + +WORKDIR /piu + +ENTRYPOINT [ "xterm" ] + +# TODO need a custom entry point that takes the game folder as an argument to run the run.sh script because we need the xsession etc \ No newline at end of file diff --git a/dist/piueb b/dist/piueb index 1b1bde1..a1d6f4c 100755 --- a/dist/piueb +++ b/dist/piueb @@ -369,6 +369,54 @@ cmd_help() print_usage } +cmd_docker_build() +{ + # TODO check if docker installed + + docker rm -f pumptools-run + docker build -t pumptools:run . + docker create --name pumptools-run pumptools:run +} + +cmd_docker_run() +{ + # TODO check if docker installed + + local game_data_path="$1" + local params_piueb="${@:2}" + + if [ ! "$game_data_path" ]; then + log_error "No path to game data given" + exit 1 + fi + + local game_data_path_abs="$(realpath $game_data_path)" + + if [ ! -d "$game_data_path_abs" ]; then + log_error "Game data path is either not a directory or does not exist: $game_data_path_abs" + exit 1 + fi + + log_debug "Path to game data for docker run: $game_data_path_abs" + + log_info "Running in docker container..." + + # Reference: https://gitlab.com/nvidia/container-images/samples/-/raw/master/opengl/ubuntu16.04/glxgears/Dockerfile + xhost +si:localuser:root + + docker run \ + -it \ + --rm \ + --privileged \ + --gpus all \ + -v /dev/bus/usb:/dev/bus/usb \ + -v "$game_data_path_abs:/piu" \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -e DISPLAY \ + pumptools:run \ + /bin/bash +} + ################################################################################## CMD="$1" @@ -399,6 +447,14 @@ case $CMD in cmd_run "valgrind" "${@:2}" exit 0 ;; + "docker-build") + cmd_docker_build + exit 0 + ;; + "docker-run") + cmd_docker_run "${@:2}" + exit 0 + ;; *) echo "Invalid command." print_usage diff --git a/dist/x11docker b/dist/x11docker new file mode 100755 index 0000000..dbbf3e6 --- /dev/null +++ b/dist/x11docker @@ -0,0 +1,9001 @@ +#! /usr/bin/env bash + +# x11docker +# Run GUI applications and desktop environments in Docker containers. +# +# - Runs additional X servers to circumvent common X security leaks. +# - Restricts container capabilities to enhance container security. +# - Container user is same as host user to avoid root in container. +# - Features e.g. sound, hardware acceleration and data storage. +# +# Run 'x11docker --help' or scroll down to read usage information. +# More documentation at: https://github.com/mviereck/x11docker + +Version="6.7.0-beta" + +# --enforce-i: Enforce running in interactive mode to allow commands tty and weston-launch in special setups. +grep -q -- "--enforce-i" <<< "$*" && case $- in + *i*) set +H ;; + *) exec bash --noprofile --norc --noediting -i -- "$0" "$@" ;; +esac + +usage() { # --help: show usage information + echo " +x11docker: Run GUI applications and desktop environments in Docker containers. + +Usage: +To run a Docker container on a new X server: + x11docker IMAGE + x11docker [OPTIONS] IMAGE [COMMAND] + x11docker [OPTIONS] -- IMAGE [COMMAND [ARG1 ARG2 ...]] + x11docker [OPTIONS] -- DOCKER_RUN_OPTIONS -- IMAGE [COMMAND [ARG1 ARG2 ...]] +To run a host application on a new X server: + x11docker [OPTIONS] --exe COMMAND + x11docker [OPTIONS] --exe -- COMMAND [ARG1 ARG2 ...] +To run only an empty new X server: + x11docker [OPTIONS] --xonly + +x11docker always runs a fresh container from image and discards it afterwards. +Runs on Linux and (with some restrictions) on MS Windows. Not adapted for macOS. + +Optional features: + * GPU hardware acceleration + * Sound with pulseaudio or ALSA + * Clipboard sharing + * Printer access + * Webcam access + * Persistent home folder + * Wayland support + * Language locale creation + * Several init systems and DBus in container + * Support of several container runtimes +Focus on security: + * Avoids X security leaks using additional X servers. + * Container user is same as host user to avoid root in container. + * Restricts container capabilities to bare minimum. + +x11docker sets up an unprivileged container user with password 'x11docker' +and restricts container capabilities. Some applications might behave different +than with a regular 'docker run' command due to these security restrictions. +Achieve a less restricted setup with --cap-default, --sudouser or --user=root. + +Dependencies on host: + For core functionality x11docker only needs bash, docker and an X server. + Depending on chosen options x11docker might need some additional tools. + It checks for them on startup and shows messages if some are missing. + Core list of recommended tools: + * Recommended to allow security and convenience: + X servers: xpra Xephyr nxagent + X tools: xauth xclip xrandr xhost xinit + * Advanced GPU support: weston Xwayland xpra xdotool + See also: https://github.com/mviereck/x11docker/wiki/Dependencies + +Dependencies in image: + No dependencies in image except for a few feature options. Most important: + --gpu: OpenGL/MESA packages, collected often in 'mesa-utils' package. + --pulseaudio: Needs pulseaudio on host and pulseaudio client libs in image. + --printer: Needs cups on host and cups client libs in image. + See also: https://github.com/mviereck/x11docker/wiki/Dependencies + +Options: (short options do not accept arguments) + --help Display this message and exit. + -e, --exe Execute host application instead of docker container. + --xonly Only create empty X server. + +Basic settings: + -d, --desktop Indicate a desktop environment in image. + In that case important for automatical X server choice. + -i, --interactive Run with an interactive tty to allow shell commands. + Useful with commands like bash. + +Host integration: + --alsa [=ALSA_CARD] Sound with ALSA. You can define a desired sound card + with ALSA_CARD. List of available sound cards: aplay -l + -c, --clipboard Share clipboard. Graphical clips with --xpra only. + -g, --gpu GPU access for hardware accelerated OpenGL rendering. + Works best with open source drivers on host and in image. + For closed source nvidia drivers regard terminal output. + -I, --network [=NET] Allow internet access. (Currently enabled by default, + will change in future.) + For optional argument NET see Docker documentation + of docker run option --network. + --network=none disables internet access. (future default) + -l, --lang [=LOCALE] Set language variable LANG=LOCALE in container. + Without arg LOCALE host variable --lang=\$LANG is used. + If LOCALE is missing in image, x11docker generates it + with 'localedef' in container (needs 'locales' package). + Examples for LOCALE: ru, en, de, zh_CN, cz, fr, fr_BE. + -P, --printer [=MODE] Share host printers through CUPS server. + Optional MODE can be 'socket' or 'tcp'. Default: socket + -p, --pulseaudio [=MODE] Sound with pulseaudio. Needs 'pulseaudio' on host + and in image. Optional arg MODE can be 'socket' or 'tcp'. + --webcam Share host webcam device files. + +Shared host folders or Docker volumes: + -m, --home [=ARG] Create a persistant HOME folder for data storage. + Default: Uses ~/.local/share/x11docker/IMAGENAME. + ARG can be another host folder or a Docker volume. + (~/.local/share/x11docker has a softlink to ~/x11docker.) + (Use --homebasedir to change this base storage folder.) + --share ARG Share host file or folder ARG. Read-only with ARG:ro + Device files in /dev can be shared, too. + ARG can also be a Docker volume instead of a host folder. + +X server options: + --auto Automatically choose X server (default). Influenced + noteable by options --desktop, --gpu, --wayland, --wm. + -h, --hostdisplay Share host display :0. Quite bad container isolation! + Least overhead of all X server options. + Some apps may fail due to restricted untrusted cookies. + Remove restrictions with option --clipboard. + -n, --nxagent Nested X server supporting seamless and --desktop mode. + Faster than --xpra, but can have compositing issues. + -Y, --weston-xwayland Desktop mode like --xephyr, but supports option --gpu. + Runs from console, within X and within Wayland. + -y, --xephyr Nested X server for --desktop mode. Without --desktop + a host window manager will be provided (option --wm). + -x, --xorg Core Xorg server. Runs ootb from console. + Switch tty with ..... Always switch + to a black tty before switching to X to avoid crashes. + -a, --xpra Nested X server supporting seamless and --desktop mode. + -A, --xpra-xwayland Like --xpra, but supports option --gpu. + +Special X server options: + --kwin-xwayland Like --weston-xwayland, but using kwin_wayland + -t, --tty Terminal only mode. Does not run an X or Wayland server. + --xdummy Invisible X server using dummy video driver. + --xvfb Invisible X server using Xvfb. + --xdummy and --xvfb can be used for custom VNC access. + Output of environment variables on stdout. (--showenv) + Along with option --gpu an invisible setup with Weston, + Xwayland and xdotool is used (instead of Xdummy or Xvfb). + -X, --xwayland Blanc Xwayland, needs a running Wayland compositor. + --xwin X server to run in Cygwin/X on MS Windows. + +Wayland instead of X: + -W, --wayland Automatically set up a Wayland environment. + Chooses one of following options and regards --desktop. + -T, --weston Weston without X for pure Wayland applications. + Runs in X, in Wayland or from console. + -K, --kwin KWin without X for pure Wayland applications. + Runs in X, in Wayland or from console. + -H, --hostwayland Share host Wayland without X for pure Wayland apps. + +X and Wayland appearance options: + --border [=COLOR] Draw a colored border in windows of --xpra[-xwayland]. + Argument COLOR can be e.g. 'orange' or '#F00'. Thickness + can be specified, too, e.g. 'red,3'. Default: 'blue,1' + --dpi N dpi value (dots per inch) to submit to X clients. + Influences font size of some applications. + -f, --fullscreen Run in fullscreen mode. + --output-count N Multiple virtual monitors for Weston, KWin or Xephyr. + --rotate N Rotate display (--xorg, --weston and --weston-xwayland) + Allowed values: 0, 90, 180, 270, flipped, flipped-90, + flipped-180, flipped-270. (flipped means mirrored) + --scale N Scale/zoom factor N for xpra, Xorg or Weston. + Allowed for --xpra, --xorg --xpra-xwayland: 0.25...8.0. + Allowed for --weston and --weston-xwayland: 1...9. + (Mismatching font sizes can be adjusted with --dpi). + Odd resolutions with --xorg might need --scale=1. + --size WxH Screen size of new X server (e.g. 800x600). + -w, --wm [=ARG] Provide a window manager to container applications. + If available, image x11docker/openbox will be used. + Otherwise x11docker looks for a host window manager. + Possible ARG: + host: Enforce autodetection of a host window manager. + COMMAND: COMMAND can be a desired host window manager. + IMAGE: IMAGE can be a local docker image with a WM. + none: Run without a window manager. Same as --desktop. + -F, --xfishtank Show fish tank on new X server. + +X and Wayland special configuration: + --clean-xhost Disable xhost access policies on host display. + --display N Use display number N for new X server. + --keymap LAYOUT Set keyboard layout for new X server, e.g. de, us, ru. + For possible LAYOUT look at /usr/share/X11/xkb/symbols. + --no-auth Allow access to X for everyone. Security risk! + --vt N Use vt / tty N (regarded by --xorg only). + --westonini FILE Custom weston.ini for --weston and --weston-xwayland. + --xhost STR Set \"xhost STR\" on new X server (see 'man xhost'). + (Use with care. '--xhost +' allows access for everyone). + --xoverip Connect to X over TCP network. For special setups only. + Only supported by a subset of X server options. + --xtest [=yes|no] Enable or disable X extension XTEST. Default is yes for + --xpra, --xvfb and --xdummy, no for other X servers. + Needed to allow custom access with xpra. + +Container user settings: + --group-add GROUP Add container user to group GROUP. + --hostuser USER Run X (and container user) as user USER. Default is + result of \$(logname). (x11docker must run as root). + --sudouser Allow su and sudo for container user. Use with care, + severe reduction of default x11docker security! + --user N Create container user N (N=name or N=uid). Default: + same as host user. N can also be an unknown user id. + You can specify a group id with N being 'user:gid'. + Special case: --user=RETAIN keeps image user settings. + +Container capabilities: + In most setups x11docker sets --cap-drop=ALL --security-opt=no-new-privileges + and shows warnings if doing otherwise. + Custom capabilities can be added with --cap-add=CAP after -- + --cap-default Allow default docker container capabilities. + Includes --newprivileges=yes. + --hostipc Sets docker option --ipc=host. Disables IPC namespacing. + Severe reduction of container isolation! Shares + host interprocess communication and shared memory. + Allows MIT-SHM extension of X servers. + --limit [=FACTOR] Limit CPU and RAM usage of container to + currently free RAM x FACTOR and available CPUs x FACTOR. + Allowed range is 0 < FACTOR <= 1. + Default for --limit without argument FACTOR: 0.5 + --newprivileges [=yes|no|auto] Set or unset docker run option + --security-opt=no-new-privileges. Default with no + argument is 'yes'. Default for most cases is 'no'. + +Container init system, elogind and DBus daemon: + --dbus [=system] Run DBus user session daemon for container command. + With argument 'system' also run a DBus system daemon. + (To run a DBus system daemon rather use one of + --init=systemd|openrc|runit|sysvinit ) + --hostdbus Connect to DBus user session from host. + --init [=INITSYSTEM] Run an init system as PID 1 in container. Solves the + zombie reaping issue. INITSYSTEM can be: + tini: Default. Mostly present as docker-init on host. + none: No init system, container command will be PID 1. + Others: systemd, sysvinit, runit, openrc, s6-overlay. + --sharecgroup Share /sys/fs/cgroup. Allows elogind in container if + used with one of --init=openrc|runit|sysvinit + +Container special configuration: + --env VAR=value Set custom environment variable VAR=value + --name NAME Specify container name NAME. + --no-entrypoint Disable ENTRYPOINT in image to allow other commands, too + --runtime RUNTIME Specify docker runtime. Known by x11docker: + runc: Docker default runtime. + crun: Fast replacement for runc written in C. + nvidia: Runtime for nvidia/nvidia-docker images. + kata-runtime: Runtime using a QEMU VM. + --shell SHELL Set preferred user shell. Example: --shell=/bin/zsh + --stdin Forward stdin of x11docker to container command. + --workdir DIR Set working directory DIR. + +Additional commands: (You might need to move them to background with 'CMD &'.) + --runasroot CMD Run command CMD as root in container. + Caution: Runs with --privileged host access. + --runasuser CMD Run command CMD with user privileges in container + before running image command. + --runfromhost CMD Run host command CMD on new X server. + +Miscellaneous: + --cachebasedir DIR Custom base folder for cache files. + --homebasedir DIR Custom base folder for option --home. + --enforce-i Run x11docker in interactive bash mode to allow tty + access. Can help to run weston-launch on special systems. + --fallback [yes|no] Allow or deny fallbacks if a chosen option cannot + be fulfilled. By default fallbacks are allowed. + --launcher Create application launcher with current options + on desktop and exit. You can get a menu entry moving + the created .desktop file to ~/.local/share/applications + --mobyvm Use MobyVM (for WSL2 only that defaults to linux Docker). + --preset FILE Read a set of predefined options stored in file FILE. + Useful to shortcut often used option combinations. + FILE is searched in directory /etc/x11docker/preset, + or in directory ~/.config/x11docker/preset or absolute. + Multiple lines in FILE are allowed. + Comment lines must begin with # + --pull [=ask|yes|no|always] Behaviour if image is missing on host. + ask: Ask in terminal, timeout after 60s (default). + yes: Allow docker pull (default for --pull without arg). + no: Do not run or ask for 'docker pull' + always: Always run 'docker pull'. Download only if + newer image is available. Allows sort of auto-update. + --pw FRONTEND Choose frontend for password prompt. Possible FRONTEND: + su sudo gksu gksudo lxsu lxsudo kdesu kdesudo + pkexec beesu none + +Output of parseable information on stdout: + Get output e.g. with: read xenv < <(x11docker --showenv x11docker/check) + --showenv Print new \$DISPLAY, \$XAUTHORITY and \$WAYLAND_DISPLAY. + --showid Print container ID. + --showinfofile Print path to internal x11docker info storage file. + --showpid1 Print host PID of container PID 1. + +Verbosity options: + -D, --debug Debug mode: Show some less verbose debug output + and enable rigorous error control. + -q, --quiet Suppress x11docker terminal messages. + -v, --verbose Be verbose. Output of x11docker.log on stderr. + -V Be verbose with colored output. + +Installation options (need root permissions), license and cleanup: + --install Install x11docker and x11docker-gui from current folder. + Useful to install from an extracted zip file. + --update Download and install latest release from github. + --update-master Download and install latest master version from github. + --remove Remove x11docker from your system. Includes --cleanup. + Preserves ~/.local/share/x11docker from option --home. + --license Show license of x11docker (MIT) and exit. + --cleanup Clean up orphaned containers and cache files. + Terminates currently running x11docker containers, too. + +Exit codes: + 0: Success + 64: x11docker error + 130: Terminated by ctrl-c + other: Exit code of command in container + +x11docker version: $Version +Please report issues and get help at: https://github.com/mviereck/x11docker +" +} +license() { # --license: show license (MIT) +echo 'MIT License + +Copyright (c) 2015, 2016, 2017, 2018, 2019, 2020, 2021 Martin Viereck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.' +} + +#### messages +alertbox() { # X alert box with title $1 and message $2 + local Title Message + Title="${1:-}" + Message="${2:-}" + + Message="$(echo "$Message" | LANG=C sed "s/[\x80-\xFF]//g" | fold -w120 )" # remove UTF-8 special chars; line folding at 120 chars + + # try some tools to show alert message. If all tools fail, return 1 + command -v xmessage >/dev/null && [ -n "${DISPLAY:-}" ] && { + echo "$Title + +$Message" | xmessage -file - -default okay ||: + } || { + command -v gxmessage >/dev/null && [ -n "${DISPLAY:-}" ] && { + echo "$Title + +$Message" | gxmessage -file - -default okay ||: + } + } || { + command -v zenity >/dev/null && [ -n "${DISPLAY:-}" ] && { + zenity --error --no-markup --ellipsize --title="$Title" --text="$Message" 2>/dev/null ||: + } + } || { + command -v yad >/dev/null && [ -n "${DISPLAY:-}" ] && { + yad --image "dialog-error" --title "$Title" --button=gtk-ok:0 --text "$(echo "$Message" | sed 's/\\/\\\\/g')" --fixed 2>/dev/null ||: + } + } || { + command -v kaptain >/dev/null && [ -n "${DISPLAY:-}" ] && { + echo 'start "'$Title'" -> message @close=" cancel" ; + message "'$(echo "$Message" | sed 's/\\/\\\\\\/g' | sed 's/"/\\"/g' | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g' )'" -> @fill ;' | kaptain ||: + } + } || { + command -v kdialog >/dev/null && [ -n "${DISPLAY:-}" ] && { + kdialog --title "$Title" --error "$(echo "$Message" | sed 's/\\/\\\\/g' )" 2>/dev/null ||: + } + } || { + command -v xterm >/dev/null && [ -n "${DISPLAY:-}" ] && { + xterm -title "$Title" -e "echo '$(echo "$Message" | sed "s/'/\"/g")' ; read -n1" ||: + } + } || { + [ -n "$Passwordterminal" ] && [ "$Passwordterminal" != "eval" ] && [ -e "$Cachefolder" ] && { + mkfile $Cachefolder/message + echo "#! /usr/bin/env bash +echo '$Title + +$Message +(Press any key to close window)' +read -n1 +: +" >> $Cachefolder/message + $Passwordterminal /usr/bin/env bash $Cachefolder/message + } + } || { + notify-send "$Title: + +$Message" 2>/dev/null + } || { + warning "Could not display message on X: +$Message" + return 1 + } + return 0 +} +debugnote() { # show debug output $* + [ "$Debugmode" = "yes" ] && [ "$Verbose" = "no" ] && echo "${Colblue}DEBUGNOTE[$(timestamp)]:${Colnorm} $*" >&${FDstderr} + logentry "DEBUGNOTE[$(timestamp)]: $*" + return 0 +} +error() { # show error message and exit + local Message + + Message="$* + + Type 'x11docker --help' for usage information + Debug options: '--verbose' (full log) or '--debug' (log excerpt). + Logfile will be: $Logfilebackup + Please report issues at https://github.com/mviereck/x11docker" + + Message="$(rmcr <<< "$Message")" + + # output to terminal + [ "$Verbose" = "no" ] && echo -e " +${Colredbg}x11docker ERROR:${Colnorm} $Message +" >&2 + + # output to logfile + logentry "x11docker ERROR: $Message +" + saygoodbye error + storeinfo test error && waitfortheend + storeinfo error=64 + + # output to X dialogbox if not running in terminal + [ "$Runsinterminal" = "no" ] && [ "$Silent" = "no" ] && export ${Hostxenv:-DISPLAY} && alertbox "x11docker ERROR" "$Message" & + + finish +} +logentry() { # write into logfile + [ -e "$Logfile" ] && { + [ -n "$Logmessages" ] && echo "$Logmessages" >>$Messagelogfile 2>/dev/null && Logmessages="" + echo "$*" >>$Messagelogfile 2>/dev/null + : + } || Logmessages="$Logmessages +$*" +} +note() { # show notice messages + [ "$Verbose" = "no" ] && echo "${Colgreen}x11docker note:${Colnorm} $* +" >&${FDstderr} + logentry "x11docker note: $* +" +} +traperror() { # trap ERR: --debug: Output for 'set -o errtrace' + debugnote "traperror: Command at Line ${2:-} returned with error code ${1:-}: + ${4:-} + ${3:-} - ${5:-}" + storeinfo error=64 + saygoodbye traperror +} +verbose() { # show verbose messages + # only logfile notes here, terminal output is done with tail in setup_verbosity() + logentry "x11docker[$(timestamp)]: $* +" +} +warning() { # show warning messages + [ "$Verbose" = "no" ] && echo "${Colyellow}x11docker WARNING:${Colnorm} $* +" >&${FDstderr} + logentry "x11docker WARNING: $* +" +} +watchmessagefifo() { # watch for messages coming from container or dockerrc + # message in fifo must end with :$Messagetype + local Line= Message= Messagetype= + trap '' SIGINT + while [ -e "$Cachefolder" ]; do + IFS= read -r Line <&${FDmessage} ||: + [ "$Line" ] || sleep 2 # sleep for MSYS2/CYGWIN workaround + [ "$Line" ] && Message="$Message +$Line" + grep -q -E ":WARNING|:NOTE|:DEBUGNOTE|:VERBOSE|:ERROR|:STDOUT" <<< "$Line" && { + Messagetype=":$(echo $Line | rev | cut -d: -f1 | rev)" + Message="${Message%$Messagetype }" + Message="$(tail -n +2 <<< "$Message")" # remove leading newline + case "$Messagetype" in + :WARNING) warning "$Message" ;; + :NOTE) note "$Message" ;; + :DEBUGNOTE) debugnote "$Message" ;; + :ERROR) error "$Message" ;; + :VERBOSE) [ "-d " = "$(cut -c1-3 <<<"$Message" | head -n1)" ] && debugnote "$(tail -c +4 <<< "$Message")" || verbose "$Message" ;; + :STDOUT) echo "$Message" ;; + esac + Message= + Messagetype= + } + done +} + +#### exit +finish() { # trap EXIT routine to clean up background processes and cache + local Pid Name Zeit Exitcode Pid1pid= Dockerlogspid= Dockerstopshellpid= Wmcontainerpid1= Watchmessagefifopid= i + + # do not finish() in subshell, just give signal to all other processes and terminate subshell + [ "$$" = "$BASHPID" ] || { + saygoodbye finish-subshell + exit 0 + } + + debugnote "Terminating x11docker." + saygoodbye "finish" + trap - EXIT + trap - ERR + trap - SIGINT + + # --pw=sudo: no password prompt here, rather fail ### FIXME + [ "$Sudo" ] && { + sudo -n echo 2>/dev/null && Sudo="sudo -n" || Sudo="" + } + + while read -r Line ; do + + Pid="$(echo $Line | awk '{print $1}')" + Name="$(echo $Line | awk '{print $2}')" + debugnote "finish(): Checking pid $Pid ($Name): $(pspid $Pid || echo '(already gone)')" + + checkpid $Pid && { + case $Name in + watchmessagefifo) + Watchmessagefifopid="$Pid" + ;; + dockerstopshell) + Dockerstopshellpid="$Pid" + ;; + dockerlogs) + Dockerlogspid=$Pid + #[ "$Winsubsystem" ] && Dockerlogspid="" + ;; + containerpid1) + Pid1pid="$Pid" + #[ "$Winsubsystem" ] && Pid1pid="" + termpid "$Pid1pid" "$Name" || Debugmode="yes" + # Give container time for graceful shutdown + for Count in 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0; do + checkpid $Pid1pid || break + mysleep $(awk "BEGIN { print $Count * 0.1 }") + debugnote "finish(): Waiting for container PID 1: $Pid1pid to terminate." + done + ;; + wmcontainerpid1) + Wmcontainerpid1="$Pid" + #[ "$Winsubsystem" ] && Wmcontainerpid1="" + termpid "$Wmcontainerpid1" "$Name" + ;; + *) + termpid "$Pid" "$Name" + ;; + esac + } + done < <(tac "$Storepidfile" 2>/dev/null) + + # --pulseaudio: unload module + Pulseaudiomoduleid="$(storeinfo dump pulseaudiomoduleid)" + [ "$Pulseaudiomoduleid" ] && pactl unload-module "$Pulseaudiomoduleid" + + # Check if container is still running -> docker stop + [ "$X11dockermode" = "run" ] && containerisrunning && { + Debugmode="yes" + debugnote "finish(): Container still running. Executing 'docker stop'. + Will wait up to 15 seconds for docker to finish." + + case $Mobyvm in + no) echo "stop" >> "$Dockerstopsignalfifo" ;; + yes) $Dockerexe stop $Containername >>$Containerlogfile 2>&1 ;; + esac + + Zeit="$(date +%s)" + while :; do + containerisrunning || break + debugnote "finish(): Waiting for container to terminate ..." + sleep 1 + [ 15 -lt $(($(date +%s) - $Zeit)) ] && break + done + + containerisrunning && { + Exitcode="64" + debugnote "finish(): Container did not terminate as it should. + Will not clean cache to avoid file permission issues. + You can remove the new container with command: + docker rm -f $Containername + Afterwards, remove cache files with: + rm -R $Cachefolder + or let x11docker do the cleanup work for you: + x11docker --cleanup" + Preservecachefiles="yes" + } || debugnote "finish(): Container terminated successfully" + } + + # remove container + [ "$Preservecachefiles" = "no" ] && [ "$Containername" ] && { + debugnote "Removing container $Containername + $($Dockerexe rm -f "$Containername" 2>&1)" + } + + # Check if 'docker logs' is still running + [ "$FDdockerstop" ] && { + checkpid $Dockerlogspid && echo "stop" >&${FDdockerstop} + # Check if window manager container is still running + checkpid $Wmcontainerpid1 && echo "stop" >&${FDdockerstop} + # Terminate watching subshell in dockerrc + [ -e "$Dockerstopsignalfifo" ] && echo "exit" >&${FDdockerstop} + checkpid $Dockerstopshellpid && sleep 1 + } + + # Stop watching for messages, check others again + while read -r Line ; do + Pid="$(echo $Line | awk '{print $1}')" + Name="$(echo $Line | awk '{print $2}')" + checkpid $Pid && termpid "$Pid" "$Name" + checkpid $Pid && { + # should never happen + warning "Failed to terminate pid $Pid ($Name): $(pspid $Pid ||:)" + storeinfo error=64 + } + done < <(tac "$Storepidfile" 2>/dev/null) + + Exitcode=$(storeinfo dump error) + Exitcode="${Exitcode:-0}" + debugnote "x11docker exit code: $Exitcode" + storeinfo test cmdexitcode && { + Exitcode=$(storeinfo dump cmdexitcode) + debugnote "CMD exit code: $Exitcode" + } + + # backup of logfile in $Cachebasefolder + [ -e "$Logfile" ] && { + [ "$Verbose" = "yes" ] && sleep 1 + unpriv "cp '$Logfile' '$Logfilebackup'" + case $Winsubsystem in + WSL1|WSL2) + [ "$Mobyvm" = "yes" ] && unpriv "cp -T '$Logfilebackup' '$Hostuserhome/.cache/x11docker/x11docker.log'" + ;; + esac + #unpriv "rmcr '$Logfilebackup'" + } + + # close file descriptors + mysleep 0.2 + for Descriptor in ${FDcmdstdin} ${FDdockerstop} ${FDmessage} ${FDstderr} ${FDtimetosaygoodbye} ${FDwatchpid} ; do + exec {Descriptor}>&- + done + + # remove cache files + [ "$Preservecachefiles" = "no" ] && grep -q cache <<<$Cachefolder && grep -q x11docker <<<$Cachefolder && [ "x11docker" != "$(basename "$Cachefolder")" ] && unpriv "rm -f -R '$Cachefolder'" + + case $Runssourced in + yes) return $Exitcode ;; + *) exit $Exitcode ;; + esac +} +finish_sigint() { # trap SIGINT to activate debug mode on finish() + local Pid1pid + Debugmode="yes" + debugnote "Received SIGINT" + storeinfo error=130 + finish +} +saygoodbye() { # create file signaling watching processes to terminate + debugnote "time to say goodbye ($*)" + [ -e "$Timetosaygoodbyefile" ] && echo timetosaygoodbye >> $Timetosaygoodbyefile + [ -e "$Timetosaygoodbyefifo" ] && echo timetosaygoodbye >> $Timetosaygoodbyefifo +} + +#### watching processes +checkpid() { # check if PID $1 is active + #ps -p ${1:-} >/dev/null 2>&1 + [ -e "/proc/${1:-NONSENSE}" ] +} +containerisrunning() { # check if container is running + storeinfo test containerid || return 1 + case $Mobyvm in + no) checkpid "$(storeinfo dump pid1pid)" ;; + yes) $Dockerexe inspect "$(storeinfo dump containerid)" >/dev/null 2>&1 ;; + esac +} +pspid() { # ps -p $1 --no-headers + # On some systems ps does not have option --no-headers. + # On some systems (busybox) ps -p is not supported ### FIXME + # return 1 if not found + LC_ALL=C ps -p "${1:-}" 2>/dev/null | grep -v 'TIME' +} +rocknroll() { # check whether x11docker session is still running + [ -s "$Timetosaygoodbyefile" ] && return 1 + [ -e "$Timetosaygoodbyefile" ] || return 1 + return 0 +} +setonwatchpidlist() { # add PID $1 to watchpidlist() + debugnote "watchpidlist(): Setting pid ${1:-} on watchlist: ${2:-}" + echo "${1:-}" >>$Watchpidfifo + # add to list of background processes + grep -q CONTAINER <<< "${1:-}" || storepid "${1:-}" "${2:-}" +} +storepid() { # store pid $1 and name $2 of process in file $Storepidfile. + # Store pid and process name of background processes in a file + # Used in finish() to clean up background processes + # Store: + # $1 Pid + # $2 codename + # Test for stored pid or codename: + # $1 test + # $2 pid or codename + # Dump stored pid: + # $1 dump + # $2 codename + + case "${1:-}" in + dump) grep -w "${2:-}" "$Storepidfile" | cut -d' ' -f1 ;; + test) grep -q -w "${2:-}" "$Storepidfile" ;; + *) + echo "${1:-NOPID}" "${2:-NONAME}" >> "$Storepidfile" + debugnote "storepid(): Stored pid '${1:-}' of '${2:-}': $(pspid ${1:-} ||:)" + ;; + esac +} +termpid() { # kill PID $1 with codename $2 + # TERM + debugnote "termpid(): Terminating ${1:-} (${2:-}): $(pspid ${1:-} ||:)" + checkpid "${1:-}" && { + kill ${1:-} 2>/dev/null + : + } || return 0 + mysleep 0.1 + checkpid "${1:-}" && mysleep 0.4 || return 0 + + # KILL + debugnote "termpid(): Killing ${1:-} (${2:-}): $(pspid ${1:-} ||:)" + checkpid "${1:-}" && kill -s KILL ${1:-} 2>/dev/null + mysleep 0.2 + checkpid "${1:-}" && { + note "Failed to terminate ${1:-} (${2:-}): $(ps -u -p ${1:-} 2>/dev/null | tail -n1)" + return 1 + } + + return 0 +} +waitfortheend() { # wait for end of x11docker session + # signal is byte in $Timetosaygoodbyefifo + # decent read to wait for signal to terminate + case $Usemkfifo in + yes) + while rocknroll; do + bash -c "read -n1 <${FDtimetosaygoodbye}" && saygoodbye timetosaygoodbyefifo || sleep 1 + done + ;; + no) # Reading from fifo fails on Windows, workaround + while rocknroll; do + sleep 2 + done + ;; + esac + return 0 +} +watchpidlist() { # watch list of important pids + # terminate x11docker if a PID in $Watchpidlist terminates + # serves mainly watching X server, Wayland compositor, container and hostexe + # echo PIDs to watch into >{FDwatchpid} (setonwatchpidlist()) + local Pid= Containername= Line= Watchpidlist= + trap '' SIGINT + + while rocknroll; do + # check for new Pid once a second + read -t1 Pid <&${FDwatchpid} ||: + [ "$Usemkfifo" = "no" ] && sleep 2 # read does not wait if not a fifo + # Got new pid + [ "$Pid" ] && { + [ "${Pid:0:9}" = "CONTAINER" ] && { + # Workaround for MS Windows where the pid cannot be watched + Containername="${Pid#CONTAINER}" + debugnote "watchpidlist(): Watching Container: $Containername" + } || { + Watchpidlist="$Watchpidlist $Pid" + debugnote "watchpidlist(): Watching pids: +$(for Line in $Watchpidlist; do pspid "$Line" || echo "(pid $Line not found)" ; done)" + } + } + # check all stored pids + for Pid in $Watchpidlist; do + [ -e /proc/$Pid ] || { + debugnote "watchpidlist(): PID $Pid has terminated" + saygoodbye "watchpidlist $Pid" + } + done + # Container PID not watchable in MSYS2/Cygwin/WSL11. + [ "$Containername" ] && { + $Dockerexe inspect $Containername >/dev/null 2>&1 || { + debugnote "watchpidlist(): Container $Containername has terminated" + saygoodbye "watchpidlist $Containername" + } + } + done + saygoodbye "watchpidlist" +} + +#### more or less general routines +askyesno() { # ask Yes/no question. Default 'yes' for ENTER, timeout with 'no' after 60s + local Choice + read -t60 -n1 -p "(timeout after 60s assuming no) [Y|n]" Choice + [ "$?" = '0' ] && { + [[ "$Choice" == [YyJj]* ]] || [ -z "$Choice" ] && return 0 + } + return 1 +} +check_envvar() { # allow only chars in string $1 that can be exspected in environment variables + # Allows only chars in "a-zA-Z0-9_:/.,@=-" + # Option -w allows whitespace, too. Can be needed for PATH. + # Char * as in LS_COLORS is not allowed to avoid abuse. + # Replaces forbidden chars with X and returns 1 + # Returns 0 if no change occured. + # Echoes result. + local Newvar Space= + + case "${1:-}" in + -w) Space=" " ; shift ;; + esac + + Newvar="$(printf %s "${1:-}" | LC_ALL=C tr -c "a-zA-Z0-9_:/.,@=${Space}-" "X" )" + + printf %s "$Newvar" + printf "\n" + + [ "$Newvar" = "${1:-}" ] && return 0 + + debugnote "check_envvar(): Input string has been changed. Result: + $Newvar" + return 1 +} +check_parent_sshd() { # check whether pid $1 runs in SSH session + local Wanted_pid="${1:-}" Process_line + local Return + ps -p 1 >/dev/null 2>&1 || { + debugnote "check_parent_sshd(): Failed to check for sshd. ps -p not supported." + return 1 + } + while [ $Wanted_pid -ne 1 ] ; do + Process_line="$(ps -f -p "$Wanted_pid"| tail -n1)" + Wanted_pid="$(echo $Process_line| awk '{print $3}')" + [[ $Process_line =~ sshd ]] && Return=0 + [ "$Return" ] && break + done + return ${Return:-1} +} +download() { # download file at URL $1 and store it in file $2 + # Uses wget or curl. If both are missing, returns 1. + # With no arguments it checks for curl/wget without downloading. + # Download follows redirects. + local Downloader= + command -v wget >/dev/null && Downloader="wget" + command -v curl >/dev/null && Downloader="curl" + [ "$Downloader" ] || return 1 + [ "${1:-}" ] || return 0 + case $Downloader in + wget) wget "${1:-}" -O "${2:-}" ;; + curl) curl -L "${1:-}" --output "${2:-}" ;; + esac +} +escapestring() { # escape special chars of $1 + # escape all characters except those described in [^a-zA-Z0-9,._+@=:/-] + echo "${1:-}" | LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@=:/-]/\\&/g; ' +} +getrandomnumber() { # get random number + # chosen by fair dice roll + # guaranteed to be random + echo "4" +} +isnum() { # check if $1 is a number + [ "1" = "$(awk -v a="${1:-}" 'BEGIN {print (a == a + 0)}')" ] +} +makecookie() { # bake a cookie + mcookie 2>/dev/null || echo $RANDOM$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM | cut -b1-32 +} +mysleep() { # catch cases where sleep only supports integer + sleep "${1:-1}" 2>/dev/null || sleep 1 +} +storeinfo() { # store some information for later use + # store and provide pieces of information + # replace entry if codeword is already present + # Store as codeword=string: + # $1 codeword=string + # Dump stored string: + # $1 dump + # #2 codeword + # Drop stored string: + # $1 drop + # #2 codeword + # Test for codeword: (return 1 if not found) + # $1 test + # $2 codeword + # + # note: sed -i causes file permission issues if called in container in Cygwin, compare ticket #187 + # chmod 666 for $Sharefolder could probably fix that. (FIXME) + # + [ -e "$Storeinfofile" ] || return 1 + case "${1:-}" in + dump) grep "^${2:-}=" $Storeinfofile | sed "s/^${2:-}=//" ;; # dump entry + drop) sed -i "/^${2:-}=/d" $Storeinfofile ;; # drop entry + test) grep -q "^${2:-}=" $Storeinfofile ;; # test for entry + *) # store entry + debugnote "storeinfo(): ${1:-}" + grep -q "^$(echo "${1:-}" | cut -d= -f1)=" $Storeinfofile && { + sed -i "/^$(echo "${1:-}" | cut -d= -f1)=/d" $Storeinfofile # drop possible old entry + } + echo "${1:-}" >> $Storeinfofile + ;; + esac +} +rmcr() { # remove carriage return to translate DOS/Windows newlines into UNIX newlines + # convert stdin if $1 is empty. Otherwise convert file $1. + case "${1:-}" in + "") sed "s/$(printf "\r")//g" ;; + *) sed -i "s/$(printf "\r")//g" "${1:-}" ;; + esac +} +timestamp() { # print HH:MM:SS,NNN + date +%T,%N | cut -c1-12 +} +unspecialstring() { # replace special chars of $1 with - + # Replace all characters except those described in "a-zA-Z0-9_" with a '-'. + # Replace newlines, too. + # Remove leading and trailing '-' + # Avoid double '--' + # Return empty string if only special chars are given. + printf %s "${1:-}" | LC_ALL=C tr -cs "a-zA-Z0-9_" "-" | sed -e 's/^-// ; s/-$//' +} +verlt() { # version number check $1 less than $2 + [ "${1:-}" = "${2:-}" ] && return 1 || { verlte "${1:-}" "${2:-}" && return 0 || return 1 ; } +} +verlte() { # version number check $1 less than or equal $2 + [ "${1:-}" = "$(echo -e "${1:-}\n${2:-}" | sort -V | head -n1)" ] && return 0 || return 1 +} +wincmd() { # execute a command on MS Windows with cmd.exe + MSYS2_ARG_CONV_EXCL='*' cmd.exe /C "${@//&/^&}" | rmcr +} + +#### file routines +convertpath() { # convert unix and windows pathes + # $1: Mode: + # windows echo Windows path - result: c:/path + # unix echo unix path - result: /c/path + # subsystem echo path within subsystem - result: /cygdrive/c/path or /path or /mnt/c/path + # volume echo --volume compatible syntax - result: 'unixpath':'containerpath':rw (or ":ro") + # container echo path of volume in container - result: /path + # share echo path of $Sharefolder/file in container - result: /containerpath + # $2: Path to convert. Arbitrary syntax, can be C:/path, /c/path, /cygdrive/c/path, /path + # Can have suffix :rw or :ro. If none is given, return with :rw + # $3: Optional for mode volume: containerpath + + local Mode Path Drive= Readwritemode + + Mode="${1:-}" + Path="${2:-}" + + # check path for suffix :rw or :ro + Readwritemode="$(echo "$Path" | rev | cut -c1-3 | rev)" + [ "$(cut -c1 <<< "$Readwritemode")" = ":" ] && { + Path="$(echo "$Path" | rev | cut -c4- | rev)" + } || Readwritemode=":rw" + + # replace ~ with HOME + Path="$(sed s%"~"%"${Hostuserhome:-${HOME:-}}"% <<< "$Path")" + + # share: Replace $Sharefolder with $Sharefoldercontainer + [ "$Mode" = "share" ] && { + [ -z "$Path" ] && echo "" && return 0 + case $X11dockermode in + run) echo "${Sharefoldercontainer}${Path#$Sharefolder}" ;; + exe) echo "$Path" ;; + esac + return 0 + } + + # replace \ with / + Path="$(tr '\\' '/' <<< "$Path")" + + # remove possible already given mountpoint + Path="${Path#$Winsubmount}" + + # Given format is /c/ + [ "$(cut -c1,3 <<< "$Path")" = "//" ] && { + Drive="$(cut -c2 <<< "$Path")" + Path="$(cut -c3- <<< "$Path")" + } + + # Given format is C:/ + [ "$(cut -c2 <<< "$Path")" = ":" ] && { + Drive="$(cut -c1 <<< "$Path")" + Path="$(cut -c3- <<< "$Path")" + } + + # change C to c + Drive="${Drive,}" + + # docker volume + [ "${Path:0:1}" = "/" ] || { + case $Mode in + unix|subsystem|windows) echo "$Path" ; debugnote "convertpath() $Mode: Docker volumes do not have a specified path on host: $Path" ;; + volume) echo "'$Path':'${3:-/$Path}'$Readwritemode" ;; + container) echo "${3:-/$Path}" ;; + esac + return 0 + } + + # not on Windows + [ -z "$Winsubsystem" ] && { + case $Mode in + unix|subsystem) echo "$Path" ;; + windows) warning "convertpath(): Nonsense path conversion $Mode: $Path" ; return 1 ;; + volume) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; + container) echo "${3:-$Path}" ;; + esac + return 0 + } + + case $Winsubsystem in + WSL1) + [ -z "$Drive" ] && case $Mode in + windows|unix|volume) + debugnote "convertpath(): Request of WSL path: $Path" + grep -q "$Cachefolder" <<< "$Path" || { + [ "$Readwritemode" = ":rw" ] && warning "Request of Windows path to path within WSL: + $Path + Write access from Windows host to WSL files can damage the WSL file system. + Read-only access is ok. + Option --share: You can add :ro to the path to allow read-only access. + Example: --share='$Path:ro'" + } + ;; + esac + ;; + esac + + case $Drive in + "") # Path points into subsystem + Path="${Path#"$Winsubpath"}" + Drive="$(cut -c2 <<<"$Winsubpath")" + case $Mode in + windows) echo "${Drive^}:$(cut -c3- <<<$Winsubpath)$Path" ;; + unix) echo "$Winsubpath$Path" ;; + subsystem) echo "$Path" ;; + volume) + case $Mobyvm in + no) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; + yes) echo "'$Winsubpath$Path':'${3:-$Path}'$Readwritemode" ;; + esac + ;; + container) echo "${3:-$Path}" ;; + esac + ;; + *) # Path outside of subsystem + case $Mode in + windows) echo "${Drive^}:$Path" ;; + unix) echo "/$Drive$Path" ;; + subsystem) echo "$Winsubmount/$Drive$Path" ;; + volume) echo "'/$Drive$Path':'${3:-/$Drive$Path}'$Readwritemode" ;; + container) echo "${3:-/$Drive$Path}" ;; + esac + ;; + esac + + return 0 +} +getwslpath() { # get path to currently running WSL system + + # Fork from https://github.com/Microsoft/WSL/issues/2578#issuecomment-354010141 + + local RUN_ID= BASE_PATH= + + RUN_ID="/tmp/$(makecookie)" + + # Mark our filesystem with a temporary file having an unique name. + touch "${RUN_ID}" + + powershell.exe -Command '(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath.replace(":", "").replace("\", "/")' | while IFS= read -r BASEPATH; do + # Remove trailing whitespaces. + BASEPATH="${BASEPATH%"${BASEPATH##*[![:space:]]}"}" + # Build the path on WSL. + BASEPATH="/mnt/${BASEPATH,}/rootfs" + + # Current WSL instance doesn't have an access to its mount from within + # itself despite all others are available. That's the hacky way we're + # using to determine current instance. + # + # The second of part of the condition is a fallback for a case if our + # trick will stop working. For that we've created a temporary file with + # an unique name and now seeking it among all WLSs. + if ! ls "${BASEPATH}" > /dev/null 2>&1 || [ -f "${BASEPATH}${RUN_ID}" ]; then + echo "${BASEPATH}" + # You can create and simultaneously run multiple WSL instances, comment + # out the "break", run this script within each one and it'll return only + # single value. + break + fi + done + rm "${RUN_ID}" + return 0 +} +mkfile() { # create file $1 owned by $Hostuser + :> "${1:-}" || return 1 + chown $Hostuser "${1:-}" || return 1 + chgrp $Hostusergid "${1:-}" || return 1 + chmod 644 "${1:-}" || return 1 + [ -n "${2:-}" ] && { chmod ${2:-} "${1:-}" || return 1 ; } + return 0 +} +myrealpath() { # real path of possible symlink + command -v realpath >/dev/null && { + realpath "$@" + } || { + [ -h "$@" ] && warning "Could not check for symbolic links. + Please install 'realpath' (package 'coreutils'), + or provide real file path instead of symbolic link path. + Possible symbolic link: $@" + echo "$@" ### FIXME: Maybe workaround with ls + return 1 + } +} +waitforlogentry() { # wait for entry $3 in logfile $2 of application $1 + # $1 is the application we are waiting for to be ready + # $2 points to logfile + # $3 keyword to wait for + # $4 possible error keywords + # $5 time to wait in seconds or infinity. default: 60 + + local Startzeit Uhrzeit Dauer Count=0 Schlaf + local Errorkeys="${4:-}" + local Warten="${5:-60}" + local Error= + + Startzeit="$(date +%s ||:)" + Startzeit="${Startzeit:-0}" + [ "$Warten" = "infinity" ] && Warten=32000 + + debugnote "waitforlogentry(): ${1:-}: Waiting for logentry \"${3:-}\" in $(basename ${2:-})" + + while ! grep -q "${3:-}" <"${2:-}" ; do + Count="$(( $Count + 1 ))" + Uhrzeit="$(date +%s ||:)" + Uhrzeit="${Uhrzeit:-0}" + Dauer="$(( $Uhrzeit - $Startzeit ))" + Schlaf="$(( $Count / 10 ))" + [ "$Schlaf" = "0" ] && Schlaf="0.5" + mysleep "$Schlaf" + + [ "$Dauer" -gt "10" ] && debugnote "waitforlogentry(): ${1:-}: Waiting since ${Dauer}s for log entry \"${3:-}\" in $(basename ${2:-})" + + [ "$Dauer" -gt "$Warten" ] && error "waitforlogentry(): ${1:-}: Timeout waiting for entry \"${3:-}\" in $(basename ${2:-}) + Last lines of $(basename ${2:-}): +$(tail "${2:-}")" + +# grep -i -q -E 'xinit: giving up|unable to connect to X server|Connection refused|server error|Only console users are allowed|Failed to process Wayland|failed to create display|] fatal:' <"${2:-}" && \ + [ "$Errorkeys" ] && grep -i -q -E "$Errorkeys" <"${2:-}" && \ + error "waitforlogentry(): ${1:-}: Found error message in logfile. + Last lines of logfile $(basename ${2:-}): +$(tail "${2:-}")" + + rocknroll || { + debugnote "waitforlogentry(): ${1:-}: Stopped waiting for ${3:-} in $(basename ${2:-}) due to terminating signal." + Error=1 + break + } + done + [ "$Error" ] && return 1 + + debugnote "waitforlogentry(): ${1:-}: Found log entry \"${3:-}\" in $(basename ${2:-})." + return 0 +} +writeaccess() { # check if useruid $1 has write access to folder $2 + local dirVals= gMember= IFS= + IFS=$'\t' read -a dirVals < <(stat -Lc "%U %G %A" "${2:-}") + [ "$(id -u $dirVals)" == "${1:-}" ] && [ "${dirVals[2]:2:1}" == "w" ] && return 0 + [ "${dirVals[2]:8:1}" == "w" ] && return 0 + [ "${dirVals[2]:5:1}" == "w" ] && { + gMember="$(groups ${1:-} 2>/dev/null)" + [[ "${gMember[*]:2}" =~ ^(.* |)${dirVals[1]}( .*|)$ ]] && return 0 + } + [ "w" = "$(getfacl -pn "${2:-}" | grep user:${1:-}: | rev | cut -c2)" ] && return 0 || return 1 +} + +#### special jobs of x11docker (not running X or docker) +cleanup() { # --cleanup : check for non-removed containers and left cache files + # Cleanes x11docker cache and removes running and stopped x11docker containers. + # Does not change --home folders. + local Orphanedcontainers= Orphanedfolders= Line= + + note "x11docker will check for orphaned containers from earlier sessions. + This can happen if docker was not closed successfully. + x11docker will look for those containers and will clean up x11docker cache. + Caution: any currently running x11docker sessions will be terminated, too." + + cd $Cachebasefolder || error "Could not cd to cache folder '$Cachebasefolder'." + + grep -q .cache/x11docker <<<$Cachebasefolder && Orphanedfolders="$(find "$Cachebasefolder" -mindepth 1 -maxdepth 1 -type d | sed s%$Cachebasefolder/%% | grep -w -v x11docker-gui)" + # e X11DOCKER_LASTCLEANFOLDER may be set by x11docker-gui to spare its cache folder. + [ "${X11DOCKER_LASTCLEANFOLDER:-}" ] && Orphanedfolders="$(echo "$Orphanedfolders" | grep -v $X11DOCKER_LASTCLEANFOLDER)" + Orphanedcontainers="$($Dockerexe ps -a --filter name=x11docker_X | grep -v NAMES | rev | cut -d' ' -f1 | rev)" + Orphanedcontainers="$Orphanedcontainers $(find "$Cachebasefolder" -mindepth 2 -maxdepth 2 -type f -name 'container.id' -exec cat {} \;)" + Orphanedcontainers="$(env IFS='' echo $Orphanedcontainers)" + + # check for double entrys name/id, check for already non-existing containers + for Line in $Orphanedcontainers; do + $Dockerexe inspect $Line -f '{{.Id}}' >/dev/null 2>/dev/null && { + echo $Line | grep -q x11docker_X && { + $Dockerexe inspect $Line -f '{{.Id}}' + Line="$($Dockerexe inspect $Line -f '{{.Id}}')" + Orphanedcontainers="$(sed s/$Line// <<< $Orphanedcontainers)" + } ||: + } || Orphanedcontainers="$(sed s/$Line// <<< $Orphanedcontainers)" + done + + [ -z "$Orphanedcontainers$Orphanedfolders" ] && { + note "No orphaned containers or cache files found. good luck!" + } || { + note "Found orphaned containers: +$Orphanedcontainers" + note "Found orphaned folders in $Cachebasefolder: +$Orphanedfolders" + + for Line in $Orphanedfolders ; do + [ -d "$Cachebasefolder/$Line/share" ] && [ ! -s "$Cachebasefolder/$Line/share/timetosaygoodbye" ] && { + note "Found possibly active container for cache dir $Line. + Will summon it to terminate itself." + echo timetosaygoodbye >> "$Cachebasefolder/$Line/share/timetosaygoodbye" + } + done + [ -n "$Orphanedfolders" ] && sleep 3 + + [ -n "$Orphanedcontainers" ] && { + note "Removing containers with: $Dockerexe rm -f $Orphanedcontainers" + bash -c "$Dockerexe rm -f $Orphanedcontainers" 2>&1 + } + [ -n "$Orphanedfolders" ] && { + note "Removing cache files with: rm -R -f $Orphanedfolders" + rm -R -f $Orphanedfolders 2>&1 + } + } + + [ "${X11DOCKER_LASTCLEANFOLDER:-}" ] && { + echo timetosaygoodbye >>$X11DOCKER_LASTCLEANFOLDER/share/timetosaygoodbye + echo timetosaygoodbye >>$X11DOCKER_LASTCLEANFOLDER/share/timetosaygoodbye.fifo + sleep 2 + } + + Logfile= + + note "Removing remaining files with: rm -Rf -v $Cachebasefolder/*" + rm -Rf -v $Cachebasefolder/* + + note "Removing cache base folder with: rmdir -v $Cachebasefolder" + cd + [ "$(basename $Cachebasefolder)" = x11docker ] && rmdir -v $Cachebasefolder || warning "Did not succeed in removing cache folder + $Cachebasefolder + Please run 'x11docker --cleanup' as root." + + $Dockerexe info >/dev/null 2>/dev/null || warning "Could not check for docker images. + Please run 'x11docker --cleanup' as root + to make sure that no orphaned containers are left." + + note "Cleanup ready." +} +create_launcher() { # --launcher: create application launcher on desktop + local Name= + + command -v xdg-desktop-icon >/dev/null || error "Command 'xdg-desktop-icon' not found. + x11docker needs it to place the new icon on your desktop. + Please install xdg-utils" + + note "Will create a new application launcher icon on your desktop. + If you move the new file to: + + $Hostuserhome/.local/share/applications + + it will appear in your applications menu." + + Name="$Codename" + [ "$Codename" = "xonly" ] && Name="$(echo $Xserver | tr -d '-')" + Name="${Name% }" + + read -re -p "Please choose a name for your application launcher: +" -i "$Name" Name + [ -z "$Name" ] && return 1 ### FIXME: check for valid file name / invalid chars? + + Parsedoptions_global="${Parsedoptions_global//--launcher/}" + Parsedoptions_global="${Parsedoptions_global//--starter/}" + mkfile "$Cachefolder/$Name.desktop" + { + echo "#!/usr/bin/xdg-open +[Desktop Entry] +# x11docker desktop file +Type=Application +Name=$Name +Exec=x11docker $Parsedoptions_global +Icon=x11docker +Comment= +Categories=System +Keywords=docker x11docker $(echo $Name | tr -c '[:alpha:][:digit:][:blank:]' ' ' ) +" + case $(command -v x11docker) in + "")echo "TryExec=$0 $Parsedoptions_global" ;; + *) echo "TryExec=x11docker $Parsedoptions_global" ;; + esac + } >> "$Cachefolder/$Name.desktop" + + unpriv "xdg-desktop-icon install --novendor '$Cachefolder/$Name.desktop'" +} +installer() { # --install, --update, --update-master, --remove: Installer for x11docker + # --install: + # - copies x11docker and x11docker-gui to /usr/bin + # - installs icon in /usr/share/icons + # - creates x11docker.desktop file in /usr/share/applications + # --update: + # - download and install latest release from github + # --update-master: + # - download and install latest master version from github + # --remove + # - remove installed files + local Key1= Key2= Oldversion= Newversion= Format= + export PATH="${PATH:-}:/usr/local/bin" # avoid bug on opensuse where root does not have this in $PATH. Will become obsolete as new default is /usr/bin + + # Prepairing + case ${1:-} in + --install) + [ -f "./x11docker" ] || { error "File x11docker not found in current folder. + Try 'x11docker --update' instead." ; } + command -v x11docker > /dev/null && { warning "x11docker seems to be installed already. + Will overwrite existing installation. + Consider to use option '--update' or '--update-master' instead." ; } + ;; + --update|--update-master) + grep -q x11docker <<< "$0" && { + Oldversion="$($0 --version)" + note "Current installed version: x11docker $Oldversion" + } || { + Oldversion="" + } + + [ -d /tmp/x11docker-install ] && rm -R /tmp/x11docker-install + mkdir -p /tmp/x11docker-install && cd /tmp/x11docker-install || error "Could not create or cd to /tmp/x11docker-install." + download || error "Neither wget nor curl found. Need 'wget' or 'curl'for download. + Please install wget or curl." + command -v unzip >/dev/null && Format="zip" + command -v tar >/dev/null && Format="tar.gz" + [ "$Format" ] || error "Cannot extract archive. Please install 'unzip' or 'tar'." + + case ${1:-} in + --update-master) + note "Downloading latest x11docker master version from github." + download "https://codeload.github.com/mviereck/x11docker/$Format/master" "x11docker-update.$Format" || error "Failed to download x11docker from github." + ;; + --update) + download "https://raw.githubusercontent.com/mviereck/x11docker/master/CHANGELOG.md" "CHANGELOG.md" || error "Failed to download CHANGELOG.md from github." + Releaseversion="v$(cat CHANGELOG.md | grep "## \[" | grep -v 'Unreleased' | head -n1 | cut -d[ -f2 | cut -d] -f1)" + note "Downloading latest x11docker release $Releaseversion from github." + download "https://codeload.github.com/mviereck/x11docker/$Format/$Releaseversion" "x11docker-update.$Format" || error "Failed to download x11docker from github." + ;; + esac + + note "Extracting $Format archive." + case $Format in + zip) unzip "x11docker-update.$Format" ;; + tar.gz) tar xzf "x11docker-update.$Format" ;; + esac || error "Failed to extract $Format archive." + echo "" + cd /tmp/x11docker-install/$(ls -l | grep drwx | rev | cut -d' ' -f1 | rev) || error "Could not cd to /tmp/x11docker-update/$(ls -l | grep drwx | rev | cut -d' ' -f1 | rev)" + ;; + esac + + # Doing + case ${1:-} in + --install|--update|--update-master) + note "Installing x11docker and x11docker-gui in /usr/bin" + [ -e /usr/local/bin/x11docker ] && rm -v /usr/local/bin/x11docker + [ -e /usr/local/bin/x11docker-gui ] && rm -v /usr/local/bin/x11docker-gui + cp x11docker /usr/bin/ || error "Could not copy x11docker to /usr/bin" + chmod 755 /usr/bin/x11docker || error "Could not set executeable bit on x11docker" + cp x11docker-gui /usr/bin/ && chmod 755 /usr/bin/x11docker-gui || warning "x11docker-gui not found" + + note "Installing icon for x11docker with xdg-icon-resource" + xdg-icon-resource install --context apps --novendor --mode system --size 64 "$(pwd)/x11docker.png" x11docker || warning "Could not install icon for x11docker. + Is 'xdg-icon-resource' (xdg-utils) installed on your system?" + xdg-icon-resource uninstall --size 72 x11docker ||: # deprecated icon size, may still be present. + + note "Creating application entry for x11docker." + [ -e "/usr/bin/x11docker-gui" ] && { + echo "[Desktop Entry] + Version=1.0 + Type=Application + Name=x11docker + Comment=Run GUI applications in docker images + Exec=x11docker-gui + Icon=x11docker + Categories=System +" > /usr/share/applications/x11docker.desktop + } || note "Did not create desktop entry for x11docker-gui" + command -v kaptain >/dev/null || note "Could not find 'kaptain' for x11docker-gui. + Consider to install 'kaptain' (version 0.73 or higher). + It's useful for x11docker-gui only, though. x11docker itself doesn't need it. + If your distributions does not provide kaptain, look at kaptain repository: + https://github.com/mviereck/kaptain + Fallback: x11docker-gui will try to use image x11docker/kaptain." + + note "Storing README.md, CHANGELOG.md and LICENSE.txt in + /usr/share/doc/x11docker" + mkdir -p /usr/share/doc/x11docker && { + cp README.md /usr/share/doc/x11docker/ + cp CHANGELOG.md /usr/share/doc/x11docker/ + cp LICENSE.txt /usr/share/doc/x11docker/ + } || note "Error while creating /usr/share/doc/x11docker" + + Newversion="$(/usr/bin/x11docker --version)" + note "Installed x11docker version $Newversion" + ;; + --remove) + note "Removing x11docker from your system." + cleanup + [ -x /usr/local/bin/x11docker ] && { # from older installations. /usr/bin is default now as /usr/local/bin can miss in $PATH for root + rm -v /usr/local/bin/x11docker + rm -v /usr/local/bin/x11docker-gui + } + [ -x /usr/bin/x11docker ] && { + rm -v /usr/bin/x11docker + rm -v /usr/bin/x11docker-gui + } + [ -e "/usr/share/applications/x11docker.desktop" ] && rm -v /usr/share/applications/x11docker.desktop + [ -e "/usr/share/doc/x11docker" ] && rm -R -v /usr/share/doc/x11docker + [ -e "/usr/share/icons/x11docker.png" ] && rm /usr/share/icons/x11docker.png + xdg-icon-resource uninstall --size 64 x11docker ||: + xdg-icon-resource uninstall --size 72 x11docker ||: # deprecated icon size, may still be present. + note "Will not remove files in your home folder. + There may be files left in \$HOME/.local/share/x11docker + The symbolic link \$HOME/x11docker may exist, too. + The cache folder \$HOME/.cache/x11docker should be removed already." + ;; + esac + + # Cleanup + case ${1:-} in + --update|--update-master) + note "Removing downloaded temporary files." + cd ~ + rm -R /tmp/x11docker-install + ;; + esac + + # Changelog excerpt + case ${1:-} in + --update) + echo "$Oldversion" | grep -q beta && { + warning "You are switching from master branch to stable releases. + To get latest master beta version, use option --update-master instead" + Key1="\[${Newversion}\]" + Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" + } || { + Key1="\[${Newversion}\]" + Key2="\[${Oldversion}\]" + [ "$Newversion" = "$Oldversion" ] && { + Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" + note "Version $Newversion was already installed before this update. + If you want the latest beta version from master branch, use --update-master." + } + [ -z "$Oldversion" ] && Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" + } + ;; + --update-master) + echo "$Oldversion" | grep -q beta && { + Key1="\[Unreleased\]" + Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" + } || { + Key1="\[Unreleased\]" + Key2="\[${Oldversion}\]" + [ -z "$Oldversion" ] && Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" + } + ;; + esac + case ${1:-} in + --update|--update-master) + note "Excerpt of x11docker changelog: +$(sed -n '/'$Key1'/,/'$Key2'/p' /usr/share/doc/x11docker/CHANGELOG.md | head -n-1)" + ;; + esac + note "Ready." +} + +#### features +check_windowmanager() { # option --wm: search a host window manager + # check --wm arguments, adjust mode + case "$Windowmanagermode" in + ""|none) + Windowmanagermode="none" + return 0 + ;; + auto) + case $X11dockermode in + exe) Windowmanagermode="host" ;; + run) Windowmanagermode="container" ;; + esac + [ -n "$Windowmanagercommand" ] && { + command -v "$(cut -d' ' -f1 <<< "$Windowmanagercommand")" >/dev/null && { + Hostwindowmanager="$Windowmanagercommand" + Windowmanagermode="host" + } || { + note "Option --wm: Did not find command on host: $Windowmanagercommand + If a docker image with this name exists, x11docker will run it." + Windowmanagermode="container" + } + } + ;; + host) ;; + container) + [ "$X11dockermode" = "exe" ] && { + note "Option --wm: With option --exe + x11docker does not support a containerized window manager yet. + Fallback: Setting --wm=host" + check_fallback + Windowmanagermode="host" + } + ;; + esac + + # Find a host window manager + [ "$Hostwindowmanager" ] || for Hostwindowmanager in $Wm_all WM_NOT_FOUND; do + command -v "$Hostwindowmanager" >/dev/null && break + done + + # + case "$Windowmanagercommand" in + "") + case "$Windowmanagermode" in + container) + Windowmanagercommand="x11docker/openbox sh -c 'openbox --sm-disable --config-file /etc/x11docker/openbox-nomenu.rc'" + ;; + host) + [ "$Hostwindowmanager" = "WM_NOT_FOUND" ] && { + Hostwindowmanager="" + note "Option --wm: No host window manager found. + Fallback: Setting --wm=none" + check_fallback + Windowmanagermode="none" + } + ;; + esac + ;; + esac + + case "$Windowmanagermode" in ### FIXME warning does not appear if dockerrc runs host wm as a fallback + host) + [ "$Xtest" = "yes" ] && warning "Options --xtest --wm: Did not disable X extension XTEST + for X server $Xserver. + If your host window manager $Hostwindowmanager can start applications + on its own (for example with a context menu), container applications + can abuse this to run and remotely control host applications. + If you provide content of X server $Xserver over network to others, + they may take control over your host system!" + ;; + esac + + # command adjustment for some host window managers + case $(basename "$(cut -d' ' -f1 <<< "$Hostwindowmanager")") in + cinnamon|cinnamon-session) Hostwindowmanager="cinnamon --sm-disable";; + compiz) # if none, create minimal config to have useable window decoration and can move windows + [ -e "$Hostuserhome/.config/compiz-1/compizconfig/Default.ini" ] || { + unpriv "mkdir -p '$Hostuserhome/.config/compiz-1/compizconfig'" + mkfile "$Hostuserhome/.config/compiz-1/compizconfig/Default.ini" + echo '[core] +s0_active_plugins = core;composite;opengl;decor;resize;move; +' >> "$Hostuserhome/.config/compiz-1/compizconfig/Default.ini" + } ;; + enlightenment|e17|e16|e19|e20|e) Hostwindowmanager="enlightenment_start" ;; + matchbox) Hostwindowmanager="matchbox-window-manager" ;; + mate|mate-session) Hostwindowmanager="mate-session -f" ;; + mate-wm) Hostwindowmanager="marco --sm-disable" ;; + openbox) + Hostwindowmanager="openbox --sm-disable" + [ -e "/etc/xdg/openbox/rc.xml" ] && { + cp /etc/xdg/openbox/rc.xml $Sharefolder/openbox-nomenu.rc + sed -i /ShowMenu/d $Sharefolder/openbox-nomenu.rc + sed -i s/NLIMC/NLMC/ $Sharefolder/openbox-nomenu.rc + Hostwindowmanager="$Hostwindowmanager --config-file $Sharefolder/openbox-nomenu.rc" + } + ;; + esac + + verbose "Detected host window manager: ${Hostwindowmanager:-"(none)"}" + return 0 +} +clean_xhost() { # option --clean-xhost: disable xhost policies on host X + [ -z "$Hostdisplay" ] && note "Option --clean-xhost: No host X display found." && return 1 + [ -z "$Hostxauthority" ] && warning "Option --clean-xhost: You host X server does not provide + an authentication cookie in \$XAUTHORITY. + Host applications started after xhost cleanup might fail to start." + echo "Option --clean-xhost:" 2>&1 >> $Xinitlogfile + DISPLAY="$Hostdisplay" XAUTHORITY="$Hostxauthority" disable_xhost 2>&1 >> $Xinitlogfile +} +setup_clipboard() { # option --clipboard: create shareclipboard script + # Clipboard sharing works different depending on the new X server + # - xpra and nxagent have their own clipboard management. + # - Only xpra supports image clips. + # - --hostdisplay accesses the clipboard from host X directly + # - Other X servers: A script is created to synchronize clipboard between X servers. + # - No clipboard support for Wayland yet. + # The script uses xclip or xsel. It is executed in xinitrc. + + local Clipsend= Clipreceive= + + case $Xserver in + --tty|--weston|--hostwayland|--kwin) + warning "Option --clipboard is not supported for $Xserver. + Fallback: Disabling option --clipboard." + check_fallback + Shareclipboard="no" + return 1 + ;; + --nxagent|--xpra|--xpra-xwayland|--xwin) ;; # have their own clipboard management, look at create_xcommand(). + + --xephyr|--xorg|--xdummy|--xdummy-xwayland|--xvfb|--xwayland|--weston-xwayland) + # check for either xclip or xsel + command -v xsel >/dev/null && { + Clipsend="xsel --clipboard --input" + Clipreceive="xsel --clipboard --output" + } + command -v xclip >/dev/null && { + Clipsend="xclip -selection clipboard -in" + Clipreceive="xclip -selection clipboard -out" + } + [ -z "$Clipsend" ] && { + note "Option --clipboard: Need either xclip or xsel + for clipboard sharing with X server $Xserver. + Fallback: Disabling option --clipboard." + check_fallback + Shareclipboard="no" + } + [ -z "$Hostdisplay" ] && { + note "Option --clipboard: No host display DISPLAY found + to share clipboard. Fallback: Disabling option --clipboard" + check_fallback + Shareclipboard="no" + } + + echo "#! /usr/bin/env bash +# share clipboard between X servers $Hostdisplay and $Newdisplay + +$(declare -f mysleep) +$(declare -f rocknroll) +Timetosaygoodbyefile='$Timetosaygoodbyefile' + +while rocknroll ; do + # read content of clipboard of first X server $Hostdisplay + X1clip=\"\$(env DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority $Clipreceive)\" + + # check if clipboard of first X server has changed; if yes, send new content to second X server + [ \"\$Shareclip\" != \"\$X1clip\" ] && { + Shareclip=\"\$X1clip\" + env DISPLAY=$Newdisplay XAUTHORITY=$Xclientcookie $Clipsend <<< \"\$Shareclip\" +# echo \"\$Shareclip\" | env DISPLAY=$Newdisplay XAUTHORITY=$Xclientcookie $Clipsend + } + Shareclip=\"\${Shareclip:-' '}\" # avoid empty string error + mysleep 0.3 # sleep a bit to avoid high cpu usage + + # read content of clipboard of second X server $Newdisplay + X2clip=\"\$(env DISPLAY=$Newdisplay XAUTHORITY=$Xclientcookie $Clipreceive)\" + + # check if clipboard of second X server has changed; if yes, send new content to first X server + [ \"\$Shareclip\" != \"\$X2clip\" ] && { + Shareclip=\"\$X2clip\" + env DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority $Clipsend <<< \"\$Shareclip\" +# echo \"\$Shareclip\" | env DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority $Clipsend + } + Shareclip=\"\${Shareclip:-' '}\" # avoid empty string error + mysleep 0.3 # sleep a bit to avoid high cpu usage +done +" >> $Clipboardrc + ;; + esac + return 0 +} +setup_gpu() { # option --gpu: share /dev/dri and check nvidia driver + # Easiest case: share /dev/dri. + # Works for open source MESA drivers on host and in image. + # Debian packages for MESA drivers in image: libgl1-mesa-dri, libglx-mesa0 + # + # Closed source NVIDIA drivers does not integrate well within linux. + # Instead, free nouveau driver is a better choice, or no NVIDIA hardware at all. + # Posibilities: + # - Install NVIDIA driver in image. It must be the very same version as on your host. + # The image is not portable anymore. + # - x11docker can install NVIDIA driver on the fly in running container. See notes below. + # + # g $Nvidiainstallerfile nvidia driver file to install in container in containerrootrc + # g $Nividaversion nvidia driver version on host + + local Gpudevice + + Containerusergroups="$Containerusergroups video render" + + # check device files + while read -r Gpudevice ; do + store_runoption volume "$Gpudevice" + done < <(find /dev/dri /dev/nvidia* /dev/vga_arbiter /dev/nvhost* /dev/nvmap -maxdepth 0 2>/dev/null ||:) + + [ -z "$Nvidiaversion" ] && return 0 + + # check for closed source nvidia driver on host, provide automated installation, warn about disadvantages + debugnote "NVIDIA: Detected driver version $Nvidiaversion on host." + + [ "$Runtime" = "nvidia" ] && { + debugnote "NVIDIA: Option --runtime=nvidia: Skipping driver installation." + Nvidiainstallerfile="" + return 0 + } + + Nvidiainstallerfile="$(find /usr/local/share/x11docker/NVIDIA*$Nvidiaversion*.run $Hostuserhome/.local/share/x11docker/NVIDIA*$Nvidiaversion*.run 2>/dev/null | tail -n1 )" + Nvidiainstallerfile="$(myrealpath "$Nvidiainstallerfile" 2>/dev/null)" + + [ -e "$Nvidiainstallerfile" ] && { + debugnote "NVIDIA: Found proprietary closed source NVIDIA driver installer + $Nvidiainstallerfile" + return 0 + } + + Nvidiainstallerfile="" + + note "Option --gpu: You are using the closed source NVIDIA driver. + GPU acceleration will only work if you have installed the very same driver + version in image. That makes images less portable. + It is recommended to use free open source nouveau driver on host instead. + Ask NVIDIA corporation to at least publish their closed source API, + or even better to actively support open source driver nouveau." + + note "Option --gpu: x11docker can try to automatically install NVIDIA driver + version $Nvidiaversion in container on every container startup. + Drawbacks: Container startup is a bit slower and its security will be reduced. + + You can look here for a driver installer: + https://www.nvidia.com/Download/index.aspx + https://http.download.nvidia.com/ + A direct download URL is probably: + https://http.download.nvidia.com/XFree86/Linux-x86_64/$Nvidiaversion/NVIDIA-Linux-x86_64-$Nvidiaversion.run + If you got a driver, store it at one of the following locations: + $Hostuserhome/.local/share/x11docker/ + /usr/local/share/x11docker/ + + Be aware that the version number must match exactly the version on host. + The file name must begin with 'NVIDIA', contain the version number $Nvidiaversion + and end with suffix '.run'." + + return 0 +} +setup_hostdbus() { # option --hostdbus: connect to host DBus session daemon. + warning "--hostdbus: Connecting container to host DBus degrades + container isolation. Container applications might send malicious requests." + Dbusrunsession=no + + [ "$DBUS_SESSION_BUS_ADDRESS" ] || { + # no running DBus session? + command -v dbus-launch >/dev/null && { + export $(dbus-launch) + note "Option --hostdbus: DBUS_SESSION_BUS_ADDRESS is empty. + Creating abstract DBus socket with dbus-launch." + } || note "Option --hostdbus: Is DBus running on host? + Did not find an active session and did not find dbus-launch. + DBUS_SESSION_BUS_ADDRESS is empty. + $Wikipackages" + } + + grep -q "unix:path" <<< "$DBUS_SESSION_BUS_ADDRESS" && { + # DBus socket file + store_runoption env "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" + store_runoption volume "/$(cut -d/ -f2- <<<"$DBUS_SESSION_BUS_ADDRESS"):ro" + } + + grep -q "unix:abstract" <<< "$DBUS_SESSION_BUS_ADDRESS" && { + # DBus abstract socket (dbus-launch) + store_runoption env "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" + [ "${DBUS_SESSION_BUS_PID:-}" ] && store_runoption env "${DBUS_SESSION_BUS_PID:-}" + [ "${DBUS_SESSION_BUS_WINDOWID:-}" ] && store_runoption env "${DBUS_SESSION_BUS_WINDOWID:-}" + Network="host" + warning "Option --hostdbus: Did not find a DBus session socket file + but an abstract unix socket. To allow access for container, + x11docker sets option '--network=host'. + This degrades container isolation. Container shares host network stack." + } + return 0 +} +setup_printer() { # option --printer: connect to cups printer server + # Default CUPS setups create a unix socket /run/cups/cups.sock as given from 'lpstat -H'. + # Sharing this socket and pointing environment variable CUPS_SERVER to it serves most cases. + # Possible CUPS network setups need to allow access from container, see note below. + local Cupsserver= + + command -v lpstat >/dev/null || { + warning "Option --printer: command lpstat not found. + Is cups printer server installed on your system? + Error: Cannot share access to printer." + Sharecupsmode="" + return 1 + } + + case $Sharecupsmode in + tcp) Cupsserver="$Hostip:631" ;; + socket) Cupsserver="$(lpstat -H)" ;; + esac + + grep -q ":" <<<$Cupsserver && { + [ "$(cut -d: -f1 <<<$Cupsserver)" = "localhost" ] && Cupsserver="$Hostip:$(cut -d: -f2 <<<$Cupsserver)" + [ "$(cut -d: -f1 <<<$Cupsserver)" = "127.0.0.1" ] && Cupsserver="$Hostip:$(cut -d: -f2 <<<$Cupsserver)" + note "Option --printer: Network setup for CUPS detected. + Server address: $Cupsserver + You may need to allow container access in /etc/cups/cupsd.conf, e.g.: + +Port 631 + + # Allow remote access... + Order allow,deny + Allow 172.17.0.* + Allow 127.0.0.1 +" + } + + [ "$Cupsserver" ] && store_runoption env "CUPS_SERVER=$Cupsserver" + [ -e "$Cupsserver" ] && { + [ "$(dirname $Cupsserver)" = "/run/cups" ] && store_runoption volume "/run/cups" || store_runoption volume "$Cupsserver" + } + + return 0 +} +setup_sound_alsa() { # option --alsa: share sound devices + # Sound with ALSA is directly supported by the kernel and only needs to share devices in /dev/snd. + # libasound2 in image is rcommended. + # The desired sound card can be specified with environment variable ALSA_CARD. See card name in 'aplay -l'. + # Further documentation at https://github.com/mviereck/x11docker/wiki/Container-sound:-ALSA-or-Pulseaudio + + warning "ALSA sound with option --alsa degrades container isolation. + Shares device files in /dev/snd, container gains access to sound hardware. + Container applications can catch audio output and microphone input." + + [ "$Alsacard" ] && store_runoption env "ALSA_CARD=$Alsacard" + + pgrep pulseaudio >/dev/null && note "It seems that pulseaudio is running on your host. + Pulseaudio can interfere with ALSA sound (option --alsa). + Host sound may not work while container is playing sound and vice versa. + Alternative: with pulseaudio on host and in image, use option --pulseaudio." + + [ -d /dev/snd ] && store_runoption volume "/dev/snd" || { + warning "Option --alsa: /dev/snd not found. + Sound support not possible." + Sharealsa="no" + return 1 + } + + [ -s "$Pulseaudioconf" ] || echo "# Connect to host pulseaudio server using mounted UNIX socket +default-server = none +# Prevent a server running in container +autospawn = no +daemon-binary = /bin/true +# Prevent use of shared memory +enable-shm = false +" >> $Pulseaudioconf + + [ "$Sharealsa" = "yes" ] && Containerusergroups="$Containerusergroups audio" + + return 0 +} +setup_sound_pulseaudio() { # option --pulseaudio: set up pulseaudio connection + # Allowing container access to Pulseaudio on host can be done with a shared socket or over TCP. + # Sharing host user socket in XDG_RUNTIME_DIR fails since Pulseaudio v12.0. + # Instead, a new socket is created with pactl. + # TCP module is created after container startup to authenticate it with container IP. + # Detailed documentation at: https://github.com/mviereck/x11docker/wiki/Container-sound:-ALSA-or-Pulseaudio + # + # g $Pulseaudiomode =tcp or =socket: Connect over tcp or with shared socket + # g $Pulseaudioport TCP port + local Lowerport= Upperport= + local Pulseaudiopath + + warning "Option --pulseaudio allows container applications + to catch your audio output and microphone input." + + [ -z "$Pulseaudiomode" ] && Pulseaudiomode="socket" + [ "$Pulseaudiomode" = "auto" ] && { + Pulseaudiomode="socket" + [ "$Containeruser" = "$Hostuser" ] || Pulseaudiomode="tcp" + [ "$Runtime" = "kata-runtime" ] && Pulseaudiomode="tcp" + [ "$Runsinsnap" = "yes" ] && Pulseaudiomode="tcp" + LC_ALL=C pactl info | grep -q "User Name: pulse" && Pulseaudiomode="tcp" + } + + case $Pulseaudiomode in + socket) + # create pulseaudio socket + Pulseaudiomoduleid="$(unpriv "pactl load-module module-native-protocol-unix socket=$Pulseaudiosocket 2>&1")" + [ "$Pulseaudiomoduleid" ] && { + storeinfo "pulseaudiomoduleid=$Pulseaudiomoduleid" + store_runoption env "PULSE_SERVER=unix:$(convertpath share $Pulseaudiosocket)" + store_runoption env "PULSE_COOKIE=$(convertpath share $Pulseaudiocookie)" + } || { + note "Option --pulseaudio: command pactl failed. + Is pulseaudio running at all on your host? + Fallback: Enabling option --alsa" + check_fallback + Pulseaudiomode="" + Sharealsa="yes" + } + + echo "# Connect to host pulseaudio server using mounted UNIX socket +default-server = unix:$(convertpath share $Pulseaudiosocket) +# Prevent a server running in container +autospawn = no +daemon-binary = /bin/true +# Prevent use of shared memory +enable-shm = false +" >> $Pulseaudioconf + verbose "Generated pulseaudio client.conf: +$(nl -ba <$Pulseaudioconf)" + ;; + tcp) + read Lowerport Upperport < /proc/sys/net/ipv4/ip_local_port_range 2>/dev/null + [ "$Lowerport" ] || Lowerport=33000 + [ "$Upperport" ] || Upperport=60000 + while : ; do + Pulseaudioport="$(shuf -i $Lowerport-$Upperport -n1)" + ss -lpn | grep -q ":$Pulseaudioport " || break + done + [ -e "$Hostuserhome/.config/pulse/cookie" ] && cp "$Hostuserhome/.config/pulse/cookie" "$Pulseaudiocookie" || note "Option --pulseaudio: Did not find cookie + $Hostuserhome/.config/pulse/cookie" + store_runoption env "PULSE_SERVER=tcp:$Hostip:$Pulseaudioport" + ;; + esac + return 0 +} +setup_webcam() { # option --webcam: share webcam devices + # Webcam devices appear as /dev/video* files. + # Unprivileged users need to be in group video. + # (This works only if webcam is plugged in before container starts. + # Hotplug support would have to be different.) + local Webcamdevice + + while read -r Webcamdevice ; do + store_runoption volume "$Webcamdevice" + done < <(find /dev/video* -maxdepth 0 2>/dev/null || note "Option --webcam: No webcam devices /dev/video* found. + Webcam in container will fail.") + Containerusergroups="$Containerusergroups video" + + # at least cheese and gnome-ring need some device information from udev. + store_runoption volume "/run/udev/data:ro" +} + +#### X server setup +check_newxenv() { # find free display, create $Newxenv + local Line + # find free display number + [ "$Newdisplaynumber" ] || { + Newdisplaynumber="100" + while :; do + case $Xserver in + --xwin|--runx) Newdisplaynumber="$((RANDOM / 10 + 200))" ;; + *) Newdisplaynumber="$((Newdisplaynumber + 1))" ;; + esac + grep -q -x "$Newdisplaynumber" < "$Numbersinusefile" || [ -n "$(find "/tmp/.X11-unix/X$Newdisplaynumber" "/tmp/.X$Newdisplaynumber-lock" "$XDG_RUNTIME_DIR/wayland-$Newdisplaynumber" 2>/dev/null)" ] || break + done + } + echo "$Newdisplaynumber" >> "$Numbersinusefile" + + # X over IP/TCP + [ "$Xoverip" ] || case $Xserver in + --xwin|--runx) Xoverip="yes" ;; + esac + + # set $Newdisplay (DISPLAY of container) and $Newxsocket + case $Xserver in + --hostdisplay) + case $Xoverip in + yes) + [ "$(cut -c1 <<< "$Hostdisplay")" = ":" ] && Newdisplay="${Hostip}${Hostdisplay}" || Newdisplay="$Hostdisplay" ;; + no|"") + Newdisplay="$Hostdisplay" + Newdisplaynumber="$(echo $Newdisplay | cut -d: -f2 | cut -d. -f1)" + Newxsocket="$Hostxsocket" + ;; + esac + ;; + --weston|--kwin|--hostwayland|--tty) + Newdisplay="" + Newxsocket="" + Xclientcookie="" + Xservercookie="" + ;; + *) + case $Xoverip in + yes) Newdisplay="$Hostip:$Newdisplaynumber" ;; + no|"") + Newdisplay=":$Newdisplaynumber" + Newxsocket="/tmp/.X11-unix/X$Newdisplaynumber" + Newxlock="/tmp/.X$Newdisplaynumber-lock" + [ -n "$(find $Newxsocket $Newxlock 2>/dev/null)" ] && error "Display $Newdisplay is already in use." + ;; + esac + ;; + esac + + # set $Newwaylandsocket + case $Xserver in + --weston|--weston-xwayland|--kwin|--kwin-xwayland|--xpra-xwayland|--xdummy-xwayland) Newwaylandsocket="wayland-$Newdisplaynumber" ;; + --hostwayland|--xwayland) Newwaylandsocket="$Hostwaylandsocket" ;; + esac + + + # create $Newxenv: collection of environment variables to access new X from host (e.g. in xinitrc) + [ "$Newdisplay" ] && storeinfo "DISPLAY=$Newdisplay" && Newxenv="$Newxenv DISPLAY=$Newdisplay" + [ -e "$Xclientcookie" ] && storeinfo "XAUTHORITY=$Xclientcookie" && Newxenv="$Newxenv XAUTHORITY=$Xclientcookie" + [ "$Newxsocket" ] && storeinfo "XSOCKET=$Newxsocket" && Newxenv="$Newxenv XSOCKET=$Newxsocket" + [ "$Newwaylandsocket" ] && storeinfo "WAYLAND_DISPLAY=$Newwaylandsocket" && Newxenv="$Newxenv WAYLAND_DISPLAY=$Newwaylandsocket" + [ "$Setupwayland" = "yes" ] && for Line in $Waylandtoolkitenv ; do Newxenv="$Newxenv $Line" ; done + storeinfo "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" && Newxenv="$Newxenv XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" + storeinfo "Xenv=$Newxenv" + + # X / Wayland environment variables for container + case $Xserver in + --xpra|--xephyr|--xpra-xwayland|--weston-xwayland|--hostdisplay|--xorg|--xdummy|--xvfb|--xdummy-xwayland|--xwayland|--kwin-xwayland|--nxagent|--xwin|--runx) + store_runoption env "DISPLAY=$Newdisplay" + store_runoption env "XAUTHORITY=$(convertpath share $Xclientcookie)" + ;; + --weston|--kwin|--hostwayland|--tty) + store_runoption env "WAYLAND_DISPLAY=$Newwaylandsocket" + ;; + esac + return 0 +} +check_screensize() { # check physical and virtual screen size (also option --size) + local Line= + + [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && verbose "check_screensize(): Skipping check on pure Wayland environment" + + # check whole display size, can include multiple monitors + [ -n "$Hostdisplay" ] && { + command -v xrandr >/dev/null && { + Line="$(xrandr 2>/dev/null | grep current | head -n1 | cut -d, -f2)" + Maxxaxis="$(echo "$Line" | cut -d' ' -f3)" + Maxyaxis="$(echo "$Line" | cut -d' ' -f5)" + } + [ -z "$Maxxaxis" ] && command -v xdpyinfo >/dev/null && { + Line="$(xdpyinfo | grep dimensions)" + Maxxaxis="$(echo "$Line" | cut -dx -f1 | rev | cut -d ' ' -f1 | rev)" + Maxyaxis="$(echo "$Line" | cut -dx -f2 | cut -d ' ' -f1)" + } + [ -z "$Maxxaxis" ] && command -v xwininfo >/dev/null && { + Line="$(xwininfo -root -stats)" + Maxxaxis="$(echo "$Line" | grep Width | rev | cut -d' ' -f1 | rev)" + Maxyaxis="$(echo "$Line" | grep Height | rev | cut -d' ' -f1 | rev)" + } + [ -z "$Maxxaxis" ] && note "check_screensize(): Could not determine your screen size. + Please improve this by installing one of xrandr, xdpyinfo or xwininfo. + Or use option --size=XxY. + $Wikipackages" + } + + case $Xserver in + --xvfb|--xdummy) + [ "$Screensize" ] && { + Maxxaxis="${Screensize%x*}" + Maxyaxis="${Screensize#*x}" + } || { + Maxxaxis=4720 + Maxyaxis=3840 + note "Option $Xserver: Specifying quite big virtual screen size + for $Xserver: ${Maxxaxis}x${Maxyaxis} + This costs some memory, but will fit most possible remote screens. + To save memory, specify needed screen size only with e.g. --size=1980x1200 + Check output of 'xrandr | grep current' on your target display." + } + ;; + esac + + [ -n "$Maxxaxis" ] && { + Xaxis="$Maxxaxis" + Yaxis="$Maxyaxis" + } + + [ "$Fullscreen" = "yes" ] && [ "$Runsonconsole" = "no" ] && [ -n "$Maxxaxis" ] && Screensize="${Maxxaxis}x${Maxyaxis}" + + # size for windowed desktops, roughly maximized relative to primary monitor + case $Xserver in + --xpra|--xpra-xwayland) [ "$Desktopmode" = "yes" ] && Xserver="${Xserver}-desktop" ;; + esac + case $Xserver in + --xephyr|--weston-xwayland|--weston|--kwin|--kwin-xwayland|--nxagent|--xpra-desktop|--xpra-xwayland-desktop) + [ "$Runsonconsole" = "yes" ] && { + : # nothing to do on tty. ### FIXME maybe should check --size=$Screensize + } || { + command -v xrandr > /dev/null && xrandr 2>/dev/null | grep -q ' connected' && { # reduce size to primary monitor for windowed desktop + Xaxis="$(xrandr 2>/dev/null | grep ' connected' | head -n1 | cut -dx -f1 | rev | cut -d' ' -f1 | rev)" + Yaxis="$(xrandr 2>/dev/null | grep ' connected' | head -n1 | cut -dx -f2 | cut -d' ' -f1 | cut -d+ -f1)" + Xaxis="$((Xaxis-96))" + Yaxis="$((Yaxis-96))" + Xaxis="$(( $(( $Xaxis / 8 )) * 8 ))" # avoid grey edge in Xwayland, needs full byte x width + } || { + note "Could not determine size of your primary display to + create a roughly maximized window for $Xserver. + Please install xrandr or use option --size=XxY. + Fallback: setting virtual screen size 800x600 + $Wikipackages" + Xaxis="800" + Yaxis="600" + } + } + ;; + esac + Xserver="${Xserver%-desktop}" + + [ -z "$Xaxis" ] && { ### FIXME: arbitrary resolution. At least, --xorg checks again with xrandr in xinitrc + Xaxis="4720" + Yaxis="3840" + } + + # regard scaling (option --scale) + [ "$Scaling" ] && { + Xaxis="$(awk -v a=$Xaxis -v b=$Scaling 'BEGIN {print (a / b)}')" + Xaxis="${Xaxis%.*}" + Yaxis="$(awk -v a=$Yaxis -v b=$Scaling 'BEGIN {print (a / b)}')" + Yaxis="${Yaxis%.*}" + } + [ -n "$Screensize" ] && { # regard --size, overwriting Xaxis/Yaxis from above + Xaxis="${Screensize%x*}" + Yaxis="${Screensize#*x}" + } + case $Xserver in + --xorg) ;; # Xorg autodetects screen size, preset only with option --size + *) [ "$Runsonconsole" = "no" ] && Screensize="${Xaxis}x${Yaxis}" ;; + esac + [ -z "$Maxxaxis" ] && { + Maxxaxis="$Xaxis" + Maxyaxis="$Yaxis" + } + [ "$Xaxis" -gt "$Maxxaxis" ] && Maxxaxis="$Xaxis" + [ "$Yaxis" -gt "$Maxyaxis" ] && Maxyaxis="$Yaxis" + + command -v cvt >/dev/null && Modeline="$(cvt $Xaxis $Yaxis | tail -n1 | cut -d' ' -f2-)" + Modeline="$(echo $Modeline | cut -d_ -f1)\" $(echo $Modeline | cut -d_ -f2- | cut -d' ' -f2-)" + + verbose "Virtual screen size: $Screensize" + verbose "Physical screen size: + $(xrandr 2>/dev/null | grep Screen ||:)" + + # create set of Modelines if needed + { [ "$Xserver" = "--xpra" ] && [ "$Xpravfb" = "Xvfb" ] && [ "$Desktopmode" = "yes" ] ; } || [ "$Xserver" = "--xvfb" ] && { + Modelinefile="$(create_modelinefile ${Maxxaxis}x${Maxyaxis})" + } + + return 0 +} +check_vt() { # option --xorg: find free vt / tty + local Line= Ttyinuse= + + # if started from console, use current tty + tty -s && [ "$Runsonconsole" = "yes" ] && { + Newxvt="$(tty | rev | cut -d/ -f1 | rev)" + Newxvt="${Newxvt#tty}" + } + + # check ttys currently in use + [ "$Newxvt" ] || { + for Line in $(find /sys/class/vc/vcsa*); do + Ttyinuse="$Ttyinuse ${Line#/sys/class/vc/vcsa} " + done + debugnote "check_vt(): TTYs currently known to kernel: $Ttyinuse" + } + + [ "$Newxvt" ] && grep -q " $Newxvt " <<< "$Ttyinuse" && warning "TTY $Newxvt seems to be already in use." + + # try to find free tty within range of 8..12 + [ "$Newxvt" ] || { + for ((Newxvt=8 ; Newxvt<=12 ; Newxvt++)) ; do + grep -q " $Newxvt " <<< "$Ttyinuse" || break + done + } + + # try to find free tty within range of 1..7 + [ "$Newxvt" ] || { + for ((Newxvt=1 ; Newxvt<=7 ; Newxvt++)) ; do + grep -q " $Newxvt " <<< "$Ttyinuse" || break + done + } + + # try to find free tty with fgconsole. Fails in some cases within X. + [ "$Newxvt" ] || Newxvt="$(fgconsole --next-available 2>/dev/null)" + [ "$Newxvt" ] || Newxvt="$(fgconsole --next-available 2>/dev/null /dev/null 2>/dev/null || note "Could not check for a free tty below or equal to 12. + Would need to use command fgconsole for a better check. + Possibilities: + 1.) Run x11docker as root. + 2.) Add user to group tty (not recommended, may be insecure). + 3.) Use display manager gdm3. + 4.) Run x11docker directly from console." + note "To access X on tty$Newxvt, use command 'chvt $Newxvt'" + } || { + note "New Xorg server $Newdisplay will run on tty $Newxvt. + Access it with [CTRL][ALT][F$Newxvt]." + } + + warning "On debian 9, switching often between multiple X servers can + cause a crash of one X server. This bug may be debian specific and is probably + some sort of race condition. If you know more about this or it occurs on + other systems, too, please report at https://github.com/mviereck/x11docker. + + You can avoid this issue with switching to a black tty before switching to X." + return 0 +} +check_xdepends() { # check dependencies on host for X server option $1 + # Return 1 if something is missing + local Return= Message= + + [ "$Lastcheckedxserver" = "${1:-}" ] && debugnote "Dependencies of ${1:-} already checked: $Lastcheckedxserverresult " && return $Lastcheckedxserverresult + + case $Autochooseserver in + yes) Message="debugnote" ;; + no) Message="note" ;; + esac + case ${1:-} in + --xephyr|--xpra|--nxagent|--xorg|--xvfb|--xdummy|--xwayland|--weston-xwayland|--kwin-xwayland|--xdummy-xwayland) + command -v xinit >/dev/null || { + $Message "${1:-}: xinit not found." + Return=1 + } + ;; + esac + case ${1:-} in + --xpra-xwayland|--weston-xwayland|--xwayland|--weston|--kwin|--kwin-xwayland|--xdummy-xwayland|--hostwayland) + [ "$Nvidiaversion" ] && { + $Message "${1:-}: Closed source NVIDIA driver does not support Wayland." + Return=1 + } + [ "$Runtime" = "kata-runtime" ] && { + $Message "${1:-} not supported with --runtime=kata-runtime" + Return=1 + } + case $Mobyvm in + yes) + $Message "${1:-} not supported with MobyVM / docker-for-win" + Return=1 + ;; + esac + ;; + esac + case ${1:-} in + --xpra|--xpra-xwayland) + command -v "xpra" >/dev/null || { + $Message "${1:-}: xpra not found. + $Wikipackages" + Return=1 + } ;; + --xephyr) + command -v "Xephyr" >/dev/null || command -v "Xnest" >/dev/null || { + $Message "${1:-}: Neither Xephyr nor Xnest found. + $Wikipackages" + Return=1 + } ;; + --nxagent) + command -v "nxagent" >/dev/null || { + $Message "${1:-}: nxagent not found. + $Wikipackages" + Return=1 + } ;; + --xvfb) + command -v "Xvfb" >/dev/null || { + $Message "${1:-}: Xvfb not found. + $Wikipackages" + Return=1 + } ;; + --xorg|--xdummy) + command -v "Xorg" >/dev/null || { + $Message "${1:-}: Xorg not found. + $Wikipackages" + Return=1 + } ;; + --xwin) + case "$Winsubsystem" in + CYGWIN) + command -v XWin >/dev/null || { + $Message "${1:-}: XWin not found. + Need packages 'xinit', 'xauth' and 'xwininfo' in Cygwin (X11 section)." + Return=1 + } + ;; + WSL1|WSL2) + $Message "${1:-}: XWin is available in Cygwin on MS Windows only. + Use runx to provide XWin in WSL: https://github.com/mviereck/runx" + Return=1 + ;; + MSYS2) + $Message "${1:-}: XWin is available in Cygwin on MS Windows only. + With runx XWin is available in WSL, too. + In MSYS2 you can only use runx with VcXsrv to provide an X server: + https://github.com/mviereck/runx" + Return=1 + ;; + "") + $Message "${1:-}: XWin is available in Cygwin on MS Windows only." + Return=1 + ;; + esac + command -v xwininfo >/dev/null || { + $Message "${1:-}: xwininfo not found. + Need 'xwininfo' package from Cygwin/X (X11 section)." + Return=1 + } + [ "$Hostip" ] || { + $Message "${1:-}: Failed to get host IP address." + Return=1 + } + ;; + --runx) + [ "$Winsubsystem" ] || { + $Message "${1:-}: runx is available on MS Windows only." + Return=1 + } + command -v runx >/dev/null || { + $Message "${1:-}: runx not found. + Need runx from https://github.com/mviereck/runx" + Return=1 + } + ;; + esac + case ${1:-} in + --weston|--xpra-xwayland|--weston-xwayland|--xdummy-xwayland) + command -v "weston" >/dev/null || { + $Message "${1:-}: weston not found. + $Wikipackages" + Return=1 + } ;; + --kwin|--kwin-xwayland) + command -v "kwin_wayland" >/dev/null || { + $Message "${1:-}: kwin_wayland not found. + $Wikipackages" + Return=1 + } ;; + esac + case ${1:-} in + --xpra-xwayland|--weston-xwayland|--kwin-xwayland|--xwayland|--xdummy-xwayland) + command -v "Xwayland" >/dev/null || { + $Message "${1:-}: Xwayland not found. + $Wikipackages" + Return=1 + } ;; + esac + case ${1:-} in + --xpra-xwayland|--xdummy-xwayland) + command -v "xdotool" >/dev/null || { + $Message "${1:-}: xdotool not found. + $Wikipackages" + Return=1 + } ;; + esac + case ${1:-} in + --xpra|--xpra-xwayland) + [ "$Return" = "1" ] || { + # check xpra version + [ "$Xpraversion" ] || { + Xpraversion="$(xpra --version 2>/dev/null | cut -d' ' -f2)" + Xprarelease="$(echo $Xpraversion | cut -s -d- -f2)" + verbose "Xpra version: ${Xpraversion:-XPRA_NOT_FOUND}" + [ "$Xprahelp" ] || Xprahelp="$(xpra --help 2>/dev/null)" + } + ! verlte "$Xprarelease" "r18663" && verlte $Xprarelease "r19519" && { + [ "$Sharehostipc" = "no" ] && { + $Message "Your xpra version has a MIT-SHM bug that would force + x11docker to share host IPC namespace. That would reduce container isolation. + Current installed version: xpra $Xpraversion + Please update to at least xpra v2.3.1-19519 or xpra v2.4-r19520, + or downgrade to xpra v2.2.5 or lower, or use another X server option. + If you insist on using current xpra, set insecure option --hostipc. + Fallback: will search for another available X server setup." + Return=1 + } + } + } + ;; + esac + case ${1:-} in + --xpra) + [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && { + verlt $Xprarelease r23305 && { + $Message "Option ${1:-}: xpra on Wayland needs at least xpra v3.0-r23305 + with python3 backend." + Return=1 + } || { + $Message "Option ${1:-}: Support in pure Wayland is experimental + and needs latest xpra v3.x versions with python3 backend. + If issues occure, use --weston-xwayland, --kwin-xwayland or --hostwayland + or use option ${1:-} in an X environment." + } + } || { + [ "$Hostdisplay" ] || { + $Message "${1:-} needs a running X server. DISPLAY is empty. Wayland support is experimental option." + Return=1 + } + } + ;; + --hostdisplay|--xpra-xwayland|--xdummy-xwayland|--xephyr|--nxagent) + [ "$Hostdisplay" ] || { + $Message "${1:-} needs a running X server. DISPLAY is empty." + Return=1 + } + ;; + --hostwayland|--xwayland) + [ "$Hostwaylandsocket" ] || { + $Message "${1:-} needs a running Wayland compositor. WAYLAND_DISPLAY is empty." + Return=1 + } + ;; + esac + [ "$Winsubsystem" ] && { + case ${1:-} in + --tty|--xwin|--runx) ;; + --xpra|--xpra-xwayland) + $Message "${1:-} is not supported on MS Windows." + Return=1 + ;; + *) + [ -z "$Hostdisplay" ] && { + case $Winsubsystem in + Cygwin) $Message "${1:-} needs a running X server. DISPLAY is empty. + Please install packages in Cygwin: xinit xauth xwininfo + or use runx to provide an X server on MS Windows: + https://github.com/mviereck/runx" ;; + MSYS2|WSL1|WSL2) $Message "${1:-} needs a running X server. DISPLAY is empty. + Please use runx to provide an X server on MS Windows: + https://github.com/mviereck/runx" ;; + esac + Return=1 + } + ;; + esac + } + [ "$Xoverip" = "yes" ] && { + case ${1:-} in + --xephyr|--xorg|--nxagent|--xpra|--xvfb|--xdummy|--xwin|--runx|--tty) ;; + --hostdisplay) + [ -n "$(cut -d: -f1 <<< "$Hostdisplay")" ] || { + $Message "${1:-} does not support X over TCP + except if started remotely with 'ssh -X'." + Return=1 + } + ;; + --xwayland|--weston-xwayland|--kwin|--kwin-xwayland|--xpra-xwayland|--xdummy-xwayland|--hostwayland) + $Message "${1:-} does not support X over TCP." + Return=1 + ;; + esac + } + [ "$Sharegpu" = "yes" ] && { + case ${1:-} in + --hostdisplay|--xorg|--xpra-xwayland|--weston-xwayland|--kwin-xwayland|--xdummy-xwayland|--xwayland|--xwin|--runx) ;; + --weston|--kwin|--hostwayland) ;; + *) + $Message "${1:-} does not support hardware acceleration (option --gpu)." + Return=1 + ;; + esac + } + + Return="${Return:-"0"}" + debugnote "Dependency check for ${1:-}: $Return" + + [ "$Return" = "1" ] && { + check_fallback + Autochooseserver="yes" + } + + Lastcheckedxserver="${1:-}" + Lastcheckedxserverresult="$Return" + + return "$Return" +} +check_xpraoption() { # check if xpra option $1 is available + local Option + Option="$(cut -d= -f1 <<< "${1:-}")" + grep -q "noprobe" <<< "${1:-}" && { + grep "OpenGL" <<< "$Xprahelp" | grep -q "probe" && echo "$@" || { + debugnote "Xpra option $@ not supported: $Option" + return 1 + } + return 0 + } + grep -q -- "$Option" <<< "$Xprahelp" && echo "$@" || { + debugnote "Xpra option not found: $Option" + return 1 + } +} +check_xserver() { # check chosen X server, auto-choose X server + + ## default option '--auto': Try to automatically choose best matching and available X server + [ "$Autochooseserver" = "yes" ] && { Xserver="--xpra" + [ "$Sharegpu" = "yes" ] && Xserver="--xpra-xwayland" + [ "$Xfishtank" = "yes" ] && Xserver="--xephyr" + [ "$Desktopmode" = "yes" ] && Xserver="--xephyr" + [ "$Xserver" = "--xephyr" ] && { check_xdepends --xephyr || Xserver="--weston-xwayland" ; } ### FIXME: don't use check_xdepends() here + [ "$Sharegpu" = "yes" ] && [ "$Xserver" = "--xephyr" ] && Xserver="--weston-xwayland" + [ "$Outputcount" != "1" ] && Xserver="--weston-xwayland" + [ -n "$Rotation" ] && Xserver="--weston-xwayland" + [ "$Scaling" ] && [ "$Sharegpu" = "yes" ] && Xserver="--xpra-xwayland" + [ "$Scaling" ] && [ "$Sharegpu" = "no" ] && Xserver="--xpra" + [ "$Runsonconsole" = "yes" ] && Xserver="--xorg" + [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && Xserver="--xpra" + [ "$Winsubsystem" ] && Xserver="--runx" + [ "$Winsubsystem" = "CYGWIN" ] && Xserver="--xwin" + [ "$Setupwayland" = "yes" ] && { [ -n "$Hostwaylandsocket" ] && [ "$Desktopmode" = "no" ] && Xserver="--hostwayland" || Xserver="--weston" ; } + } + + [ "$Sharegpu" = "yes" ] && { + [ "$Runtime" = "kata-runtime" ] && { + note "Option --gpu: Hardware acceleration with --runtime=kata-runtime + is not supported. Fallback: Disabling option --gpu." + check_fallback + Sharegpu="no" + } + case $Xserver in + --xpra) + note "Option --xpra does not support GPU access. + Fallback: Will try to use option --xpra-xwayland." + check_fallback + Xserver="--xpra-xwayland" + ;; + --xephyr) + note "Option --xephyr does not support GPU access. + Fallback: Will try to use option --weston-xwayland." + check_fallback + Xserver="--weston-xwayland" + ;; + --nxagent) + case "$Desktopmode" in + yes) Xserver="--weston-xwayland" ;; + no) Xserver="--xpra-xwayland" ;; + esac + note "Option --nxagent does not support GPU access. + Fallback: Will try to use option $Xserver." + check_fallback + ;; + --xdummy|--xvfb) + note "Using special setup with Weston, Xwayland and xdotool + instead of Xdummy or Xvfb to allow GPU access." + Xserver="--xdummy-xwayland" + ;; + esac + } + + grep -q -i "GNOME" <<< "$XDG_CURRENT_DESKTOP" && { + Gnomeversion="$(gnome-shell --version)" + [ "$Gnomeversion" ] && verlt "$Gnomeversion" "GNOME Shell 3.38" && { + case $Xserver in + --hostdisplay) ;; + *) + warning "You are running GNOME desktop in outdated version + $Gnomeversion + This might cause issues with host applications if using additional X servers. + It is recommended to use another desktop environment or GNOME >= 3.38. + Only otherwise discouraged option --hostdisplay might work as expected." + case $Autochooseserver in + yes) Xserver="--hostdisplay" ;; + esac + ;; + esac + } + } + + # X over TCP + [ -z "$Xoverip" ] && { + [ "$Runtime" = "kata-runtime" ] && Xoverip="yes" + [ "$Runsinsnap" = "yes" ] && Xoverip="yes" + case $Mobyvm in + yes) Xoverip="yes" ;; + esac + [ "$Xoverip" = "yes" ] && [ "$Autochooseserver" = "no" ] && note "Enabled X over TCP instead of sharing unix socket." + } + + [ "$Nvidiaversion" ] && [ "$Sharegpu" = "yes" ] && case $Xserver in + --xpra-xwayland|--weston-xwayland|--xwayland|--weston|--kwin|--kwin-xwayland|--xdummy-xwayland|--hostwayland) + note "Your system uses closed source NVIDIA driver. + GPU support will work only with options --hostdisplay and --xorg. + Consider to use free open source nouveau driver instead." + ;; + esac + + [ "$Runsonconsole" = "no" ] && [ "$Runsoverssh" = "no" ] && [ -z "$Hostdisplay$Hostwaylandsocket" ] && [ "$Xserver" != "--tty" ] && [ -z "$Winsubsystem" ] && { + warning "Environment variables DISPLAY and WAYLAND_DISPLAY are empty, + but it seems x11docker was started within X, not from console. + Please set DISPLAY and XAUTHORITY. + If you have started x11docker with su or sudo, su/sudo may be configured to + unset X environment variables. It may work if you run x11docker with + sudo -E x11docker [...] + If your system does not support 'sudo -E', you can try + sudo env DISPLAY=\$DISPLAY XAUTHORITY=\$XAUTHORITY x11docker [...] + Otherwise, you can use tools like gksu/gksudo/kdesu/kdesudo/lxsu/lxsudo." + + [ -n "${PKEXEC_UID:-}" ] && note "It seems you have started x11docker with pkexec. + Can not determine DISPLAY and XAUTHORITY, can not use your X server. + To allow other X server options, please provide environment variables with + pkexec env DISPLAY=\$DISPLAY XAUTHORITY=\$XAUTHORITY x11docker [ARGS]." + + [ "$Autochooseserver" = "yes" ] && Xserver="--xorg" + } + + [ "$Runsoverssh" = "yes" ] && [ -z "$Hostdisplay$Hostwaylandsocket" ] && [ "$Xserver" != "--tty" ] && [ "$Autochooseserver" = "yes" ] && { + error "You are running x11docker over SSH without providing a display. + Please run with 'ssh -X' or 'ssh -Y'. + (If you insist, you can run with option '--xorg', but won't see the result remotely.)" + } + + ## check if dependencies for chosen X server are installed, fall back to best alternatives if not + [ "$Xserver" = "--xwin" ] && { check_xdepends --xwin || Xserver="--runx" ; } + [ "$Xserver" = "--runx" ] && { check_xdepends --runx || Xserver="--hostdisplay" ; } + [ "$Xserver" = "--hostdisplay" ] && { check_xdepends --hostdisplay || Xserver="--xpra" ; } + [ "$Xserver" = "--xephyr" ] && { check_xdepends --xephyr || Xserver="--nxagent" ; } + [ "$Xserver" = "--xvfb" ] && { check_xdepends --xvfb || Xserver="--xdummy" ; } + [ "$Xserver" = "--hostwayland" ] && { check_xdepends --hostwayland || Xserver="--weston" ; } + [ "$Xserver" = "--nxagent" ] && { check_xdepends --nxagent || { [ "$Desktopmode" = "yes" ] && Xserver="--xephyr" || Xserver="--xpra" ; } ; } + [ "$Xserver" = "--xpra" ] && { check_xdepends --xpra || { [ -z "$Hostdisplay" ] && Xserver="--weston-xwayland" ; } ; } + [ "$Xserver" = "--xpra" ] && { check_xdepends --xpra || { check_xdepends --nxagent && Xserver="--nxagent" || Xserver="--xephyr" ; } ; } + [ "$Xserver" = "--xorg" ] && { check_xdepends --xorg || Xserver="--weston-xwayland" ; } + [ "$Xserver" = "--xpra-xwayland" ] && { check_xdepends --xpra-xwayland || Xserver="--weston-xwayland" ; } + [ "$Xserver" = "--xwayland" ] && { check_xdepends --xwayland || Xserver="--weston-xwayland" ; } + [ "$Xserver" = "--xpra-xwayland" ] && { check_xdepends --xpra-xwayland || { [ "$Desktopmode" = "yes" ] && Xserver="--kwin-xwayland" || Xserver="--hostdisplay" ; } ; } + [ "$Xserver" = "--kwin-xwayland" ] && { check_xdepends --kwin-xwayland || Xserver="--weston-xwayland" ; } + [ "$Xserver" = "--kwin" ] && { check_xdepends --kwin || Xserver="--weston" ; } + [ "$Xserver" = "--weston-xwayland" ] && { check_xdepends --weston-xwayland || Xserver="--kwin-xwayland" ; } + [ "$Xserver" = "--weston" ] && { check_xdepends --weston || Xserver="--kwin" ; } + [ "$Xserver" = "--xdummy-xwayland" ] && { check_xdepends --xdummy-xwayland || Xserver="--kwin-xwayland" ; } + + case $Xserver in + --weston|--kwin|--hostwayland) Setupwayland="yes" ;; + esac + [ "$Setupwayland" = "yes" ] && { check_xdepends $Xserver || error "Failed to set up a Wayland environment. + Please install 'weston' or 'kwin_wayland'. + Wayland is not possible with proprietary NVIDIA driver + or with --runtime=kata-runtime." ; } + + # Xephyr as fallback for all options. Last fallback: Xorg + check_xdepends $Xserver || Xserver="--xephyr" + [ "$Xserver" = "--xephyr" ] && { check_xdepends --xephyr || { + check_xdepends --kwin-xwayland && Xserver="--kwin-xwayland" + check_xdepends --hostdisplay && [ "$Desktopmode" = "no" ] && Xserver="--hostdisplay" + check_xdepends --runx && Xserver="--runx" + check_xdepends --xwin && Xserver="--xwin" + check_xdepends --nxagent && Xserver="--nxagent" + check_xdepends --weston-xwayland && Xserver="--weston-xwayland" + check_xdepends --xpra && Xserver="--xpra" + } + [ "$Sharegpu" = "yes" ] && case $Desktopmode in + yes) check_xdepends --weston-xwayland && Xserver="--weston-xwayland" ;; + no) check_xdepends --hostdisplay && Xserver="--hostdisplay" ;; + esac + [ "$Runsonconsole" = "yes" ] && { + check_xdepends --kwin-xwayland && Xserver="--kwin-xwayland" + check_xdepends --weston-xwayland && Xserver="--weston-xwayland" + check_xdepends --xorg && Xserver="--xorg" + } + check_xdepends $Xserver || Xserver="--xorg" + } + + check_xdepends $Xserver || { + case $Winsubsystem in + "") + error "Did not find a possibility to provide a display. + Recommendations: + To run within an already running X server, install one or all of: + Xephyr xpra nxagent + To run with GPU acceleration, install: + weston and Xwayland, optionally also: xpra and xdotool + To run from TTY or within Wayland, install: + weston and Xwayland + $Wikipackages" + ;; + CYGWIN) + error "Did not find a possibility to provide a display. + Please install packages 'xinit' and 'xauth' in Cygwin, + or run x11docker with --runx: https://github.com/mviereck/runx" + ;; + MSYS2|WSL1|WSL2) + [ "$Hostdisplay" ] && { + error "Did not find a possibility to provide a nested display. + Please install package 'xinit' and one or all of: nxagent Xephyr xpra + $Wikipackages" + } || { + error "Did not find a possibility to provide a display. + Please use --runx to provide an X server on MS Windows: + https://github.com/mviereck/runx" + } + ;; + esac + } + + [ "$Autochooseserver" = "yes" ] && note "Using X server option $Xserver" + storeinfo "xserver=$Xserver" + return 0 +} +create_xcommand() { ### create command to start X server and/or Wayland compositor + local Xserveroptions_custom= Xpraoptions= Nxagentoptions= Compositorpid= Weston= Westonoutput= Count= Envvar= + + Xserveroptions_custom="$Xserveroptions" + Xserveroptions="" + + #### General X server options + case $Xserver in + --nxagent) + { [ "$Sharehostipc" = "yes" ] || [ "$X11dockermode" = "exe" ] ; } && { + Xserveroptions="$Xserveroptions \\ + -shmem \\ + -shpix" + } || { + Xserveroptions="$Xserveroptions \\ + -noshmem \\ + -noshpix" + } + ;; + *) Xserveroptions=" \\ + -retro \\ + +extension RANDR \\ + +extension RENDER \\ + +extension GLX \\ + +extension XVideo \\ + +extension DOUBLE-BUFFER \\ + +extension SECURITY \\ + +extension DAMAGE \\ + +extension X-Resource \\ + -extension XINERAMA -xinerama" + case $Sharehostipc in + yes) Xserveroptions="$Xserveroptions \\ + +extension MIT-SHM" ;; + no) + Xserveroptions="$Xserveroptions \\ + -extension MIT-SHM" + Xprashm="XPRA_XSHM=0" ;; + esac + ;; + esac + + # X extension COMPOSITE + [ "$Xcomposite" ] || case $Xserver in + --nxagent|--xwin) Xcomposite="no" ;; + *) Xcomposite="yes" ;; + esac + case $Xcomposite in + yes) + # Old X servers have extension "Composite", recent ones call it "COMPOSITE". + Xserveroptions="$Xserveroptions \\ + +extension Composite +extension COMPOSITE" ;; + no) + Xserveroptions="$Xserveroptions \\ + -extension Composite -extension COMPOSITE" ;; + esac + + # X extension XTEST + [ "$Xtest" ] || case $Xserver in + --xpra|--xpra-xwayland|--xdummy|--xdummy-xwayland|--xvfb) Xtest="yes" ;; + *) Xtest="no" ;; + esac + case "$Xtest" in + yes) Xserveroptions="$Xserveroptions \\ + +extension XTEST" ;; + no) Xserveroptions="$Xserveroptions \\ + -extension XTEST -tst" ;; + esac + + # Disable screensaver + Xserveroptions="$Xserveroptions \\ + -dpms \\ + -s off" + + # X cookie authentication + case $Xauthentication in + yes) + Xserveroptions="$Xserveroptions \\ + -auth $Xservercookie" ;; + no) + case $Xoverip in + yes) warning "Option --no-auth: SECURITY RISK! + Allowing access to new X server for everyone. + Your X server is accessable over TCP network without any restriction. + That can be abused to take control over your system." ;; + no|"") [ "$Xserver" = "--hostdisplay" ] || warning "Option --no-auth: SECURITY RISK! + Allowing access to new X server for everyone." + Xserveroptions="$Xserveroptions \\ + -ac" ;; + esac + esac + + # X over IP/TCP + case $Xoverip in + yes) + case $Xserver in + --nxagent) ;; + *) Xserveroptions="$Xserveroptions \\ + -listen tcp" ;; + esac + ;; + no|"") Xserveroptions="$Xserveroptions \\ + -nolisten tcp" ;; + esac + + # check DPI + case $Xserver in + --xpra|--xpra-xwayland) + { [ -n "$Dpi" ] || [ "$Scaling" ] ; } && verlt "$Xpraversion" "v2.1-r16547" && ! verlt "$Xpraversion" "v2.1" && { + note "Option --dpi is buggy in xpra $Xpraversion + due to xpra bug #1605. Need at least xpra v2.1-r16547 or one of 2.0 series. + This affects option --scale, too, leading to wrong font sizes. + Fallback: disabling dpi settings." + check_fallback + Dpi="-1" + } ;; + esac + case $Xserver in + --weston|--kwin|--tty|--hostdisplay) ;; + --xwin|--runx) ;; + *) + [ -z "$Dpi" ] && { + xdpyinfo >/dev/null 2>&1 && { + Dpi="$(xdpyinfo | grep dots | cut -dx -f2 | cut -d' ' -f1)" + } || { + [ -n "$Hostdisplay" ] && [ -z "$(command -v xdpyinfo)" ] && note "Could not determine dpi settings. If you encounter too big or + too small fonts with $Xserver, please install xdpyinfo or use option --dpi. + $Wikipackages" + } + case $Xserver in + --xpra|--xpra-xwayland) + [ "$Scaling" ] && [ "$Desktopmode" = "no" ] && { + Dpi="$(awk -v a="$Scaling" -v b="$Dpi" 'BEGIN {print (b * a * a)}')" + Dpi="${Dpi%.*}" + } ;; + esac + } + ;; + esac + [ "$Dpi" = "-1" ] && Dpi="" + [ -n "$Dpi" ] && Xserveroptions="$Xserveroptions \\ + -dpi $Dpi" + + + #### xpra server and client command + case $Xserver in + --xpra|--xpra-xwayland) + + Xpraoptions="\\ + $(check_xpraoption --csc-modules=none) \\ + $(check_xpraoption --encodings=rgb) \\ + $(check_xpraoption --microphone=no) \\ + $(check_xpraoption --notifications=no) \\ + $(check_xpraoption --pulseaudio=no) \\ + $(check_xpraoption --socket-dirs="'$Cachefolder'") \\ + $(check_xpraoption --speaker=no) \\ + $(check_xpraoption --start-via-proxy=no) \\ + $(check_xpraoption --webcam=no) \\ + $(check_xpraoption --xsettings=no)" +# $(check_xpraoption --clipboard-direction=both) \\ +# $(check_xpraoption --system-tray=yes) \\ + + # --keymap + [ "$Xkblayout" ] && Xpraoptions="$Xpraoptions \\ + $(check_xpraoption --keyboard-layout="'$Xkblayout'") \\ + $(check_xpraoption --keyboard-raw=yes)" + +# Xpraoptions="$Xpraoptions $(check_xpraoption --debug=all)" ; Preservecachefiles="yes" # Debugging only + + # xpra server command + [ "$Desktopmode" = "yes" ] && Xpraservercommand="xpra start-desktop" || Xpraservercommand="xpra start" + Xpraservercommand="$Xpraservercommand :$Newdisplaynumber --use-display $Xpraoptions \\ + $(check_xpraoption --clipboard=yes)\\ + $(check_xpraoption --dbus-proxy=no) \\ + $(check_xpraoption --daemon=no) \\ + $(check_xpraoption --fake-xinerama=no) \\ + $(check_xpraoption --file-transfer=off) \\ + $(check_xpraoption --html=off) \\ + $(check_xpraoption --opengl=noprobe) \\ + $(check_xpraoption --mdns=no) \\ + $(check_xpraoption --printing=no) \\ + $(check_xpraoption --session-name="'$Codename'") \\ + $(check_xpraoption --start-new-commands=no) \\ + $(check_xpraoption --systemd-run=no) \\ + $(check_xpraoption --video-encoders=none)" + # disable --dpi for buggy versions + [ -n "$Dpi" ] && verlt "$Xpraversion" "v2.1-r16547" && ! verlt "$Xpraversion" "v2.1" && Dpi="" + [ -n "$Dpi" ] && Xpraservercommand="$Xpraservercommand \\ + $(check_xpraoption --dpi="'$Dpi'")" + + # xpra client command + Xpraclientcommand="xpra attach :$Newdisplaynumber $Xpraoptions \\ + $(check_xpraoption --clipboard=$Shareclipboard) \\ + $(check_xpraoption --compress=0) \\ + $(check_xpraoption --modal-windows=no) \\ + $(check_xpraoption --opengl=auto) \\ + $(check_xpraoption --quality=100) \\ + $(check_xpraoption --video-decoders=none)" + [ "$Fullscreen" = "yes" ] && Xpraclientcommand="$Xpraclientcommand \\ + $(check_xpraoption --desktop-fullscreen=yes)" + [ "$Scaling" ] && Xpraclientcommand="$Xpraclientcommand \\ + $(check_xpraoption --desktop-scaling="'$Scaling'")" +# [ -n "$Dpi" ] && Xpraclientcommand="$Xpraclientcommand \\ +# $(check_xpraoption --dpi="'$Dpi'")" + [ "$Xpraborder" ] && Xpraclientcommand="$Xpraclientcommand \\ + $(check_xpraoption --border="$Xpraborder")" + [ "$X11dockermode" = "run" ] && { + case "$Desktopmode" in + yes) Xpraclientcommand="$Xpraclientcommand \\ + $(check_xpraoption --title="'$Codename on $Newdisplay [in container] (shift+F11 toggles fullscreen)'")" ;; + no) Xpraclientcommand="$Xpraclientcommand \\ + $(check_xpraoption --title="'@title@ [in container]'")" ;; + esac + } + + # xpra environment variables + for Line in $Xpracontainerenv; do + store_runoption env "$Line" + done + ;; + esac + + + #### Prepare weston.ini: config file for Weston + case $Xserver in + --weston|--weston-xwayland|--xpra-xwayland|--xdummy-xwayland) + command -v weston-launch >/dev/null && [ "$Runsonconsole" = "yes" ] && [ "$Runsinteractive" = "yes" ] && Weston="weston-launch -v --" || Weston="weston" + echo "[core] +shell=desktop-shell.so +idle-time=0 +[shell] +panel-location=none +panel-position=none +locking=false +background-color=0xff002244 +animation=fade +startup-animation=fade +[keyboard] +" >> "$Westonini" + # --keymap: keyboard layout + [ -n "$Xkblayout" ] && echo "keymap_layout=$Xkblayout" >> "$Westonini" + [ -z "$Xkblayout" ] && [ "$Runsonconsole" = "yes" ] && echo "$(echo -n "keymap_layout=" && grep XKBLAYOUT <"/etc/default/keyboard" | cut -d= -f2 | cut -d'"' -f2)" >> "$Westonini" + + case $Runsonconsole in + no) # Display prefix X or WL; needed to indicate if host Wayland or host X provides the nested window. + #[ -n "$Hostwaylandsocket" ] && [ "$Xserver" != "--xpra-xwayland" ] && [ "$Hostsystem" != "ubuntu" ] && [ "$Fullscreen" = "no" ] && Westonoutput="WL" + [ -n "$Hostdisplay" ] && Westonoutput="X" + [ -z "$Westonoutput" ] && [ -n "$Hostwaylandsocket" ] && Westonoutput="WL" + ;; + yes) # short start&stop of Weston to grep name of monitor. Needed instead of WL or X prefix + [ -n "$Screensize" ] || [ "$Scaling" ] || [ -n "$Rotation" ] && { + Westonoutput="$(weston_getoutputname)" + debugnote "$Xserver: Detected screen output for weston: $Westonoutput" + } + ;; + esac + ;; + esac + + + #### create command to run X server + case $Xserver in + --xorg) + Xserveroptions="$Xserveroptions \\ + -verbose" + [ "$Xorgconf" ] && Xserveroptions="$Xserveroptions \\ + -config '$Xorgconf'" # --xorgconf + [ "$Runsonconsole" = "yes" ] && Xserveroptions="$Xserveroptions \\ + -keeptty" + Xcommand="$(command -v Xorg) :$Newdisplaynumber vt$Newxvt $Xserveroptions" + ;; + + --xpra) + case $Xpravfb in + Xvfb) Xcommand="$(command -v Xvfb) :$Newdisplaynumber $Xserveroptions \\ + -screen 0 ${Maxxaxis}x${Maxyaxis}x24" ;; + Xdummy) Xcommand="$Xorgwrapper :$Newdisplaynumber $Xserveroptions \\ + -config $Xdummyconf \\ + vt128" ;; + esac + ;; + + --xdummy) + Xcommand="$Xorgwrapper :$Newdisplaynumber vt128 $Xserveroptions \\ + -config $Xdummyconf" + ;; + + --xvfb) + Xcommand="$(command -v Xvfb) :$Newdisplaynumber $Xserveroptions \\ + -screen 0 ${Screensize}x24" ### FIXME: hardcoded setting of depth 24. Could be better? + ;; + + --xephyr) + command -v Xephyr >/dev/null && { + Xserveroptions="$Xserveroptions \\ + -resizeable \\ + -noxv" +# Xserveroptions="$Xserveroptions \\ +# -glamor" # disabled because of lagginess reported in #196 + case $Fullscreen in + yes) + Xserveroptions="$Xserveroptions \\ + -fullscreen" + ;; + no) + grep -q -- "-output " <<< "$Xserveroptions_custom" || Xserveroptions="$Xserveroptions \\ + -screen $Screensize" + ;; + esac + Xcommand="$(command -v Xephyr) :$Newdisplaynumber $Xserveroptions" + } || { + # Fallback: Xnest + Xcommand="$(command -v Xnest) :$Newdisplaynumber $Xserveroptions \\ + -geometry $Screensize \\ + -scrns $Outputcount \\ + -name '$Codename on $Newdisplay' " + note "Option --xephyr: Xephyr not found. Fallback: using Xnest. + Xnest is less stable and has less features than Xephyr. + For example, it misses RandR and Composite extensions and fullscreen mode. + It is recommended to install 'Xephyr'. + $Wikipackages" + check_fallback + } + ;; + + --xwayland) + Xcommand="$(command -v Xwayland) :$Newdisplaynumber $Xserveroptions" + ;; + + --xpra-xwayland|--xdummy-xwayland) + Xcommand="$(command -v Xwayland) :$Newdisplaynumber $Xserveroptions" + + echo "[output] +name=${Westonoutput}1 +mode=$Screensize" >> $Westonini + [ -n "$Customwestonini" ] && Westonini="$Customwestonini" + + Compositorcommand="$Weston \\ + --socket=$Newwaylandsocket \\ + --backend=x11-backend.so \\ + --config='$Westonini'" + [ "$Xserver" = "--xpra-xwayland" ] && case $Scaling in + "") Compositorcommand="$Compositorcommand \\ + --fullscreen" ;; + *) Compositorcommand="$Compositorcommand \\ + --width=$(cut -dx -f1 <<< "$Screensize") --height=$(cut -dx -f2 <<< "$Screensize")" ;; + esac + ;; + + --weston|--weston-xwayland) + Xcommand="$(command -v Xwayland) :$Newdisplaynumber $Xserveroptions" + + [ -n "$Westonoutput" ] && for ((Count=1 ; Count<="$Outputcount" ; Count++)) ; do + [ "$Westonoutput" = "WL" ] || [ "$Westonoutput" = "X" ] || { + Count="" + [ -z "$Screensize" ] && Screensize="preferred" + } + echo "[output] +name=$Westonoutput$Count +mode=$Screensize +" >> $Westonini + [ "$Scaling" ] && echo "scale=$Scaling" >> $Westonini + [ -n "$Rotation" ] && echo "transform=$Rotation" >> $Westonini + [ "$Count" ] || break + done + + Compositorcommand="$Weston \\ + --socket=$Newwaylandsocket" + [ "$Fullscreen" = "yes" ] && Compositorcommand="$Compositorcommand \\ + --fullscreen" + [ "$Outputcount" = "1" ] || Compositorcommand="$Compositorcommand \\ + --output-count=$Outputcount" + case $Westonoutput in + WL) Compositorcommand="$Compositorcommand \\ + --backend=wayland-backend.so" ;; + X) Compositorcommand="$Compositorcommand \\ + --backend=x11-backend.so" ;; + *) + case "$Runsonconsole" in + yes) Compositorcommand="$Compositorcommand \\ + --backend=drm-backend.so" ;; + no) Compositorcommand="$Compositorcommand \\ + --backend=x11-backend.so" ;; + esac + ;; + esac + [ -n "$Customwestonini" ] && Westonini="$Customwestonini" + Compositorcommand="$Compositorcommand \\ + --config='$Westonini'" + ;; + + --kwin|--kwin-xwayland) + Xcommand="$(command -v Xwayland) :$Newdisplaynumber $Xserveroptions" + + Compositorcommand="kwin_wayland \\ + --xwayland \\ + --socket=$Newwaylandsocket \\ + --width=$Xaxis --height=$Yaxis" + [ "$Outputcount" = "1" ] || Compositorcommand="$Compositorcommand \\ + --output-count=$Outputcount" + [ "$Xkblayout" ] && Compositorcommand="KWIN_XKB_DEFAULT_KEYMAP=$Xkblayout $Compositorcommand" + Compositorcommand="env QT_XKB_CONFIG_ROOT=/usr/share/X11/xkb $Compositorcommand" + case $Runsonconsole in + yes) Compositorcommand="$Compositorcommand \\ + --drm" ;; + no) + kwin_wayland --help | grep -q -- '--windowed' && { + Compositorcommand="$Compositorcommand \\ + --windowed" + } || { + kwin_wayland --help | grep -q -- '--x11-display' && [ "$Hostdisplay" ] && Compositorcommand="$Compositorcommand \\ + --x11-display=$Hostdisplay" + };; + esac + ;; + + --nxagent) + # files needed by nxagent + export NXAGENT_KEYSTROKEFILE="$Nxagentkeysfile" + export NX_CLIENT="$Nxagentclientrc" + + Xserveroptions="$Xserveroptions \\ + -norootlessexit \\ + -ac \\ + -options $Nxagentoptionsfile \\ + -keystrokefile $NXAGENT_KEYSTROKEFILE" + case $Desktopmode in + "yes") Xserveroptions="$Xserveroptions \\ + -D \\ + -name '$Imagename on $Newdisplay (shift+F11 toggles fullscreen)'" ;; + "no") Xserveroptions="$Xserveroptions \\ + -R" ;; + esac + Xcommand="$(command -v nxagent) :$Newdisplaynumber $Xserveroptions" + + # Some additional nxagent options are stored in a file + Nxagentoptions="nx/nx" + [ "$Shareclipboard" = "yes" ] && Nxagentoptions="$Nxagentoptions,clipboard=both" || Nxagentoptions="$Nxagentoptions,clipboard=none" + case $Fullscreen in + yes) Nxagentoptions="$Nxagentoptions,fullscreen=1" ;; + no) [ -n "$Screensize" ] && Nxagentoptions="$Nxagentoptions,geometry=$Screensize" ;; + esac + + # set keyboard layout + case $Xkblayout in + "") # set layout from host. + command -v setxkbmap >/dev/null && { + Nxagentoptions="$Nxagentoptions,keyboard=$(setxkbmap -query | grep rules | awk '{print $2}')/$(setxkbmap -query | grep layout | awk '{print $2}')" + } || note "Could not check your keyboard layout due to missing setxkbmap + If you get mismatching keys, please install setxkbmap. + $Wikipackages" + ;; + *) # --keymap + case $Xkblayout in + clone) Nxagentoptions="$Nxagentoptions,keyboard='clone'" ;; + *) Nxagentoptions="$Nxagentoptions,keyboard='evdev/$Xkblayout'" ;; + esac + ;; + esac + + Nxagentoptions="${Nxagentoptions}:${Newdisplaynumber}" + echo "$Nxagentoptions" >> "$Nxagentoptionsfile" + debugnote "$Xserver: Additional nxagent options: $Nxagentoptions" + + # Workaround as nxagent ignores XAUTHORITY and fails to start if option -auth is given without containing the cookie from host display. + # Option -ac above complies "xhost +" and is reverted in xinitrc. + [ "$Xauthentication" = "yes" ] && unpriv "cp '$Hostxauthority' '$Xservercookie'" + + # fake NXclient + echo '#! /usr/bin/env bash +# helper script to terminate nxagent. +# nxagent runs program noted in NX_CLIENT if window close button is pressed. +# (real nxclient does not exist) +echo "NXclient: $*" >> '$Xinitlogfile' +parsed="$(getopt --options="" --longoptions="parent:,display:,dialog:,caption:,window:,message:" -- "$@")" +eval set -- $parsed +while [ -n "${1:-}" ] ; do + case "${1:-}" in + --dialog) dialog="${2:-}" && shift ;; + --display|--caption|--message) shift ;; + --window) shift ;; + --parent) pid="${2:-}" && shift ;; + --) ;; + esac + shift +done +case $dialog in + pulldown) ;; + yesnosuspend) + kill $pid + echo timetosaygoodbye >> '$Timetosaygoodbyefile' + ;; +esac +' >> "$NX_CLIENT" + unpriv "chmod +x '$NX_CLIENT'" + + echo ' + + + +' >> "$NXAGENT_KEYSTROKEFILE" + ;; + + --xwin) + case $Sharegpu in + no) + Iglx="no" + Xserveroptions="$Xserveroptions \\ + -nowgl" ;; + yes) + Iglx="yes" + Xserveroptions="$Xserveroptions \\ + -wgl" ;; + esac + + case $Fullscreen in + yes) + Xserveroptions="$Xserveroptions \\ + -fullscreen" ;; + no) + Xserveroptions="$Xserveroptions \\ + -lesspointer" + case $Desktopmode in + yes) + for ((Count=0 ; Count<$Outputcount ; Count++)); do + Xserveroptions="$Xserveroptions \\ + -screen $Count $Screensize" + done + ;; + no) Xserveroptions="$Xserveroptions \\ + -multiwindow" ;; + esac + ;; + esac + + case $Shareclipboard in + yes) Xserveroptions="$Xserveroptions \\ + -clipboard" ;; + no) Xserveroptions="$Xserveroptions \\ + -noclipboard" ;; + esac + + Xcommand="$(command -v XWin) :$Newdisplaynumber $Xserveroptions" + ;; + + --runx) + Xserveroptions="--display $Newdisplaynumber \ + --verbose" + [ "$Xauthentication" = "no" ] && Xserveroptions="$Xserveroptions \ + --no-auth" + [ "$Desktopmode" = "yes" ] && Xserveroptions="$Xserveroptions \ + --desktop" + [ "$Shareclipboard" = "yes" ] && Xserveroptions="$Xserveroptions \ + --clipboard" + [ "$Screensize" ] && Xserveroptions="$Xserveroptions \ + --size=$Screensize" + [ "$Sharegpu" = "yes" ] && { + Xserveroptions="$Xserveroptions \ + --gpu" + store_runoption env "LIBGL_ALWAYS_INDIRECT=1" + } + Xcommand="$(command -v runx) $Xserveroptions" + ;; + + --hostwayland|--hostdisplay|--tty) ;; + esac + + case $Xserver in + --tty|--hostdisplay|--runx) ;; + --weston|--kwin|--hostwayland) ;; + *) + case "$Iglx" in + yes) Xcommand="$Xcommand \\ + +iglx" + store_runoption env "LIBGL_ALWAYS_INDIRECT=1" ;; + no) + Xcommand="$Xcommand \\ + -iglx" ;; + esac + ;; + esac + + # --xopt + Xcommand="$Xcommand \\ + $Xserveroptions_custom" + + case $Xserver in + --weston|--kwin|--hostwayland|--hostdisplay|--tty) Xcommand="" ;; + esac + case $Xserver in + --weston|--kwin|--weston-xwayland|--kwin-xwayland|--xpra-xwayland|--xdummy-xwayland) ;; + *) Compositorcommand="" ;; + esac + + return 0 +} +disable_xhost() { # remove any access to X server granted by xhost + local Line= + command -v xhost >/dev/null || { + warning "Command 'xhost' not found. + Can not check for possibly allowed network access to X. + Please install 'xhost'." + return 1 + } + xhost 2>&1 | tail -n +2 /dev/stdin | while read -r Line ; do # read all but the first line (header) + debugnote "xhost: Removing entry $Line" + xhost -$Line # disable every entry + done + xhost - # enable access control + [ "$(xhost 2>&1 | wc -l)" -gt "1" ] && { + warning "Remaining xhost permissions found on display ${DISPLAY:-} +$(xhost 2>&1 )" + return 1 + } + xhost 2>&1 | grep "access control disabled" && { + warning "Failed to restrict xhost permissions. + Access to display ${DISPLAY:-} is allowed for everyone." + return 1 + } + return 0 +} +weston_getoutputname() { # short startup of weston on tty to grep output name + unpriv "$Weston --no-config --backend=drm-backend.so >> $Compositorlogfile 2>&1 & echo compositorpid=\$! >>$Storeinfofile" + waitforlogentry "weston-screencheck" "$Compositorlogfile" "connector" "$Compositorerrorcodes" + + grep Output <$Compositorlogfile | grep connector | head -n1 | cut -d ' ' -f3 | rev | cut -c2- | rev + + termpid "$(storeinfo dump compositorpid)" weston + storeinfo drop compositorpid + mkfile "$Compositorlogfile" +} + +#### X helper scripts +create_modelinefile() { # generate a set of smaller modelines for screen size $1 and store them in a cache file + local Newmodelinefile Modeline Size X Y Xcount Ycount + + Size="${1:-}" + X="$(echo "$Size" | cut -dx -f1)" + Y="$(echo "$Size" | cut -dx -f2)" + Newmodelinefile="$Modelinefile.$Size" + + [ -e "$Newmodelinefile" ] || { + debugnote "$Xserver: Generating modelines for $Size" + mkfile "$Newmodelinefile" + for Ycount in 25 30 40 45 50 55 60 65 70 75 80 85 90 95 100; do + for Xcount in 25 30 40 45 50 55 60 65 70 75 80 85 90 95 100; do + Modeline="$(cvt "$(awk -v a="$X" -v b=$Xcount 'BEGIN {print (a * b / 100)}')" "$(awk -v a="$Y" -v b=$Ycount 'BEGIN {print (a * b / 100)}' )" | tail -n1)" + Modeline="$(echo "$Modeline" | sed s/_60.00//g)" + echo "$Modeline" >> "$Newmodelinefile" + done + done + } + + echo "$Newmodelinefile" +} +create_xdummywrapper() { # options --xdummy, --xpra: create startscript for Xdummy + echo '#!/bin/sh +# fork of https://xpra.org/trac/browser/xpra/trunk/src/scripts/xpra_Xdummy +find_ld_linux() { + arch="$(uname -m)" + + if [ $arch = "x86_64" ]; then + LD_LINUX="/lib64/ld-linux-x86-64.so.2" + elif [ $arch = "i386" ]; then + LD_LINUX="/lib/ld-linux.so.2" + elif [ $arch = "i486" ]; then + LD_LINUX="/lib/ld-linux.so.2" + elif [ $arch = "i586" ]; then + LD_LINUX="/lib/ld-linux.so.2" + elif [ $arch = "i686" ]; then + LD_LINUX="/lib/ld-linux.so.2" + elif [ $arch = "armel" ]; then + LD_LINUX="/lib/ld-linux.so.3" + elif [ $arch = "armhfp" ]; then + LD_LINUX="/lib/ld-linux.so.3" + elif [ $arch = "armhf" ]; then + LD_LINUX="/lib/ld-linux-armhf.so.3" + elif [ $arch = "ppc64" ]; then + LD_LINUX="/lib64/ld64.so.1" + elif [ $arch = "s390x" ]; then + LD_LINUX="/lib64/ld64.so.1" + else + #suitable for: powerpc/ppc, mips/mipsel, s390 and others: + LD_LINUX="/lib/ld.so.1" + fi + + if [ ! -x "$LD_LINUX" ]; then + # Musl C / Alpine Linux + ldmusl="$(ls /lib | grep ^ld-musl)" + if [ -n "$ldmusl" ]; then + LD_LINUX="/lib/$ldmusl" + else + LD_LINUX="" + echo "could not determine ld path for $arch, please file an xpra bug" + fi + fi +} + +if [ -x "/usr/libexec/Xorg" ]; then + #Fedora 22+ workaround where /usr/bin/Xorg is not suid + #because it is a script, command -v calls /usr/libexec/Xorg.wrap + #command -v is setuid, and command -v eventually calls this one: + XORG_BIN="/usr/libexec/Xorg" +elif [ -x "/usr/libexec/Xorg.bin" ]; then + #Fedora 21 workaround where /usr/bin/Xorg is not suid + #because it is a script, command -v calls /usr/libexec/Xorg.wrap + #command -v is setuid, and command -v eventually calls this one: + XORG_BIN="/usr/libexec/Xorg.bin" +elif [ -x "/usr/lib/xorg-server/Xorg" ]; then + #Arch Linux: + exec "/usr/lib/xorg-server/Xorg" "$@" +elif [ -x "/usr/lib/xorg/Xorg" ]; then + #Ubuntu 16.10: + exec "/usr/lib/xorg/Xorg" "$@" +else + XORG_BIN="$(command -v Xorg)" +fi +if [ ! -x "$XORG_BIN" ]; then + echo "failed to locate Xorg binary to run" + exit 1 +fi +if [ -u "$XORG_BIN" ]; then + # setuid is set, we need to do magic + find_ld_linux + if [ -n "$LD_LINUX" ]; then + if [ -n "$BASH" ]; then + #running in bash, can show a more helpful command name: + exec -a "Xorg-nosuid" "$LD_LINUX" "$XORG_BIN" "$@" + else + exec "$LD_LINUX" "$XORG_BIN" "$@" + fi + else + #fallback to making a copy of the binary: + DOTXPRA_DIR="$HOME/.xpra" + if [ ! -d "$DOTXPRA_DIR" ]; then + mkdir "$DOTXPRA_DIR" + chmod 700 "$DOTXPRA_DIR" + fi + NOSUID_XORG="$DOTXPRA_DIR/Xorg-nosuid" + cp -f "$XORG_BIN" "$NOSUID_XORG" + exec "$NOSUID_XORG" "$@" + fi +else + # setuid is not set on xorg_bin + exec "$XORG_BIN" "$@" +fi +' +} >> $Xorgwrapper +create_xdummyxorgconf() { # options --xdummy, --xpra: create xorg.conf for Xdummy + local Modelinefile + echo '# This xorg configuration file is forked and changed from xpra to start a dummy X11 server. +# For original and details, please see: https://xpra.org/Xdummy.html +# Set of modelines for different resolutions is created in xinitrc. +Section "ServerFlags" + Option "DontVTSwitch" "true" + Option "AllowMouseOpenFail" "true" + Option "PciForceNone" "true" + Option "AutoEnableDevices" "false" + Option "AutoAddDevices" "false" +EndSection +Section "Device" + Identifier "dummy_videocard" + Driver "dummy" + DacSpeed 600 + Option "ConstantDPI" "true" + VideoRam '$(($Maxxaxis * $Maxyaxis * 2 * 32 / 8 / 1024))' +EndSection +Section "Monitor" + Identifier "dummy_monitor" + HorizSync 1.0 - 2000.0 + VertRefresh 1.0 - 200.0 +' >> $Xdummyconf + # Modeline for desired virtual screen size + echo "Modeline $Modeline" >> $Xdummyconf + # Subset of smaller Modelines + Modelinefile="$(create_modelinefile "${Maxxaxis}x${Maxyaxis}")" + cat "$Modelinefile" >> $Xdummyconf + echo ' +EndSection +Section "Screen" + Identifier "dummy_screen" + Device "dummy_videocard" + Monitor "dummy_monitor" + DefaultDepth 24 + SubSection "Display" + Viewport 0 0 + Depth 32 + Modes '$(echo $Modeline | cut -d " " -f1)' + Virtual '$Xaxis' '$Yaxis' + EndSubSection +EndSection +Section "ServerLayout" + Identifier "dummy_layout" + Screen "dummy_screen" +EndSection +' >> $Xdummyconf +} +create_xinitrc() { # create xinitrc: set up X environment, create cookies + echo "#! /bin/sh" + #[ "$Debugmode" = "yes" ] && echo "set -x" + + declare -f disable_xhost + declare -f pspid + declare -f rocknroll + declare -f storeinfo + declare -f storepid + echo "$Messagefifofuncs" + + echo "getscreensize() {" + echo " CurrentXaxis=\"\$(xrandr | grep primary | cut -d' ' -f4 | cut -dx -f1 )\"" + echo " CurrentYaxis=\"\$(xrandr | grep primary | cut -d' ' -f4 | cut -dx -f2 | cut -d+ -f1)\"" + echo "}" + echo "checkscreensize() {" + echo " getscreensize" + echo " [ \"\$Xaxis\" = \"\$CurrentXaxis\" ] || return 1" + echo " [ \"\$Yaxis\" = \"\$CurrentYaxis\" ] || return 1" + echo " return 0" + echo "}" + echo "getprimary() {" + echo " xrandr | grep -q primary || xrandr --output \$(xrandr | grep ' connected' | head -n1 | cut -d' ' -f1) --primary" + echo " echo \$(xrandr | grep primary | cut -d' ' -f1)" + echo "}" + echo "" + + echo "Messagefile='$Messagefifo'" + echo "Output=\"\$(getprimary)\"" + echo "Storeinfofile='$Storeinfofile'" + echo "Storepidfile='$Storepidfile'" + echo "Timetosaygoodbyefile='$Timetosaygoodbyefile'" + echo "" + echo "export PATH='${PATH:-}'" + echo "" + echo "Cookie=''" + echo "Line=''" + echo "Var=''" + echo "" + + echo "debugnote 'Running xinitrc'" + echo "" + + case $Xserver in + --weston|--kwin|--hostwayland) + echo "export $Newxenv" + echo "unset DISPLAY XAUTHORITY" + echo "export DISPLAY XAUTHORITY" + ;; + --tty) + echo "unset DISPLAY XAUTHORITY WAYLAND_DISPLAY" + echo "export DISPLAY XAUTHORITY WAYLAND_DISPLAY" + ;; + --runx) + [ "$Xauthentication" = "yes" ] && { + echo "# cookie generated by runx" + echo 'debugnote "xinitrc: Option --runx: Using cookie: $XAUTHORITY"' + echo "cp -T \"\$XAUTHORITY\" '$Xclientcookie'" + echo "cp -T \"\$XAUTHORITY\" '$Xservercookie'" + echo "debugnote \"xinitrc: Cookie: \$(xauth -f $Xclientcookie list 2>&1)\"" + } + echo "export $Newxenv" + ;; + *) # here something for real X servers + echo "export $Newxenv" + echo "# background color" + case $Xserver in + --hostdisplay) ;; + --nxagent) + echo "sleep 2 && xsetroot -solid '#7F7F7F' 2>/dev/null &" ;; + *) echo "xsetroot -solid '#7F7F7F' 2>/dev/null" ;; + esac + echo "" + + [ "$Xauthentication" = "yes" ] && { + echo "# create new XAUTHORITY cookies" + echo ":> $Xclientcookie" + echo "" + [ "$Xserver" = "--nxagent" ] && { + echo "cp $Xclientcookie $Xclientcookie.bak # nxagent workaround cookie was created before starting xinit" + echo "export XAUTHORITY=$Xclientcookie.bak" + echo "" + } + [ "$Trusted" = "yes" ] && Trusted="trusted" || Trusted="untrusted" + [ "$Xserver" = "--hostdisplay" ] && { + [ -s "$Hostxauthority" ] && echo "export XAUTHORITY=$Hostxauthority" + echo "xhost | grep -q 'SI:localuser:$Hostuser' || { xhost +SI:localuser:$Hostuser ; Xhostentry='yes' ; }" + echo "" + } + case "$Runsoverssh" in + no) + echo "echo 'Requesting $Trusted cookie from X server'" + echo "xauth -v -i -f $Xclientcookie generate $Newdisplay . $Trusted timeout 3600" + ;; + yes) + echo "verbose 'Can not use cookies created over SSH. Will bake one myself.'" + ;; + esac + echo "" + echo "[ -s '$Xclientcookie' ] || { " + echo " [ '$Trusted' = 'untrusted' ] && note 'Could not create untrusted cookie. + Maybe your X server misses extension SECURITY.'" + [ "$Xserver" = "--hostdisplay" ] && { + [ "$Sharehostipc" = "no" ] && [ "$Runsoverssh" = "no" ] && [ "$Hostmitshm" = "yes" ] && { + echo " warning 'Memory access failures and rendering glitches + may occure due to unrestricted cookie. + Avoid them with isolation breaking option --hostipc, + or use another X server option like --nxagent or --xpra.'" + } + echo " [ '$Trusted' = 'untrusted' ] && warning 'SECURITY RISK! Keylogging and remote host control " + echo " may be possible! Better avoid using option --hostdisplay," + echo " rather use --nxagent or --xpra.'" + echo " cp $Hostxauthority $Xclientcookie" + } + echo "}" + echo "[ -s '$Xclientcookie' ] || { " + echo " # still no cookie? try to create one without extension security" + echo " debugnote 'xinitrc: Failed to retrieve trusted cookie from X server. Will bake one myself.'" + echo " echo 'Failed to retrieve trusted cookie from X server. Will bake one myself.'" + echo " xauth -v -i -f $Xclientcookie add :$Newdisplaynumber . $(makecookie)" + echo " ls -l $Xclientcookie" + echo "}" + echo "" + echo "# Prepare cookie with localhost identification disabled by ffff, needed if X socket is shared. ffff means 'familiy wild'" + echo "Cookie=\"\$(xauth -i -f $Xclientcookie nlist | sed -e 's/^..../ffff/')\"" + echo "echo \"\$Cookie\" | xauth -v -i -f $Xclientcookie nmerge -" + echo "" + echo "debugnote \"xinitrc: Created cookie: \$(xauth -f $Xclientcookie list 2>&1)\"" + echo "ls -l $Xclientcookie" + echo "cp $Xclientcookie $Xservercookie" + echo "chmod 644 $Xclientcookie" + echo "" + echo "[ -s '$Xclientcookie' ] || warning 'Cookie creation failed!'" + [ "$Xserver" = "--hostdisplay" ] && echo "[ '\$Xhostentry' = 'yes' ] && env XAUTHORITY=$Hostxauthority xhost -SI:localuser:$Hostuser" + [ "$Xserver" = "--nxagent" ] && echo "rm $Xclientcookie.bak" + } + echo "export XAUTHORITY=$Xclientcookie" + echo "[ '$Xauthentication' = 'no' ] || [ ! -s '$Xclientcookie' ] && unset XAUTHORITY && warning '$Xserver: X server $Newdisplay runs without cookie authentication.'" + echo "" + + case "$Xserver" in + --hostdisplay) ;; # do not change host settings + --xwin) ;; # xhost does not work over tcp (?) + *) + case $Xauthentication in + yes) + echo "# clean xhost" + echo "verbose 'Disabling any possible access to new X server possibly granted by xhost'" + echo "disable_xhost" + ;; + esac + [ -n "$Xhost" ] && { + echo "warning \"Option --xhost: Running 'xhost $Xhost' on $Newdisplay\"" + echo "xhost $Xhost" + } + echo "" + ;; + esac + + case $Xserver in + --hostdisplay|--xwin|--nxagent) ;; + --hostwayland|--weston|--kwin|--tty) ;; + *) + echo "# Keyboard layout" + command -v setxkbmap >/dev/null && { + case "$Xkblayout" in + "") [ -n "$Hostdisplay" ] && setxkbmap -display $Hostdisplay -print >> $Xkbkeymapfile ;; + *) setxkbmap "$Xkblayout" -print >> $Xkbkeymapfile ;; + esac + : + } || { + note "setxkbmap not found. Need setxkbmap and xkbcomp to set keyboard layout. + $Wikipackages" + } + [ -s "$Xkbkeymapfile" ] && { + echo "# set keyboard layout on $Newdisplay" + echo "verbose \"Keyboard layout:" + echo "\$(cat $Xkbkeymapfile)\"" + echo "xkbcomp $Xkbkeymapfile $Newdisplay" + } + echo "" + ;; + esac + ;; + esac + + case $Xserver in + --xpra|--xpra-xwayland) + ! verlt "$Xpraversion" "v2.3" && verlt "$Xprarelease" "r19606" && { + warning "Your xpra version has a cookie authentication issue. + Installed version is: xpra $Xpraversion + Recommended: Downgrade to xpra v2.2.5, upgrade to at least r19606, + or use another X server option. + Fallback: Setting 'xhost +SI:localuser:$Containeruser'" + check_fallback + echo "xhost +SI:localuser:$Containeruser" + echo "" + } ;; + esac + + case $Xserver in + --xpra|--xvfb) + { [ "$Xserver" = "--xpra" ] && [ "$Xpravfb" = "Xvfb" ] && [ "$Desktopmode" = "yes" ] ; } || [ "$Xserver" = "--xvfb" ] && { + echo "# create set of different screen resolutions" + echo "xrandr --newmode $Modeline" + echo "xrandr --addmode \$Output $(echo $Modeline | cut -d " " -f1)" + echo "while read Line; do" + echo " Line=\"\$(echo \"\$Line\" | sed 's/Modeline//g')\"" + echo " Line=\"\$(echo \"\$Line\" | sed 's/\"//g')\"" + echo " xrandr --newmode \$Line 2>/dev/null" + echo " xrandr --addmode \"\$Output\" \$(echo \$Line | cut -d' ' -f1) 2>/dev/null" + echo "done < \"$Modelinefile\"" + } + echo "" + [ "$Xserver" = "--xpra" ] && [ "$Desktopmode" = "yes" ] && echo "xrandr --output \$Output --mode $(echo $Modeline | cut -d " " -f1)" + echo "" + ;; + --xorg) # --xorg: --scale, --size, --rotate + echo "# determine screen size" + echo "xrandr | grep connected | grep -v disconnected && {" + [ -z "$Screensize" ] && { + echo " getscreensize" + echo " Xaxis=\"\$CurrentXaxis\"" + echo " Yaxis=\"\$CurrentYaxis\"" + [ "$Scaling" ] && echo " Xaxis=\"\$(awk -v a=\"\$Xaxis\" -v b=\"$Scaling\" 'BEGIN {print (a / b)}')\"" + echo " Xaxis=\"\${Xaxis%.*}\"" + [ "$Scaling" ] && echo " Yaxis=\"\$(awk -v a=\"\$Yaxis\" -v b=\"$Scaling\" 'BEGIN {print (a / b)}')\"" + echo " Yaxis=\"\${Yaxis%.*}\"" + } || { + echo " Xaxis='$Xaxis'" + echo " Yaxis='$Yaxis'" + } + echo " Screensize=\"\${Xaxis}x\${Yaxis}\"" + echo "" + + [ "$Screensize" ] && [ -z "$Scaling" ] && { + echo " # Switch to desired screen size $Screensize" + echo " [ -n \"\$(xrandr | grep \$Screensize)\" ] && { " + echo " note \"Will try to set native resolution \$Screensize." + echo " If that looks ugly, use --scale=1 to enforce a fake scaled resolution.\"" + echo " xrandr --output \$Output --mode \$Screensize" + echo " } || note \"Resolution \$Screensize not found in xrandr.\"" + echo "" + } + + [ "$Screensize" ] && [ -z "$Scaling" ] && { + echo " checkscreensize || {" + echo " note \"Panning \$Screensize. If virtual screen is greater than " + echo " maximal screen size, you can move virtual screen with mouse at screen edges." + echo " You can force the virtual screen to match your monitor with option --scale=1\"" + echo " xrandr --output \$Output --panning \$Screensize+0+0/\$Screensize+0+0/100/100/100/100 --verbose" + echo ' }' + echo " checkscreensize || {" + echo " note 'Panning failed, trying to scale instead.'" + echo " xrandr --output \$Output --scale-from \$Screensize --panning \$Screensize+0+0/\$Screensize+0+0" + echo " checkscreensize && note \"Successfully set screen size \$Screensize\"" + echo ' }' + echo " checkscreensize || {" + echo " getscreensize" + echo " note \"Setting desired resolution \$Screensize failed." + echo " Fallback: Will use detected \${CurrentXaxis}x\${CurrentYaxis} instead.\"" + echo ' }' + echo "" + } + + [ "$Scaling" ] && { + echo " # --scale $Scaling" + [ "$Screensize" ] && [ "$Scaling" != "1" ] && echo " note 'Cannot set panning and scaling at the same time. + Desired screen size $Screensize will be scaled to your monitor size + for arbitrary values you may provide with option --scale.'" + echo " # Scaling $Scaling" + echo " note \"Setting scaled resolution \$Screensize\" with scale factor $Scaling." + # must use --scale-from and --panning because --scale causes mouse barriers/crtc-boundaries + echo " xrandr --output \$Output --scale-from \$Screensize --panning \$Screensize+0+0/\$Screensize+0+0 --verbose" + echo " checkscreensize || {" + echo " getscreensize" + echo " note \"Setting desired resolution \$Screensize failed." + echo " Detected resolution \${CurrentXaxis}x\${CurrentYaxis} instead.\"" + echo " }" + echo "" + } + + [ -n "$Rotation" ] && { + echo " # --rotate $Rotation" + echo " verbose 'Rotation $Rotation'" + case $Rotation in + 0|normal) Rotation="" ;; + 90) Rotation="--rotate right";; + 180) Rotation="--reflect xy" ;; + 270) Rotation="--rotate left";; + flipped) Rotation="--reflect y";; + flipped-90) Rotation="--rotate right --reflect x";; + flipped-180) Rotation="--reflect x";; + flipped-270) Rotation="--rotate left --reflect x";; + esac + echo " bash -c 'while read Line ; do xrandr --output \$Line $Rotation ; done < <(xrandr | grep \" connected\" | cut -d \" \" -f1)'" + echo "" + } + echo "} || {" + echo ' [ -z "$Xaxis" ] && Xaxis=1024 && Yaxis=768' + echo " Screensize=\"\${Xaxis}x\${Yaxis}\"" + echo " note \"Could not detect any connected monitor." + echo " Running on a server? Will try to set a framebuffer size" + echo " with \"xrandr --fb \$Screensize\" that may serve as a virtual display.\"" + echo " xrandr --fb \$Screensize" + echo "}" + echo "" + ;; + esac + + [ -n "$Newdisplay" ] && echo "verbose \"Output of xrandr on $Newdisplay +\$(xrandr)\"" + echo "" + + [ "$Xfishtank" = "yes" ] && echo "xfishtank & storepid \$! xfishtank" + + [ "$Runfromhost" ] && { + echo "# custom host command added with option --runfromhost" + echo "$Runfromhost" + echo "" + } + + echo "echo 'xinitrc: xinitrc is ready'" + echo "storeinfo xinitrc=ready" + echo "" + + + [ "$Shareclipboard" = "yes" ] && [ -n "$Hostdisplay" ] && { + case $Xserver in + --xpra|--xpra-xwayland|--nxagent|--xwin) ;; # have their own clipboard management + --hostdisplay) ;; # already same clipboard + *) # synchronizing between different X servers + echo "# option '-c, --clipboard': Run clipboard script " + echo "# (text copy only) (xpra has its own clipboard managment including images)" + echo "bash $Clipboardrc" + echo "" + ;; + esac + } + + echo "# wait for the end" + case $Usemkfifo in + yes) echo "read Var <$Timetosaygoodbyefifo" ;; + no) echo "while rocknroll; do sleep 1; done" ;; + esac + + return 0 +} >> $Xinitrc + +#### docker command setup +check_containerhome() { # options --home, --homedir, --homebasedir: check HOME of container user. + ## option '--home': Share folder ~/.local/share/x11docker/imagename with created container as its home directory + ## option '--home=DIR': Share custom host folder as home + ## option '--homebasedir': Specify base folder here to store container home folders for --home + + # base home folder + [ "$Hosthomebasefolder" ] && { # --homebasedir + Hosthomebasefolder="$(convertpath subsystem "$Hosthomebasefolder")" + [ -e "$Hosthomebasefolder" ] || { + warning "Option --homebasedir: Specified path does not exist: + $Hosthomebasefolder + Fallback: Using default home base directory." + check_fallback + Hosthomebasefolder="" + } + } + [ "$Hosthomebasefolder" ] || case $Mobyvm in + no) Hosthomebasefolder="$Containeruserhosthome/.local/share/x11docker" ;; + yes) Hosthomebasefolder="$(convertpath subsystem "$(wincmd 'echo %userprofile%') ")/x11docker/home" ;; + esac + + case $Sharehome in + yes|host) + [ -z "$Persistanthomevolume" ] && Persistanthomevolume="$Hosthomebasefolder/$Imagebasename" + Persistanthomevolume="$(sed "s%~%$Hostuserhome%" <<< "$Persistanthomevolume")" + [ "${Persistanthomevolume:0:1}" = "/" ] && Sharehome="host" || Sharehome="volume" + ;; + esac + + case $Sharehome in + host) + case $Createcontaineruser in + no) + note "Option --home or --home=DIR is not supported + with option --user=RETAIN. + Alternatively, specify a docker volume with --home=VOLUME. + Also you can use option --share to share host directories. + Fallback: Disabling option --home." + check_fallback + Sharehome="no" + ;; + yes) + grep -q "unknown" <<< "$Containeruser" && { + note "Option --home: Sharing a host folder is allowed only + for container users that also exist on host. + You can use a docker volume with --home=VOLUME instead. + Fallback: Disabling option --home." + check_fallback + Sharehome="no" + } + ;; + esac + ;; + esac + + case $Sharehome in + host) + Containeruserhomebasefolder="/home" + # A change can break existing configs, e.g. playonlinux +# Containeruserhomebasefolder="/home.x11docker" + [ "$Persistanthomevolume" = "$Containeruserhosthome" ] && { + # --home=$HOME must be same as on host #243 + Containeruserhomebasefolder="$(dirname "$Containeruserhosthome")" + Containeruserhome="$Containeruserhosthome" + } + ;; + no) +# Containeruserhomebasefolder="/home.tmp" + Containeruserhomebasefolder="/home" + ;; + volume) + Containeruserhomebasefolder="/home.volume/$Persistanthomevolume" + grep -q "/" <<< "$Persistanthomevolume" && error "Option --home: Invalid argument: '$Persistanthomevolume' + Please either specify an absolute path beginning with '/' + or specify a docker volume without any '/'." + ;; + esac + Containeruserhome="${Containeruserhome:-$Containeruserhomebasefolder/$Containeruser}" + + case "$Createcontaineruser" in + no) store_runoption env "HOME=/tmp" ;; + esac + + case $Sharehome in + host) + # if no home folder on host is specified (--home=DIR), create a standard one in ~/.local/share/x11docker + [ -d "$Persistanthomevolume" ] || { + [ "$Startuser" = "root" ] && su $Containeruser -c "mkdir -p '$Persistanthomevolume'" + [ "$Containeruser" = "$Hostuser" ] && unpriv "mkdir -p '$Persistanthomevolume'" && { + # create symbolic link to ~/x11docker + echo "$Persistanthomevolume" | grep -q .local/share/x11docker && [ ! -e "$Hostuserhome/x11docker" ] && unpriv "ln -s '$Hosthomebasefolder' '$Hostuserhome/x11docker'" ||: + } + } + [ -d "$Persistanthomevolume" ] || error "Option --home: Could not create persistent home folder for + user '$Containeruser' on host. Can e.g. happen with option --user. + Four possibilities to solve issue: + 1.) Run x11docker one time as user '$Containeruser'. + 2.) Run x11docker one time as user 'root'. + 3.) Use option --home=DIR with DIR pointing to a writeable folder. + 4.) Use option --home=VOLUME to use a docker volume." + writeaccess $Containeruseruid "$Persistanthomevolume" || warning "User '$Containeruser' might have no write access to + $Persistanthomevolume." + verbose "Sharing directory $Persistanthomevolume + with container as its home directory $Containeruserhome" + ;; + volume) + debugnote "Option --home: Using docker volume $Persistanthomevolume" + ;; + esac + + return 0 +} +check_containeruser() { # check container user and shared home folder (also option --user) + ## check container user + [ "$Containeruser" = "RETAIN" ] && return 0 + [ -z "$Containeruser" ] && Containeruser="$Hostuser" # default: containeruser = hostuser. can be changed with --user + [ -n "$Containeruser" ] && echo "$Containeruser" | grep -q ':' && { # option --user can specify a group/gid after : + Containerusergid="$(echo "$Containeruser" | cut -d: -f2)" + Containeruser="$(echo "$Containeruser" | cut -d: -f1)" + } + [ "$Containeruser" = "root" ] && Containeruser="0" + [ -n "$(getent passwd "$Containeruser")" ] && { # user exists on host + Containeruser=$(getent passwd "$Containeruser" | cut -d: -f1) # can be name or uid -> now name + Containeruseruid=$(getent passwd "$Containeruser" | cut -d: -f3) + [ -z "$Containerusergid" ] && Containerusergid="$(getent passwd $Containeruser | cut -d: -f4)" + [ "$Containeruser" = "$Hostuser" ] && Containeruserhosthome="$Hostuserhome" + [ -z "$Containeruserhosthome" ] && Containeruserhosthome="$(getent passwd "$Containeruser" | cut -d: -f6)" + : + } || { # user does not exist on host + [[ "$Containeruser" =~ ^[0-9]+$ ]] || error "Option --user: Unknown host user or invalid user number '$Containeruser'. + Non-host users can be specified with an UID only, not with a name." + Containeruseruid="$Containeruser" + Containeruser="unknown$Containeruseruid" + [ -z "$Containerusergid" ] && Containerusergid=100 + Containeruserhosthome="" + } + + Containerusergroup="$(getent group $Containerusergid | cut -d: -f1 || echo group_$Containeruser)" + [ "$Containeruseruid" = "0" ] && { + Containeruser="root" + Containerusergid="0" + Containerusergroup="root" + Containeruserhosthome="/root" + Sudouser="yes" && note "Option --user=root: Enabling option --sudouser." + } + + store_runoption env "USER=$Containeruser" + debugnote "container user: $Containeruser $Containeruseruid:$Containerusergid $Containeruserhosthome" + + case $X11dockermode in + run) + case $Containersetup in + no) store_runoption env "XDG_RUNTIME_DIR=/tmp" ;; + esac + ;; + exe) store_runoption env "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" ;; + esac + return 0 +} +create_dockercommand() { ### create command to run docker + local Line= Memory Tini= + + Dockercommand="$Dockerexe run --tty" + Wmdockercommand="$Dockerexe run --detach" + #[ "$Preservecachefiles" = "no" ] && Dockercommand="$Dockercommand --rm" + case $Interactive in + yes) Dockercommand="$Dockercommand --interactive" ;; + no) Dockercommand="$Dockercommand --detach" ;; + esac + + [ -z "$Containername" ] && Containername="x11docker_X${Newdisplaynumber}_${Codename}_${Cachenumber}" + Dockercommand="$Dockercommand \\ + --name $Containername" + Wmdockercommand="$Wmdockercommand \\ + --name ${Containername}_WM" + storeinfo "containername=$Containername" + + [ "$Limitresources" ] && { + Memory="$(awk "BEGIN {print int($(free -b | awk 'NR==2 {print $7}') * $Limitresources)}")" + Dockercommand="$Dockercommand \\ + --cpus=$(awk "BEGIN {print $(nproc) * $Limitresources}") \\ + --memory=$Memory \\ + --kernel-memory=$Memory" + } + + # container user. init systems switch later. + case $Initsystem in + none|tini|dockerinit) + case $Switchcontaineruser in + no) + [ "$Createcontaineruser" = "yes" ] && Dockercommand="$Dockercommand \\ + --user $Containeruseruid:$Containerusergid" ;; + yes) + Dockercommand="$Dockercommand \\ + --user root" ;; + esac + ;; + systemd|runit|openrc|sysvinit|s6-overlay) + Dockercommand="$Dockercommand \\ + --user root" ;; + esac + + Wmdockercommand="$Wmdockercommand \\ + --user 1999:1999" + + [ "$Createcontaineruser" = "yes" ] && { + # Disable user namespacing to avoid file permission issues with --home or --share. Files need same UID/GID. + case $Podman in + yes) Dockercommand="$Dockercommand \\ + --userns=keep-id" ;; + no) $Dockerexe run --help | grep -q -- '--userns' && Dockercommand="$Dockercommand \\ + --userns host" ;; + esac + } + + # add container user groups, mainly video and audio and --group-add + [ "$Switchcontaineruser" = "no" ] && { + $Dockerexe run --help | grep -q -- '--group-add' && { + for Line in $Containerusergroups; do ### FIXME: should compare GIDs from host and container + getent group ${Line:-nonsense} >/dev/null && Dockercommand="$Dockercommand \\ + --group-add $(getent group $Line | cut -d: -f3)" + done + : + } || { + note "Your docker version does not support option --group-add. + Could not add container user to groups: $Containerusergroups + Possible sound or GPU setup may fail. Please update your docker installation." + } + } + + # Runtime runc|nvidia|kata + [ "$Runtime" ] && { + Dockercommand="$Dockercommand \\ + --runtime='$Runtime'" + } + + # option --hostipc + [ "$Sharehostipc" = "yes" ] && Dockercommand="$Dockercommand \\ + --ipc host" + + # option --network + [ "$Network" ] && Dockercommand="$Dockercommand \\ + --network $Network" + + # capabilities + [ "$Capdropall" = "yes" ] && Dockercommand="$Dockercommand \\ + --cap-drop ALL" + while read Line ; do + Dockercommand="$Dockercommand \\ + --cap-add $Line" + done < <(store_runoption dump cap) + + # default no, do not gain privileges + [ "$Allownewprivileges" = "no" ] && Dockercommand="$Dockercommand \\ + --security-opt no-new-privileges" + + # SELinux restrictions for containers must be disabled to allow access to X socket. Flags z or Z do not help. + Dockercommand="$Dockercommand \\ + --security-opt label=type:container_runtime_t" + + Wmdockercommand="$Wmdockercommand \\ + --cap-drop=ALL \ + --security-opt=no-new-privileges \ + --security-opt label=type:container_runtime_t" + + # stop signal for some init systems + [ "$Stopsignal" ] && Dockercommand="$Dockercommand \\ + --stop-signal $Stopsignal" + + # setup and shared files for some init systems + case $Initsystem in + dockerinit) + Dockercommand="$Dockercommand \\ + --init" + ;; + tini) + Tini="$Tinicontainerpath --" + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$Tinibinaryfile:ro" "$Tinicontainerpath")" + ;; + systemd) + Dockercommand="$Dockercommand \\ + -v $Systemdtarget:/etc/systemd/system/x11docker.target:ro \\ + -v $Systemdconsoleservice:/lib/systemd/system/console-getty.service:ro \\ + -v $Systemdwatchservice:/etc/systemd/system/x11docker-watch.service:ro \\ + -v $Systemdjournalservice:/etc/systemd/system/x11docker-journal.service \\ + -v $Systemdenvironment:/etc/systemd/system.conf.d/x11docker.environment.conf" + ;; + esac + + # option --sharecgroup + [ "$Sharecgroup" = "yes" ] && Dockercommand="$Dockercommand \\ + --volume /sys/fs/cgroup:/sys/fs/cgroup:ro" + + # Needed especially for --init=systemd and --dbus-daemon + $Dockerexe run --help | grep -q -- '--tmpfs' && Dockercommand="$Dockercommand \\ + --tmpfs /run --tmpfs /run/lock" || { + note "Your docker version does not support option --tmpfs. + Options like --init=systemd may fail. + Please update your docker installation." + } + + # shared cache folder + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$Sharefolder:rw" $Sharefoldercontainer)" + + # --home + case $Sharehome in + host) + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$Persistanthomevolume:rw" "$Containeruserhome")" + ;; + volume) + Dockercommand="$Dockercommand \\ + --volume '$Persistanthomevolume':'$Containeruserhomebasefolder':rw" + ;; + esac + + # --share + while read -r Line; do + case "$Line" in + "$Containeruserhosthome") + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$Containeruserhosthome" "/home.host/$Containeruser")" + ;; + *) + case "$(cut -c1-5 <<< "$Line")" in + "/dev/") + Dockercommand="$Dockercommand \\ + --device $(convertpath volume "$Line")" + warning "Sharing device file: $Line" + ;; + *) + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$Line")" + ;; + esac + ;; + esac + done < <(store_runoption dump volume) + [ "$Xauthentication" = "yes" ] && Wmdockercommand="$Wmdockercommand \\ + --volume $(convertpath volume "$Xclientcookie" "$(convertpath share $Xclientcookie)")" + + # --gpu: share NVIDIDA driver installer + [ -e "$Nvidiainstallerfile" ] && Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$Nvidiainstallerfile:ro" "$Nvidiacontainerfile")" + + # X socket will be softlinked to /tmp/.X11-unix in containerrc + [ "$Newxsocket" ] && { + case $Containersetup in + yes) + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$Newxsocket" "/X$Newdisplaynumber")" + ;; + no) + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$Newxsocket")" + ;; + esac + Wmdockercommand="$Wmdockercommand \\ + --volume $(convertpath volume "$Newxsocket")" + } + + # Wayland socket will be softlinked to XDG_RUNTIME_DIR in containerrc + [ "$Setupwayland" = "yes" ] && { + case $Containersetup in + yes) + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$XDG_RUNTIME_DIR/$Newwaylandsocket" "/$Newwaylandsocket")" + ;; + no) + Dockercommand="$Dockercommand \\ + --volume $(convertpath volume "$XDG_RUNTIME_DIR/$Newwaylandsocket")" + ;; + esac + } + + ## options --pulseaudio, --alsa + { [ "$Pulseaudiomode" = "socket" ] || [ "$Sharealsa" = "yes" ] ; } && Dockercommand="$Dockercommand \\ + --volume $Pulseaudioconf:/etc/pulse/client.conf:ro" + + ## option --workdir or /tmp + case $Containersetup in + yes) + Dockercommand="$Dockercommand \\ + --workdir '${Workdir:-/tmp}'" + ;; + no) + [ "$Workdir" ] && Dockercommand="$Dockercommand \\ + --workdir '$Workdir'" + ;; + esac + + case $Containersetup in + yes) + # real entrypoint is checked in dockerrc + Dockercommand="$Dockercommand \\ + --entrypoint env" + ;; + esac + + # add environment variables. Only needed here for possible 'docker exec'. Otherwise set in containerrc + while read Line; do + Dockercommand="$Dockercommand \\ + --env '$Line'" + grep -q "DISPLAY" <<< "$Line" && Wmdockercommand="$Wmdockercommand \\ + --env '$Line'" + grep -q "XAUTHORITY" <<< "$Line" && Wmdockercommand="$Wmdockercommand \\ + --env '$Line'" + done < <(store_runoption dump env) + + # add custom docker arguments, imagename and imagecommand + [ "$Customdockeroptions" ] && Dockercommand="$Dockercommand \\ + $Customdockeroptions" + Dockercommand="$Dockercommand \\ + --" + + case $Containersetup in + yes) + case $Switchcontaineruser in + no) Dockercommand="$Dockercommand $Imagename $Tini /bin/sh - $(convertpath share $Containerrc)" ;; # dockerrc runs containerrootrc with 'docker exec' + yes) Dockercommand="$Dockercommand $Imagename $Tini /bin/sh - $(convertpath share $Containerrootrc)" ;; # containerrootrc runs containerrc + esac + ;; + no) + Dockercommand="$Dockercommand $Imagename $Containercommand" + ;; + esac + + return 0 +} +setup_capabilities() { # check linux capabilities needed by container + # compare: man capabilities + + [ "$Sudouser" = "yes" ] && Adminusercaps="yes" + [ "$Capdropall" = "no" ] && [ "$Allownewprivileges" = "auto" ] && { + note "Option --cap-default: Enabling option --newprivileges=yes. + You can avoid this with --newprivileges=no" + Allownewprivileges="yes" + } + + # --sudouser + [ "$Sudouser" = "yes" ] && warning "Option --sudouser severly reduces container security. + Container gains additional capabilities to allow sudo and su. + If an application breaks out of container, it can harm your system + in many ways without you noticing. Password: x11docker" + + # enable dbus + case $Initsystem in + systemd|sysvinit|openrc|runit) Dbussystem="yes" ;; + esac + [ "$Dbussystem" = "yes" ] && { + Dbusrunsession="yes" + store_runoption cap "CHOWN FOWNER" ### FIXME: CHOWN needed indeed here? + Switchcontaineruser="yes" + } + + case $Initsystem in + none|tini|dockerinit) ;; + systemd) + Switchcontaineruser="yes" + Sharecgroup="yes" + store_runoption cap "FSETID FOWNER SETPCAP SYS_BOOT" + ;; + runit|openrc|sysvinit) + Switchcontaineruser="yes" + store_runoption cap "SYS_BOOT KILL" + ;; + s6-overlay) + Switchcontaineruser="yes" + store_runoption cap "CHOWN KILL" + ;; + esac + + [ "$Sharecgroup" = "yes" ] && Switchcontaineruser="yes" # needed for elogind + [ "$Switchcontaineruser" = "yes" ] && Switchcontainerusercaps="yes" + + [ "$Adminusercaps" = "yes" ] && { + Switchcontainerusercaps="yes" + store_runoption cap "CHOWN KILL FSETID FOWNER SETPCAP" + [ "$Allownewprivileges" = "auto" ] && { + note "Option --sudouser: Enabling option --newprivileges=yes. + You can avoid this with --newprivileges=no" + Allownewprivileges="yes" + } + } + [ "$Switchcontainerusercaps" = "yes" ] && store_runoption cap "SETUID SETGID DAC_OVERRIDE AUDIT_WRITE" + + # Automated NVIDIA driver installation + [ "$Sharegpu" = "yes" ] && [ "$Nvidiainstallerfile" ] && [ "$Switchcontaineruser" = "yes" ] && store_runoption cap "CHOWN FOWNER" + + [ "$Allownewprivileges" = "auto" ] && Allownewprivileges="no" + + [ "$Allownewprivileges" = "yes" ] && warning "Option --newprivileges=yes: x11docker does not set + docker run option --security-opt=no-new-privileges. + That degrades container security. + However, this is still within a default docker setup." + + # Issues with hidepid=2 seen on NixOS (issue #83) + { [ "$Switchcontaineruser" = "yes" ] || [ "$Containeruser" != "$Hostuser" ] ; } && { + [ "$Hostcanwatchroot" = "no" ] && { + [ "$Hosthidepid" = "yes" ] && Message="/proc is mounted with hidepid=2." || Message="Cannot watch processes of other users for unknown reasons." + Message="$Message + x11docker cannot watch processes of root + or other users different from $Hostuser." + [ "$Hostuser" != "$Containeruser" ] && Message="$Message + Container user $Containeruser is different from host user $Hostuser." + [ "$Switchcontaineruser" = "yes" ] && Message="$Message + Container PID 1 will run as root." + Message="$Message + Therefore x11docker cannot watch container processes + for a clean termination of X and x11docker itself. + Four possible solutions: + 1. Run x11docker as root. + 2. Don't use options like --user or --init=systemd that change container user. + 3. Add user $Hostuser to group 'proc'. + 4. Change /proc mount option hidepid=2 to hidepid=1." + error "$Message" + } + } + + return 0 +} +setup_initsystem() { # option init: set up capabilities, check or create files + # some init system setup also in containerrootrc + local Message= + + case $Initsystem in + tini|systemd|sysvinit|openrc|runit|dockerinit|s6-overlay|none) ;; + *) + note "Option --init: Unknown init system $Initsystem + Possible: tini systemd sysvinit openrc runit s6-overlay none + Fallback: Using --init=tini instead." + check_fallback + Initsystem="tini" + ;; + esac + + # --init in Mobyvm. /usr/bin/docker-init is not available in MSYS2/Cygwin/WSL1 + case $Mobyvm in + yes) [ "$Initsystem" = "tini" ] && Initsystem="dockerinit" ;; + esac + + case "$X11dockermode" in + exe) + case $Initsystem in + tini|none) ;; + dockerinit) Initsystem="none" ;; + *) + note "Option --init: Only --init[=tini] or --init=none are + supported with option --exe. Fallback: Setting option --init=tini" + check_fallback + Initsystem="tini" + ;; + esac + ;; + run) + store_runoption env "container=docker" # At least OpenRC and systemd regard this hint + ;; + esac + + case $Initsystem in + none|dockerinit) ;; + tini) + Tinibinaryfile="$(command -v docker-init ||:)" + [ -z "$Tinibinaryfile" ] && Tinibinaryfile="/snap/docker/current/bin/docker-init" + [ -e "$Tinibinaryfile" ] || Tinibinaryfile="/snap/docker/current/usr/bin/docker-init" + [ -e "/usr/local/share/x11docker/tini-static" ] && Tinibinaryfile="/usr/local/share/x11docker/tini-static" + [ -e "$Hostuserhome/.local/share/x11docker/tini-static" ] && Tinibinaryfile="$Hostuserhome/.local/share/x11docker/tini-static" + Tinibinaryfile="$(myrealpath "$Tinibinaryfile" 2>/dev/null ||:)" + [ -e "$Tinibinaryfile" ] || Tinibinaryfile="" + [ "$Tinibinaryfile" ] && { + case "$Runtime" in + kata-runtime) + # avoid sharing same file that might be shared with runc already. + mkdir -p "$Hostuserhome/.local/share/x11docker" + cp -u "$Tinibinaryfile" "$Hostuserhome/.local/share/x11docker/tini-static-kata" + Tinibinaryfile="$Hostuserhome/.local/share/x11docker/tini-static-kata" + ;; + esac + [ -x "$Tinibinaryfile" ] || { + chmod +x "$Tinibinaryfile" || { + warning "Your tini binary is not executeable. Please run + chmod +x $Tinibinaryfile" + Initsystem="none" + } + } + } || Initsystem="none" + [ "$Initsystem" = "none" ] && [ "$X11dockermode" = "run" ] && { + note "Did not find container init system 'tini'. + This is a bug in your distributions docker package. + Normally, docker provides init system tini as '/usr/bin/docker-init'. + + x11docker uses tini for clean process handling and fast container shutdown. + To provide tini yourself, please download tini-static: + https://github.com/krallin/tini/releases/download/v0.18.0/tini-static + Store it in one of: + $Hostuserhome/.local/share/x11docker/ + /usr/local/share/x11docker/" + } + verbose "--init: Found tini binary: ${Tinibinaryfile:-(none)}" + [ "$Tinibinaryfile" ] && storeinfo "tini=$Tinibinaryfile" + ;; + + systemd) + Stopsignal="SIGRTMIN+3" + Containerusergroups="$Containerusergroups systemd-journal" + + echo "[Unit] +Description=x11docker target +Wants=multi-user.target +After=multi-user.target +[Install] +Also=console-getty.service +Also=x11docker-watch.service +Also=x11docker-journal.service +" >> $Systemdtarget + + echo "[Unit] +Description=x11docker start service +# start on console to support --interactive +# runs x11docker-agetty->x11docker-login->containerrc +Wants=multi-user.target +Wants=x11docker-watch.service +Wants=x11docker-journal.service +Wants=dbus.service +After=systemd-user-sessions.service +After=rc-local.service getty-pre.target +Before=getty.target +[Service] +ExecStart=/usr/local/bin/x11docker-agetty +StandardInput=tty +StandardOutput=tty +Type=idle +UtmpIdentifier=cons +TTYPath=/dev/console +TTYReset=yes +TTYVHangup=yes +KillMode=process +IgnoreSIGPIPE=no +SendSIGHUP=yes + +[Install] +WantedBy=x11docker.target +WantedBy=getty.target +WantedBy=multi-user.target +" >> $Systemdconsoleservice + + echo "[Unit] +Description=x11docker watch service +# Watches for end of containerrc and initiates shutdown +[Service] +Type=simple +ExecStart=/bin/sh -c 'while sleep 1; do systemctl is-active console-getty >/dev/null || { echo timetosaygoodbye >>$(convertpath share $Timetosaygoodbyefile) ; systemctl halt ; } ; [ -s $(convertpath share $Timetosaygoodbyefile) ] && systemctl halt ; done' +[Install] +WantedBy=x11docker.target +" >> $Systemdwatchservice + + echo "[Unit] +Description=x11docker journal log service +# get systemd log to transfer it into x11docker.log +[Service] +Type=simple +ExecStart=/bin/sh -c '/bin/journalctl --follow --no-tail --merge >> $(convertpath share $Systemdjournallogfile) 2>&1' +[Install] +WantedBy=x11docker.target +" >> $Systemdjournalservice + + echo "[Manager] +DefaultEnvironment=$(while read -r Line; do echo -n "$Line " ; done < <(store_runoption dump env)) +" >> $Systemdenvironment + ;; + + runit) + Stopsignal="HUP" + store_runoption env "VIRTUALIZATION=docker" + ;; + openrc) + ;; + sysvinit) + Stopsignal="INT" + ;; + s6-overlay) + ;; + esac + + case $Initsystem in + systemd) + warning "Option --init=systemd slightly degrades container isolation. + It adds some user switching capabilities x11docker would drop otherwise. + However, they are still within default docker capabilities. + Not within default docker capabilities it adds capability SYS_BOOT. + It shares access to host cgroups in /sys/fs/cgroup. + Some processes in container will run as root." + ;; + runit|openrc|sysvinit) + warning "Option --init=$Initsystem slightly degrades container isolation. + It adds some user switching capabilities x11docker would drop otherwise. + However, they are still within default docker capabilities. + Not within default docker capabilities it adds capability SYS_BOOT. + Some processes in container will run as root." + ;; + s6-overlay) + warning "Option --init=$Initsystem slightly degrades container isolation. + It adds some user switching capabilities x11docker would drop otherwise. + However, they are still within default docker capabilities. + Some processes in container will run as root." + ;; + tini|none|dockerinit) + [ "$Dbussystem" = "yes" ] && { + [ "$Capdropall" = "yes" ] && warning "Option --dbus=system slightly degrades container isolation. + It adds some user switching capabilities x11docker would drop otherwise. + However, they are still within default docker capabilities. + Some processes in container will run as root. + --dbus=system might need further capabilities or --cap-default to work + as expected. If in doubt, one of --init=systemd|openrc|runit|sysvinit + might be a better choice." + note "Option --dbus=system with init system '$Initsystem' + can have a quite long timeout delay until startup. + Use one of --init=systemd|openrc|runit|sysvinit in that case." + } + ;; + esac + + return 0 +} +store_runoption() { # store env, cap or volume/device for docker command + # $1 env store environment variable $2 + # volume store volume or device path $2 + # cap store apability $2 + # dump dump all entries of $2 + local Count Line Path Readwritemode + case ${1:-} in + env) + Containerenvironmentcount="$((Containerenvironmentcount + 1))" + Containerenvironment[$Containerenvironmentcount]="${2:-}" + ;; + volume) + Path="$(convertpath subsystem "${2:-}")" + Readwritemode="$(echo "${2:-}" | rev | cut -c1-3 | rev)" + [ "$Readwritemode" = ":ro" ] || Readwritemode="" + case "${Path:0:1}" in + "/") + [ -e "$Path" ] && { + Sharevolumescount="$((Sharevolumescount + 1))" + Sharevolumes[$Sharevolumescount]="${2:-}" + [ -h "$Path" ] && myrealpath "$Path" >/dev/null && { + note "Option --share: Shared file is a symbolic link. Sharing target, too. + Symlink: $Path + Target: $(myrealpath "$Path")" + store_runoption volume "$(myrealpath "$Path")$Readwritemode" + } + } + [ -e "$Path" ] || note "Option --share: Path not found: + $Path" + ;; + *) + grep -q '/' <<< "$Path" && error "Option --share: Invalid argument $Path. + Either specify an absolute path beginning with '/' + or specify a docker volume without any '/'." + Sharevolumescount="$((Sharevolumescount + 1))" + Sharevolumes[$Sharevolumescount]="${2:-}" + ;; + esac + ;; + cap) + for Line in ${2:-} ; do + Capabilities="$Capabilities +$Line" + done + ;; + dump) + case ${2:-} in + env) for ((Count=$Containerenvironmentcount ; Count>=1 ; Count --)) ; do echo "${Containerenvironment[$Count]}" ; done ;; + volume) for ((Count=1 ; Count<=$Sharevolumescount ; Count ++)) ; do echo "${Sharevolumes[$Count]}" ; done ;; + cap) + while read Line; do + [ "$Line" ] && case $Capdropall in + yes) echo "$Line" ;; + no) grep -w -q "$Line" <<< "SETPCAP MKNOD AUDIT_WRITE CHOWN NET_RAW DAC_OVERRIDE FOWNER FSETID KILL SETGID SETUID NET_BIND_SERVICE SYS_CHROOT SETFCAP" || echo "$Line" ;; + esac + done < <(echo "$Capabilities" | sort -u) + ;; + esac + ;; + esac + return 0 +} + +#### docker helper scripts +create_containerrootrc() { ### create containerrootrc: This script runs as root in container + local Line= + + echo "#! /bin/sh" + echo "" + echo "# containerrootrc" + echo "# This Script is executed as root in container." + echo "# - Create container user" + echo "# - Time zone" + echo "# - Install NVIDIA driver if requested" + echo "# - Set up init system services and DBus for --init=systemd|openrc|runit|sysvinit" + echo "" +# echo "set -x" + + echo "# redirect output to have it available before 'docker logs' starts. --init=runit (void) would eat up the output at all for unknown reasons." + echo "exec 1>>$(convertpath share $Containerlogfile) 2>&1" + echo "" + + declare -f storeinfo + declare -f rocknroll + echo "$Messagefifofuncs" + echo "Messagefile=$(convertpath share $Messagefifo)" + echo "Storeinfofile='$(convertpath share $Storeinfofile)'" + echo "Timetosaygoodbyefile=$(convertpath share $Timetosaygoodbyefile)" + echo "" + + echo "debugnote 'Running containerrootrc: Setup as root in container'" + echo "" + + echo "Error=''" + echo "for Line in cat chmod chown cut cd cp date echo env export grep id ln ls mkdir mv printf rm sed sh sleep tail touch; do" + echo " command -v \"\$Line\" || {" + echo " warning \"ERROR: Command not found in image: \$Line\"" + echo " Error=1" + echo " }" + echo "done" + echo "[ \"\$Error\" ] && error 'Commands for container setup missing in image. + You can try with option --no-setup to avoid this error.'" + echo "" + + echo "# Check type of libc" + echo "ldd --version 2>&1 | grep -q 'musl libc' && Containerlibc='musl'" + echo "ldd --version 2>&1 | grep -q -E 'GLIBC|GNU libc' && Containerlibc='glibc'" + echo 'debugnote "containerrootrc: Container libc: $Containerlibc"' + echo "" + + echo "# Prepare X environment" + echo "# Create some system dirs with needed permissions" + echo "mkdir -v -p /var/lib/dbus /var/run/dbus" + echo "mkdir -v -p -m 1777 /tmp/.ICE-unix /tmp/.X11-unix /tmp/.font-unix" + echo "chmod -c 1777 /tmp/.ICE-unix /tmp/.X11-unix /tmp/.font-unix" + echo "export DISPLAY=$Newdisplay XAUTHORITY=$(convertpath share $Xclientcookie)" + [ "$Xoverip" = "no" ] && { + echo "[ -e /X$Newdisplaynumber ] && ln -s /X$Newdisplaynumber $Newxsocket" # done again in containerrc. At least x11docker/deepin needs it here already. + echo "ls -l /X$Newdisplaynumber" + echo "ls -l $Newxsocket" + } + echo "" + + [ "$Screensize" ] && { + echo "# workaround: autostart of xrandr for some desktops like deepin, cinnamon and gnome to fix wrong autoresize" + echo "echo '#! /bin/sh +Output=\$(xrandr | grep ' connected' | cut -d\" \" -f1) +Mode=$Screensize +xrandr --output \$Output --mode \$Mode\n\ +' > /usr/local/bin/x11docker-xrandr" + echo "chmod +x /usr/local/bin/x11docker-xrandr" + echo "mkdir -p /etc/xdg/autostart" + echo "echo '[Desktop Entry] +Encoding=UTF-8 +Version=0.9.4 +Type=Application +Name=x11docker-xrandr +Comment= +Exec=/usr/local/bin/x11docker-xrandr +' > /etc/xdg/autostart/x11docker-xrandr.desktop" + } + echo "" + + + echo "# Time zone" + [ "$Hostlocaltimefile" ] && { + echo '[ ! -d /usr/share/zoneinfo ] && [ "$Containerlibc" = "'$Hostlibc'" ] && {' + echo " mkdir -p $(dirname $Hostlocaltimefile)" + echo " cp '$(convertpath share $Containerlocaltimefile)' '$Hostlocaltimefile'" + echo "}" + echo "[ -e '$Hostlocaltimefile' ] && ln -f -s '$Hostlocaltimefile' /etc/localtime" + echo "" + } + + echo "# Container system" + echo "Containersystem=\"\$(grep '^ID=' /etc/os-release 2>/dev/null | cut -d= -f2 || echo 'unknown')\"" + echo "verbose \"Container system ID: \$Containersystem\"" + echo "" + + echo "# Environment variables" + while read -r Line; do + echo "export '$Line'" + done < <(store_runoption dump env) + echo "" + + echo "# Check container user" + echo "Containeruser=\"\$(storeinfo dump containeruser)\"" # reading root access + echo "" + case $Createcontaineruser in + yes) + echo "Containeruserhome='$Containeruserhome'" + + # create container user + echo "# Create user entry in /etc/passwd (and delete possibly existing same uid)" + echo "cat /etc/passwd | grep -v ':$Containeruseruid:' > /tmp/passwd" ### FIXME gids same as uid would be deleted, too + echo "" + echo "# Disable possible /etc/shadow passwords for other users" + echo "sed -i 's%:x:%:-:%' /tmp/passwd" + case $Containerusershell in + auto) echo "bash --version >/dev/null 2>&1 && Containerusershell=/bin/bash || Containerusershell=/bin/sh" ;; + *) echo "Containerusershell='$Containerusershell'" ;; + esac + echo "Containeruserentry=\"$Containeruser:x:$Containeruseruid:$Containerusergid:$Containeruser,,,:$Containeruserhome:\$Containerusershell\"" + echo 'debugnote "containerrootrc: $Containeruserentry"' + echo 'echo "$Containeruserentry" >> /tmp/passwd' + echo "" + echo "rm /etc/passwd" + echo "mv /tmp/passwd /etc/passwd || warning 'Unable to change /etc/passwd. That may be a security risk.'" + echo "" + echo "# Create password entry for container user in /etc/shadow" + echo "rm -v /etc/shadow || warning 'Cannot change /etc/shadow. That may be a security risk.'" + echo "echo \"$Containeruser:$Containeruserpassword:17293:0:99999:7:::\" > /etc/shadow" + case $Sudouser in + no) echo "echo 'root:*:17219:0:99999:7:::' >> /etc/shadow" ;; + yes) echo "echo 'root:$Containeruserpassword:17219:0:99999:7:::' >> /etc/shadow # with option --sudouser, set root password 'x11docker'" + echo "sed -i 's%root:-:%root:x:%' /etc/passwd # allow password in /etc/shadow" + ;; + esac + echo "" + echo "# Create user group entry (and delete possibly existing same gid)" + echo "cat /etc/group | grep -v ':$Containerusergid:' > /tmp/group" + echo "echo \"$Containerusergroup:x:$Containerusergid:\" >> /tmp/group" + echo "mv /tmp/group /etc/group" + echo "" + + # sudo configuration + echo "# Create /etc/sudoers, delete /etc/sudoers.d. Overwrite possible sudo setups in image." + echo "[ -e /etc/sudoers.d ] && rm -v -R /etc/sudoers.d" + echo "[ -e /etc/sudoers ] && rm -v /etc/sudoers" + echo "echo '# /etc/sudoers created by x11docker' > /etc/sudoers" + echo "echo 'Defaults env_reset' >> /etc/sudoers" + echo "echo 'root ALL=(ALL) ALL' >> /etc/sudoers" + [ "$Sudouser" = "yes" ] && echo "echo '$Containeruser ALL=(ALL) ALL' >> /etc/sudoers" + echo "" + + # try to disable possible custom PAM setups that could allow switch to root in container + [ "$Sudouser" = "no" ] && { + echo "# Restrict PAM configuration of su and sudo" + echo "mkdir -p /etc/pam.d" + echo "[ -e /etc/pam.d/sudo ] && rm -v /etc/pam.d/sudo" + echo 'case "$Containersystem" in' + echo ' fedora)' + echo " echo '#%PAM-1.0' > /etc/pam.d/su" + echo " echo 'auth sufficient pam_rootok.so' >> /etc/pam.d/su" + #echo " echo 'auth substack system-auth' >> /etc/pam.d/su" + #echo " echo 'auth include postlogin' >> /etc/pam.d/su" + echo " echo 'account sufficient pam_succeed_if.so uid = 0 use_uid quiet' >> /etc/pam.d/su" + #echo " echo 'account include system-auth' >> /etc/pam.d/su" + #echo " echo 'password include system-auth' >> /etc/pam.d/su" + echo " echo 'session include system-auth' >> /etc/pam.d/su" + #echo " echo 'session include postlogin' >> /etc/pam.d/su" + #echo " echo 'session optional pam_xauth.so' >> /etc/pam.d/su" + echo ' ;;' + echo ' *)' + echo " echo '#%PAM-1.0' > /etc/pam.d/su" + echo " echo 'auth sufficient pam_rootok.so' >> /etc/pam.d/su # allow root to switch user without a password" + echo " echo '@include common-auth' >> /etc/pam.d/su" + echo " echo '@include common-account' >> /etc/pam.d/su" + echo " echo '@include common-session' >> /etc/pam.d/su" + echo ' ;;' + echo 'esac' + echo "" + } + ;; + no) + # check container user home. Can miss with --user=RETAIN + echo "Containeruserhome=\"\$(cat /etc/passwd | grep '\$Containeruser:.:' | cut -d: -f6)\"" + echo "Containeruserhome=\"\${Containeruserhome:-/tmp/\$Containeruser}\"" + echo "" + echo "debugnote \"containerrootrc: Container user: \$(id \$Containeruser) +\$(cat /etc/passwd | grep '\$Containeruser:.:')\"" + echo "" + ;; + esac + + # /etc/group + echo "# Set up container user groups" + for Line in $Containerusergroups ; do + echo "Groupname=\"$(cat /etc/group 2>/dev/null | grep "$Line" | cut -d: -f1)\"" + echo "Groupid=\"$(cat /etc/group 2>/dev/null | grep "$Line" | cut -d: -f3)\"" + echo "[ \"\$Groupname\" ] || Groupname=\"\$(cat /etc/group | grep \"$Line\" | cut -d: -f1)\"" + echo "[ \"\$Groupid\" ] || Groupid=\"\$(cat /etc/group | grep \"$Line\" | cut -d: -f3)\"" + echo "[ \"\$Groupname\" ] && {" + echo " cat /etc/group | sed \"s/^\$Groupname.*/\$Groupname:x:\$Groupid:\$(cat /etc/group | grep \"\$Groupname:.:\" | cut -d: -f4 ),\$Containeruser/\" | sed 's/:,/:/' > /tmp/group" + echo " cat /etc/group | grep -q \"\$Groupname:.:\" || echo \"\$Groupname:x:\$Groupid:\$Containeruser\" >> /tmp/group" + echo " cp /tmp/group /etc/group" + echo "} || note 'Failed to add container user to group $Line.'" + echo "" + done + + # HOME + echo "# Create HOME" + echo "mkdir -p \$Containeruserhome" + echo "chown \$Containeruser:\$(id -g \$Containeruser) \"\$Containeruserhome\"" + echo "ls -la \$Containeruserhome" + echo "" + + # --gpu with closed source nvidia driver + [ "$Nvidiainstallerfile" ] && { + echo "# Install NVIDIA driver" + echo "Nvidiaversion=\"\$(nvidia-settings -v 2>/dev/null | grep version | rev | cut -d' ' -f1 | rev)\"" + echo '[ "$Nvidiaversion" ] && note "Found NVIDIA driver $Nvidiaversion in image."' + echo 'case "$Nvidiaversion" in' + echo " $Nvidiaversion) note 'NVIDIA driver version in image matches version on host. Skipping installation.' ;;" + echo " *)" + echo " Installationwillsucceed=maybe" + echo ' case "$Containerlibc" in' + echo " musl) note 'Installing NVIDIA driver in container systems + based on musl libc like Alpine is not possible due to + proprietary closed source policy of NVIDIA corporation.'" + echo " Installationwillsucceed=no" + echo " ;;" + echo " esac" + echo " case \$Containersystem in" + echo " opensuse)" + echo " note \"Nvidia driver installation probably fails in \$Containersystem. + You can try to install nvidia driver $Nvidiaversion in image yourself.\"" + echo " ;;" + echo " esac" + echo " [ \"\$Installationwillsucceed\" = \"maybe\" ] && {" + echo " note 'Installing NVIDIA driver $Nvidiaversion in container.'" + echo " mkdir -m 1777 /tmp2" + echo " # provide fake tools to fool installer dependency check" + echo " ln -s /bin/true /tmp2/modprobe" + echo " ln -s /bin/true /tmp2/depmod" + echo " ln -s /bin/true /tmp2/lsmod" + echo " ln -s /bin/true /tmp2/rmmod" + echo " ln -s /bin/true /tmp2/ld" + echo " ln -s /bin/true /tmp2/objcopy" + echo " ln -s /bin/true /tmp2/insmod" + echo " Nvidiaoptions='--accept-license --no-runlevel-check --no-questions --no-backup --ui=none --no-kernel-module --no-nouveau-check'" + echo " env TMPDIR=/tmp2 PATH=\"/tmp2:\$PATH\" sh $Nvidiacontainerfile -A | grep -q -- '--install-libglvnd' && Nvidiaoptions=\"\$Nvidiaoptions --install-libglvnd\"" + echo " env TMPDIR=/tmp2 PATH=\"/tmp2:\$PATH\" sh $Nvidiacontainerfile -A | grep -q -- '--no-nvidia-modprobe' && Nvidiaoptions=\"\$Nvidiaoptions --no-nvidia-modprobe\"" + echo " env TMPDIR=/tmp2 PATH=\"/tmp2:\$PATH\" sh $Nvidiacontainerfile -A | grep -q -- '--no-kernel-module-source' && Nvidiaoptions=\"\$Nvidiaoptions --no-kernel-module-source\"" + echo " env TMPDIR=/tmp2 PATH=\"/tmp2:\$PATH\" sh $Nvidiacontainerfile --tmpdir /tmp2 \$Nvidiaoptions || note 'ERROR: Installation of NVIDIA driver failed. + Run with option --verbose to see installer output.'" + echo " rm -R /tmp2 && unset TMPDIR" + echo " } || note 'Skipping installation of $Nvidiacontainerfile'" + echo " ;;" + echo "esac" + echo "" + } + + echo "rocknroll || exit 64" + echo "" + + [ "$Switchcontaineruser" = "yes" ] && { + echo "# Create some helper scripts" + + echo "mkdir -p /usr/local/bin" + echo "" + + echo "echo \"#! /bin/sh +# Send messages to x11docker on host. +# To be sourced by other scripts. +$Messagefifofuncs_escaped +Messagefile=$(convertpath share $Messagefifo) +\" >/usr/local/bin/x11docker-message" + echo "" + + echo "echo \"#! /bin/sh +# User switch from root in containerrootrc to unprivileged user in containerrc. +# Additionally, su triggers logind and elogind. (Except su from busybox?) +# Called by x11docker-agetty. +. /usr/local/bin/x11docker-message +debugnote 'Running x11docker-login' +chmod +x $(convertpath share $Containerrc) +exec su - -s /bin/sh \$Containeruser $(convertpath share $Containerrc) +\" >/usr/local/bin/x11docker-login" + echo "chmod +x /usr/local/bin/x11docker-login" + echo "" + + echo "echo \"#! /bin/sh +# Run agetty to get a valid console. +# Needed at least for --interactive. +# Runs x11docker-login. +# Called at different places depending on init system. +. /usr/local/bin/x11docker-message +debugnote 'Running x11docker-agetty' +[ -e /sbin/agetty ] && exec agetty -a \$Containeruser -l /usr/local/bin/x11docker-login console +debugnote 'x11docker-agetty: agetty not found.' +[ '$Interactive' = 'yes' ] && note '/sbin/agetty not found. --interactive can fail. + Please install package util-linux in image.' +exec /usr/local/bin/x11docker-login +\" >/usr/local/bin/x11docker-agetty" + echo "chmod +x /usr/local/bin/x11docker-agetty" + echo "" + + echo "echo \"#! /bin/sh +# Wait for end of x11docker and shut down container. +# Started in background by x11docker for sysvinit|runit|openrc. +. /usr/local/bin/x11docker-message +debugnote 'Running x11docker-watch' +read Dummy <$(convertpath share $Timetosaygoodbyefifo) +echo timetosaygoodbye >>$(convertpath share $Timetosaygoodbyefifo) +debugnote 'x11docker-watch: $Initsystem shutdown now' +shutdown 0 +systemctl poweroff +openrc-shutdown --poweroff 0 +halt +halt -f +\" >/usr/local/bin/x11docker-watch" + echo "chmod +x /usr/local/bin/x11docker-watch" + echo "" + } + + case $Initsystem in + tini|none|dockerinit) ;; + systemd) + echo "# --init=systemd" + echo "# enable x11docker CMD service" + echo "systemctl unmask console-getty.service" + echo "systemctl enable console-getty.service" + echo "systemctl enable x11docker-journal.service" + echo "" + echo "systemctl unmask systemd-logind" + echo "systemctl enable systemd-logind" + echo "" + echo "# remove failing and annoying services" + echo "Unservicelist=' + apt-daily.service + apt-daily.timer + apt-daily-upgrade.service + apt-daily-upgrade.timer + bluetooth.service + cgproxy.service + deepin-anything-monitor.service + deepin-sync-daemon.service + display-manager.service + fprintd.service + gdm3.service + gvfs-udisks2-volume-monitor.service + hwclock_stop.service + lastore-daemon.service + lastore-update-metadata-info.service + lightdm.service + NetworkManager.service + plymouth-quit.service + plymouth-quit-wait.service + plymouth-read-write.service + plymouth-start.service + rtkit-daemon.service + sddm.service + systemd-localed.service + systemd-hostnamed.service + tracker-extract.service + tracker-miner-fs.service + tracker-store.service + tracker-writeback.service + udisks2.service + upower.service + '" + echo "for Service in \$(find /lib/systemd/system/* /usr/lib/systemd/user/* /etc/systemd/system/* /etc/systemd/user/*) ; do" + echo ' echo "$Unservicelist" | grep -q "$(basename $Service)" && {' + echo ' debugnote "--init=systemd: Removing $Service"' + echo ' rm $Service' + echo ' }' + echo "done" + echo "# Fix for Gnome 3" + echo 'sed -i "s/ProtectHostname=yes/ProtectHostname=no/" /lib/systemd/system/systemd-logind.service' + ;; + runit) + echo "# --init=runit" + echo "# create and enable x11docker service containing container command" + echo "mkdir -p /etc/sv/x11docker" + echo "mkdir -p /etc/runit/runsvdir/default" + echo "mkdir -p /etc/runit/1.d" + echo "mkdir -p /service" + echo "" + echo "echo \"#! /bin/sh +$(declare -f mysleep) +waitforservice() { + Service=\\\$1 + [ \\\"\\\$(sv check \\\$Service | cut -d: -f1)\\\" = 'ok' ] && { + echo \"x11docker: waiting for service \\\$Service ...\" + for Count in $(seq -s' ' 20); do + [ \\\"\\\$(sv status \\\$Service | cut -d: -f1)\\\" = 'down' ] && mysleep 0.2 || break + done + } +} +# make stderr visible +exec 2>&1 +# wait for all other services +echo 'Content of /etc/runit/runsvdir/default:' +ls -la /etc/runit/runsvdir/default/* +for Service in /etc/runit/runsvdir/default/* ; do waitforservice \\\$Service ;done +echo 'Current status of runit services:' +for Service in /etc/runit/runsvdir/default/* ; do sv status \\\$Service ;done +/usr/local/bin/x11docker-agetty +\" > /etc/sv/x11docker/run" + echo "chmod +x /etc/sv/x11docker/run" + echo "" + + echo "echo \"#! /bin/sh +sv down x11docker +runit-init 0 +init 0 +shutdown -h 0 +halt +\" > /etc/sv/x11docker/finish" + echo "chmod +x /etc/sv/x11docker/finish" + echo "" + + echo "ln -s /etc/sv/x11docker /etc/runit/runsvdir/default" #void + echo "ln -s /etc/sv/x11docker /service" #alpine + echo "" + + echo "[ -e /etc/runit/1 ] || echo '#!/usr/bin/env sh +set -eu +chmod 100 /etc/runit/stopit +/bin/run-parts --exit-on-error /etc/runit/1.d || exit 100 +' >/etc/runit/1" + echo "chmod +x /etc/runit/1" + echo "" + + echo "[ -e /etc/runit/2 ] || echo '#!/usr/bin/env sh +set -eu +runsvdir -P /service \"log: ..................................................................\" +' >/etc/runit/2" + echo "chmod +x /etc/runit/2" + echo "" + + echo '[ -e /etc/runit/3 ] || echo "#!/usr/bin/env sh +set -eu +exec 2>&1 +echo \"Waiting for services to stop...\" +sv -w196 force-stop /service/* +sv exit /service/* +# kill any other processes still running in the container +for ORPHAN_PID in $(ps --no-headers -eo \"%p,\" -o stat | tr -d \" \" | grep \"Z\" | cut -d, -f1); do + timeout 5 /bin/sh -c \"kill \$ORPHAN_PID && wait \$ORPHAN_PID || kill -9 \$ORPHAN_PID\" +done +" >/etc/runit/3' + echo "chmod +x /etc/runit/3" + echo "" + + echo "touch /etc/runit/stopit" + ;; + openrc) + echo "# --init=openrc" + echo "# Create and enable x11docker service containing container command" + echo "printf \"#!/sbin/openrc-run +name=x11docker +depend() { + after * +} +start() { + ebegin 'Starting containerrc' + /usr/local/bin/x11docker-agetty + openrc-shutdown --poweroff 0 + shutdown 0 + halt + halt -f + eend \$? +} +\" > /etc/init.d/x11docker.service" + echo "" + echo "chmod +x /etc/init.d/x11docker.service" + echo "rc-update add x11docker.service default" + echo "" + echo "# Tell openrc that it runs in docker container" + echo "sed -e 's/#rc_sys=\"\"/rc_sys=\"docker\"/g' -i /etc/rc.conf" + ;; + sysvinit) + echo "# --init=sysvinit" + echo "# Adding x11docker start command to rc.local" + echo "sed -i '/exit 0/d' /etc/rc.local" + echo "echo \"/usr/local/bin/x11docker-agetty || echo \\\"x11docker: Exit code of x11docker-agetty: \\\$?\\\" +echo 'x11docker: rc.local sends shutdown -h now' +shutdown -h now +exit 0\" >> /etc/rc.local" + echo "chmod +x /etc/rc.local" + ;; + esac + echo "" + + echo "# disable getty in inittab" + echo "[ -e /etc/inittab ] && sed -i 's/.*getty/##getty disabled by x11docker## \0/' /etc/inittab" + echo "" + + case $Dbussystem in + yes) + echo "# Set up DBus" + echo "command -v dbus-daemon && {" + echo ' Unservicelist=" + org.bluez + org.bluez.obex + org.freedesktop.hostname1 + org.freedesktop.network1 + org.freedesktop.resolve1 + org.freedesktop.secrets + org.freedesktop.systemd1 + org.freedesktop.timedate1 + org.freedesktop.Tracker1 + org.freedesktop.Tracker1.Miner.Extract + org.freedesktop.UDisks2 + org.freedesktop.UPower + org.gtk.vfs.UDisks2VolumeMonitor + org.opensuse.CupsPkHelper.Mechanism + com.deepin.daemon.Bluetooth + com.deepin.daemon.Grub2 + com.deepin.daemon.Power + com.deepin.lastore + com.deepin.lastore.Smartmirror + com.deepin.sync.Daemon + com.deepin.sync.Helper + com.deepin.userexperience.Daemon + "' + echo ' for Service in /usr/share/dbus-1/system-services/* /usr/share/dbus-1/services/*; do' # find is not available on fedora + echo ' Name="$(cat $Service | grep Name= | cut -d= -f2)"' + echo ' Command="$(cat $Service | grep Exec= | cut -d= -f2)"' + echo ' echo "$Unservicelist" | grep -q -w "$Name" && {' + echo ' debugnote "DBus: Removing $Name: $Service"' + echo ' rm $Service' + echo ' }' +# echo ' [ -e "$Service" ] && [ "$Command" != "/bin/false" ] && debugnote "DBus: Found $Name: $Command"' + echo ' case $Name in' + [ "$Initsystem" != "systemd" ] && { + echo ' org.freedesktop.systemd1|org.freedesktop.hostname1|org.freedesktop.locale1)' + echo ' debugnote "DBus: Removing $Name: $Service"' + echo ' rm "$Service"' + echo ' ;;' + } + echo ' org.freedesktop.login1)' + echo ' debugnote "DBus: Found login service $Name: $Command"' + [ "$Sharecgroup" = "no" ] && { + echo ' debugnote "DBus: $Name: Removing $Service"' + echo ' rm "$Service"' + echo ' echo "$Command" | grep -q elogind && {' + echo ' note "Found login service elogind.' + echo ' If you want to use it, enable option --sharecgroup."' + echo ' }' + } + echo ' ;;' + echo ' esac' + echo ' done' + echo '' + + case $Initsystem in + systemd) + echo " # Just assuming that a DBus service file will be present" + echo " systemctl unmask dbus" + echo " systemctl enable dbus" + ;; + sysvinit) + echo " echo '#!/bin/sh +### BEGIN INIT INFO +# Provides: dbus +# Required-Start: \$remote_fs \$syslog +# Required-Stop: \$remote_fs \$syslog +# Default-Start: 2 3 4 5 +# Default-Stop: +# Short-Description: D-Bus systemwide message bus +# Description: D-Bus is a simple interprocess messaging system, used +# for sending messages between applications. +### END INIT INFO +# -*- coding: utf-8 -*- +# Debian init.d script for D-BUS +# Copyright © 2003 Colin Walters +# Copyright © 2005 Sjoerd Simons +# +DAEMON=/usr/bin/dbus-daemon +UUIDGEN=/usr/bin/dbus-uuidgen +UUIDGEN_OPTS=--ensure +NAME=dbus +DAEMONUSER=messagebus +PIDDIR=/var/run/dbus +PIDFILE=\"\$PIDDIR/pid\" +DESC=\"system message bus\" +# +test -x \$DAEMON || exit 1 +. /lib/lsb/init-functions +# Source defaults file; edit that file to configure this script. +PARAMS="" +if [ -e /etc/default/dbus ]; then + . /etc/default/dbus +fi +create_machineid() { + # Create machine-id file + if [ -x \$UUIDGEN ]; then + \$UUIDGEN \$UUIDGEN_OPTS + fi +} +start_it_up() { + [ -d \$PIDDIR ] || { + mkdir -p \$PIDDIR + chown \$DAEMONUSER \$PIDDIR + chgrp \$DAEMONUSER \$PIDDIR + } + mountpoint -q /proc/ || { + log_failure_msg \"Cannot start \$DESC - /proc is not mounted\" + return 1 + } + [ -e \$PIDFILE ] && { + \$0 status > /dev/null && { + log_success_msg \"\$DESC already started; not starting.\" + return 0 + } + log_success_msg \"Removing stale PID file \$PIDFILE.\" + rm -f \$PIDFILE + } + create_machineid + log_daemon_msg \"Starting \$DESC\" \"\$NAME\" + start-stop-daemon --start --quiet --pidfile \$PIDFILE --exec \$DAEMON -- --system \$PARAMS + log_end_msg \$? +} +shut_it_down() { + log_daemon_msg \"Stopping \$DESC\" \"\$NAME\" + start-stop-daemon --stop --retry 5 --quiet --oknodo --pidfile \$PIDFILE --user \$DAEMONUSER + log_end_msg \$? + rm -f \$PIDFILE +} +reload_it() { + create_machineid + log_action_begin_msg \"Reloading \$DESC config\" + dbus-send --print-reply --system --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig > /dev/null + log_action_end_msg \$? +} +case \$1 in + start) start_it_up ;; + stop) shut_it_down ;; + reload|force-reload) reload_it ;; + restart) + shut_it_down + start_it_up + ;; + status) status_of_proc -p \$PIDFILE \$DAEMON \$NAME && exit 0 || exit \$? ;; +esac' > /etc/init.d/dbus" + echo " chmod +x /etc/init.d/dbus" + ;; + runit) + echo " [ -e /etc/sv/dbus ] || {" + echo " mkdir -p /etc/sv/dbus" + echo " echo '#!/bin/sh +[ ! -d /run/dbus ] && install -m755 -g 22 -o 22 -d /run/dbus +exec dbus-daemon --system --nofork --nopidfile' >/etc/sv/dbus/run" + echo " echo '#!/bin/sh +exec dbus-send --system / org.freedesktop.DBus.Peer.Ping > /dev/null 2> /dev/null' >/etc/sv/dbus/check" + echo " chmod +x /etc/sv/dbus/run /etc/sv/dbus/check" + echo " }" + echo " verbose 'DBus: enabling dbus service'" + echo " ln -s /etc/sv/dbus /etc/runit/runsvdir/default" # void + echo " ln -s /etc/sv/dbus /service" # alpine + ;; + openrc) + echo " echo '#!/sbin/openrc-run +start() { + ebegin \"Starting D-BUS system messagebus\" + /usr/bin/dbus-uuidgen --ensure=/etc/machine-id + mkdir -p /var/run/dbus + start-stop-daemon --start --pidfile /var/run/dbus.pid --exec /usr/bin/dbus-daemon -- --system + eend \$? +} +stop() { + ebegin \"Stopping D-BUS system messagebus\" + start-stop-daemon --stop --pidfile /var/run/dbus.pid + retval=\$? + eend \${retval} + [ -S /var/run/dbus/system_bus_socket ] && rm -f /var/run/dbus/system_bus_socket + return \${retval} +} +reload() { + ebegin \"Reloading D-BUS messagebus config\" + /usr/bin/dbus-send --print-reply --system --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig > /dev/null + retval=\$? + eend \${retval} + return \${retval} +}' >/etc/init.d/dbus && chmod +x /etc/init.d/dbus" + echo " verbose 'DBus: enabling dbus service'" + echo " rc-update add dbus default" + ;; + esac + echo "} || note 'DBus not found. + Can not run DBus system daemon. Please install dbus in image.'" + ;; + esac + echo "" + + echo "rocknroll || exit 64" + echo "" + + # --lang + while read Line; do + echo "# --lang: Language locale" + echo "verbose \"Searching for language locale matching $Line\"" + echo "Locales=\"\$(locale -a)\"" + echo "Langall=\"\$(cat /usr/share/i18n/SUPPORTED | grep -E 'UTF-8|utf8' | cut -d' ' -f1 | cut -d. -f1 | cut -d@ -f1 | sort | uniq)\"" + echo "Langland=\"\$(echo $Line | cut -d. -f1)\"" + echo "Langcontainer=''" + echo "" + echo "echo \"\$Langland\" | grep -q '_' || {" + echo " Langland=\"\$(echo \$Langland | tr '[:upper:]' '[:lower:]')_\$(echo \$Langland | tr '[:lower:]' '[:upper:]')\"" + echo " echo \"\$Langall\" | grep -q \"\$Langland\" || {" + echo " echo \"\$Langall\" | grep -i -q \"$Line\" && {" + echo " Langland=\"\$(echo \"\$Langall\" | grep -i -m1 \"$Line\")\"" + echo " }" + echo " }" + echo "}" + echo "" + echo "Langland=\"\$(echo \"\$Langland\" | cut -d_ -f1 | tr '[:upper:]' '[:lower:]')_\$(echo \"\$Langland\" | cut -d_ -f2 | tr '[:lower:]' '[:upper:]')\"" + echo "" + echo "echo \"\$Locales\" | grep -q \"\$Langland.UTF-8\" && Langcontainer=\"\$Langland.UTF-8\"" + echo "echo \"\$Locales\" | grep -q \"\$Langland.utf8\" && Langcontainer=\"\$Langland.utf8\"" + echo "" + echo "[ -z \"\$Langcontainer\" ] && {" + echo " [ -e /usr/share/i18n/SUPPORTED ] || note \"Option --lang: /usr/share/i18n/SUPPORTED not found. + Please install package 'locales' in image (belongs to glibc). + Look here to find a package for your image system: + https://github.com/mviereck/x11docker/wiki/dependencies#dependencies-in-image\"" + echo "" + echo " Langcontainer=\"\$Langland.utf8\"" + echo " note \"Option --lang: Generating language locale \$Langcontainer\"." + echo "" + echo " command -v localedef >/dev/null || note 'Option --lang: Command localedef not found in image. + Need it for language locale creation. + Look here to find a package for your image system: + https://github.com/mviereck/x11docker/wiki/dependencies#dependencies-in-image'" + echo " localedef --verbose --force -i \"\$Langland\" -f UTF-8 \$Langcontainer || verbose \"localedef exit code: \$?\"" + echo "" + echo " locale -a | grep -q \"\$Langcontainer\" || {" + echo " note \"Option --lang: Generation of locale \$Langcontainer failed.\"" + echo " Langcontainer=''" + echo " }" + echo "} || {" + echo " debugnote \"Option --lang: Found locale in image: \$Langcontainer\"" + echo "}" + echo "" + echo "[ \"\$Langcontainer\" ] && {" + echo ' storeinfo locale="$Langcontainer"' + echo " echo \"LANG=\$Langcontainer\" > /etc/default/locale" + echo "} || note 'Option --lang: Desired locale for '$Line' not found and not generated.'" + echo "" + done < <(tac <<< "$Langwunsch" | grep . ||:) + [ "$Langwunsch" ] && { + echo "debugnote \"Option --lang: Output of locale -a:" + echo "\$(locale -a)\"" + echo "" + echo "rocknroll || exit 64" + echo "" + } + + [ "$Dbussystem" = "yes" ] && { + case $Initsystem in + tini|none|dockerinit) echo "dbus-daemon --system --fork" ;; + esac + } + + # --runasroot command added here + [ "$Runasroot" ] && { + echo "# Custom setup root command added with option --runasroot" + echo "$Runasroot" + echo "" + } + + echo "storeinfo containerrootrc=ready" # signal for containerrc + echo "" + + [ "$Switchcontaineruser" = "yes" ] && { # if "no", containerrc is executed in command line $Dockercommand + echo "# --init=$Initsystem" + case $Initsystem in + none|dockerinit) +# echo "exec /usr/local/bin/x11docker-login" + echo "exec /usr/local/bin/x11docker-agetty" + ;; + tini) +# echo "exec $Tinicontainerpath -- /usr/local/bin/x11docker-agetty" + echo "exec /usr/local/bin/x11docker-agetty" + ;; + sysvinit) + echo "/usr/local/bin/x11docker-watch &" + echo "exec /sbin/init" + ;; + openrc) + echo "/usr/local/bin/x11docker-watch &" + echo "command -v openrc-init && exec openrc-init || exec /sbin/init" + ;; + runit) + echo "/usr/local/bin/x11docker-watch &" + echo "[ -e /sbin/runit-init ] && exec runit-init || exec /sbin/init" + ;; + s6-overlay) + echo "exec /init /usr/local/bin/x11docker-agetty" + ;; + systemd) + echo 'Systemd=/lib/systemd/systemd' + echo '[ -e "$Systemd" ] || Systemd=/bin/systemd' + echo '[ -e "$Systemd" ] || Systemd=/sbin/systemd' + echo '[ -e "$Systemd" ] || {' + echo ' command -v systemctl && {' + echo ' warning "Executeable for systemd not found. Will try /sbin/init"' + echo ' Systemd=/sbin/init' + echo ' } || error "systemd not found in image (option --init=systemd)."' + echo '}' + echo 'exec $Systemd' + ;; + esac + } + return 0 +} >> $Containerrootrc +create_dockerrc() { ### create dockerrc: This script runs as root (or member of group docker) on host. Also creates containerrc + # create containerrc -> runs as unprivileged user in container + # check and set up cgroup on host for systemd or elogind + # run docker + local Line= Wantcgroup= Path= Ungrep= + + echo "#! /usr/bin/env bash" + echo "" + echo "# dockerrc:" + echo "# This script runs as root (or member of group docker) on host." + echo "# - inspect image" + echo "# - pull image if needed" + echo "# - create containerrc" + echo "# - set up systemd/elogind cgroup if needed" + echo "# - run window manager in container or from host if needed" + echo "" + echo "trap '' SIGINT" + echo "" + + declare -f askyesno + declare -f checkpid + declare -f escapestring + declare -f mysleep + declare -f pspid + declare -f rmcr + declare -f rocknroll + declare -f saygoodbye + declare -f storeinfo + declare -f storepid + declare -f waitforlogentry + echo "$Messagefifofuncs" + echo "" + + [ "$Winsubsystem" = "MSYS2" ] && { + echo "# avoid path conversion in MSYS2 commands" + echo "export MSYS2_ARG_CONV_EXCL='*'" + echo "" + } + + echo "Containercommand=\"$Containercommand\"" + echo "Imagename=\"$Imagename\"" + echo "Messagefile='$Messagefifo'" + echo "Newxenv='$Newxenv'" + echo "export PATH='$PATH'" + echo "Storeinfofile='$Storeinfofile'" + echo "Storepidfile='$Storepidfile'" + echo "Timetosaygoodbyefile='$Timetosaygoodbyefile'" + echo "Timetosaygoodbyefifo='$Timetosaygoodbyefifo'" + echo "Xserver='$Xserver'" + echo "Workdir='$Workdir'" + echo "" + echo "Containerarchitecture=" + echo "Containerid=" + echo "Containerip=" + echo "Dockerlogspid=''" + echo "Dockerpull=" + echo "Exec=" + echo "Entrypoint=" + echo "Failure=" + echo "Imageuser=" + echo "Inspect=" + echo "Line=" + echo "Pid1pid=" + echo "Runtime=" + echo "Signal=" + echo "Windowmanagermode=" + echo "Windowmanagercommand=" + echo "Wmcontainerid=" + echo "Wmdockercommand=" + + echo "debugnote 'Running dockerrc: Setup as root or as user docker on host.'" + [ "$Debugmode" = "yes" ] && { + echo "PS4='+ dockerrc: \$(date +%S+%3N) '" + #echo "set -x" + #declare -f traperror | sed 's/Command/dockerrc: Command/' + echo "traperror() { # trap ERR: --debug: Output for 'set -o errtrace'" + echo ' debugnote "dockerrc: Command at Line ${2:-} returned with error code ${1:-}: + ${4:-} + ${3:-} - ${5:-}"' + echo " saygoodbye dockerrc-traperror" + echo " exit 64" + echo "}" + echo "set -Eu" + echo "trap 'traperror \$? \$LINENO \$BASH_LINENO \"\$BASH_COMMAND\" \$(printf \"::%s\" \${FUNCNAME[@]})' ERR" + } + echo "" + + # transfer DOCKER_* environment variables, e.g. DOCKER_HOST. + # can get lost e.g. if using --pw=sudo or --pw=pkexec + while read Line; do + debugnote "dockerrc: Found docker environment variable: $Line" + echo "export '$Line'" + done < <(env | grep -e '^DOCKER_' ||:) + echo "" + + echo "# Check whether docker daemon is running, get docker info" + echo "$Dockerexe info >>$Dockerinfofile 2>>$Containerlogfile || { + error \"Calling docker daemon failed. + Is docker daemon running at all? + Try to start docker daemon with: systemctl start docker + Last lines of log: +\$(rmcr < '$Containerlogfile' | tail)\" +}" + echo "" + + + echo "# Check default runtime" + echo "Runtime=\"\$( { grep 'Default Runtime' < '$Dockerinfofile' ||: ;} | awk '{print \$3}' )\"" + echo "debugnote \"dockerrc: Found default Runtime: \$Runtime\"" + echo "debugnote \"dockerrc: All \$(grep 'Runtimes' < '$Dockerinfofile' ||: )\"" + echo "[ \"\$Runtime\" != '$Runtime' ] && {" + echo " case \$Runtime in" + echo " kata-runtime) warning 'Found default docker runtime kata-runtime. + Please run x11docker with --runtime=kata-runtime to avoid issues.' ;;" + echo " nvidia) [ '$Sharegpu' = 'yes' ] && warning 'Option --gpu: Found default docker runtime nvidia. + Please run x11docker with --runtime=nvidia to avoid issues.' ;;" + echo " runc|crun|oci) ;;" + echo " *) note \"Found unknown container runtime: \$Runtime + Please report at: https://github.com/mviereck/x11docker\" ;;" + echo " esac" + [ "$Runtime" ] && echo " Runtime='$Runtime'" + echo "}" + echo "debugnote \"dockerrc: Container Runtime: \$Runtime\"" + echo "storeinfo \"runtime=\$Runtime\"" + echo "" + + echo "# Refresh images.list for x11docker-gui" + echo "$Dockerexe images 2>>$Containerlogfile | grep -v REPOSITORY | awk '{print \$1 \":\" \$2}' >>$Dockerimagelistfile.sort" + echo "rmcr $Dockerimagelistfile.sort" + echo "while read -r Line ; do" + echo ' grep -q "" <<<$Line || echo $Line >> '$Dockerimagelistfile + echo "done < <(sort < $Dockerimagelistfile.sort)" + echo "rm $Dockerimagelistfile.sort" + echo "" + + echo "# Check if image $Imagename is available locally" + echo "Dockerpull=no" + case $Pullimage in + no) ;; + always) echo "Dockerpull=yes" ;; + yes) echo "$Dockerexe inspect $Imagename >>$Containerlogfile 2>&1 || Dockerpull=yes" ;; + ask) + [ "$Runsinterminal" = "yes" ] && { + echo "grep -x -q '$Imagename' < $Dockerimagelistfile || grep -x -q '$Imagename:latest' < $Dockerimagelistfile || {" + echo " $Dockerexe inspect $Imagename >>$Containerlogfile 2>&1 || {" + echo " echo 'Image $Imagename not found locally.' >&2" + echo " echo 'Do you want to pull it from docker hub?' >&2" + echo " askyesno && Dockerpull=yes || error \"Image '$Imagename' not available locally and not pulled from docker hub.\"" + echo " }" + echo "}" + } + ;; + esac + echo "" + + echo "rocknroll || exit 64" + echo "" + + echo "[ \"\$Dockerpull\" = 'yes' ] && {" + echo " note \"Pulling image '$Imagename' from docker hub\"" + [ "$Runsinterminal" = "no" ] && case $Passwordneeded in + no) echo " env DISPLAY='$Hostdisplay' DBUS_SESSION_BUS_ADDRESS='${DBUS_SESSION_BUS_ADDRESS:-}' bash -c \"notify-send 'x11docker: Pulling image $Imagename from docker hub'\" 2>/dev/null &" ;; + yes) echo " env DISPLAY='$Hostdisplay' DBUS_SESSION_BUS_ADDRESS='${DBUS_SESSION_BUS_ADDRESS:-}' su '$Hostuser' -c \"notify-send 'x11docker: Pulling image $Imagename from docker hub'\" 2>/dev/null &" ;; + esac + echo " $Sudo $Dockerexe pull $Imagename 1>&2 || error \"Pulling docker image '$Imagename' seems to have failed!\"" + echo "}" + echo "" + + echo "rocknroll || exit 64" + echo "" + + echo "Inspect=\"\$($Dockerexe inspect $Imagename --format='{{.Config.Entrypoint}}{{.Config.Cmd}}[{{.Config.User}}][{{.Config.WorkingDir}}][{{.Architecture}}]')\"" + echo "" + + echo "# Check architecture" + echo "Containerarchitecture=\"\$(cut -d[ -f6 <<< \"\$Inspect\" | cut -d] -f1)\"" + echo "debugnote \"dockerrc: Image architecture: \$Containerarchitecture\"" + + echo "# Check CMD" + echo "[ -z \"\$Containercommand\" ] && {" + echo " # extract image command from image if not given on cli" + echo " Containercommand=\"\$(cut -d] -f2 <<< \"\$Inspect\" | cut -d[ -f2)\"" + echo " debugnote \"dockerrc: Image CMD: \$Containercommand\"" + echo " echo \"\$Containercommand\" | grep -q $(convertpath share $Containerrc) && error 'Recursion error: Found CMD $(convertpath share $Containerrc) in image. + Did you use docker commit with an x11docker container? + Please build new images with a Dockerfile instead of using docker commit, + or provide a different container command.'" + echo "}" + echo "" + + + echo "# Check USER" + echo "Imageuser=\"\$(cut -d[ -f4 <<< \"\$Inspect\" | cut -d] -f1)\"" + echo "debugnote \"dockerrc: Image USER: \$Imageuser\"" + case $Createcontaineruser in + yes) + echo "[ \"\$Imageuser\" ] && note \"Found 'USER \$Imageuser' in image." + echo " If you want to run with user \$Imageuser instead of host user $Containeruser," + echo " than run with --user=RETAIN.\"" + echo "storeinfo containeruser=\"$Containeruser\"" + ;; + no) + echo 'storeinfo containeruser="${Imageuser:-root}"' + ;; + esac + echo "" + + case $Noentrypoint in + yes) echo "Entrypoint=" ;; + no) + echo "# Check ENTRYPOINT" + echo "Entrypoint=\"\$(cut -d] -f1 <<< \"\$Inspect\" | cut -d[ -f2)\"" + echo "debugnote \"dockerrc: Image ENTRYPOINT: \$Entrypoint\"" + case $Initsystem in + systemd|sysvinit|runit|openrc|tini) + echo "echo \"\$Entrypoint\" | grep -qE 'tini|init|systemd' && {" + echo " note \"There seems to be an init system in ENTRYPOINT of image: + \$Entrypoint + Will disable it as x11docker already runs an init with option --$Initsystem. + To allow this ENTRYPOINT, run x11docker with option --init=none.\"" + echo " Entrypoint=" + echo "}" + #echo "Exec=exec" + ;; + s6-overlay) + echo "[ \"\$Entrypoint\" = '/init' ] && {" + echo " Entrypoint=" + echo " [ \"\$Containercommand\" ] || Containercommand=\"sh -c 'while :; do sleep 10; done'\"" + echo "}" + ;; + none) + echo "echo \"\$Entrypoint\" | grep -qE 'tini|init|systemd' && {" + echo " note \"There seems to be an init system in ENTRYPOINT of image: + \$Entrypoint + Returning exit code of container command will fail.\"" + echo " Exec=exec" + echo "}" + ;; + esac + ;; + esac + echo "" + + [ -z "$Workdir" ] && { + echo "# Check WORKDIR" + echo "Workdir=\"\$(cut -d[ -f5 <<< \"\$Inspect\" | cut -d] -f1)\"" + echo "debugnote \"dockerrc: Image WORKDIR: \$Workdir\"" + echo "[ \"\$Workdir\" ] && note \"Found 'WORKDIR \$Workdir' in image. + You can change it with option --workdir=DIR.\"" + echo "" + } + + echo "[ -z \"\$Containercommand\$Entrypoint\" ] && error 'No container command specified and no CMD or ENTRYPOINT found in image.'" + echo "" + + echo "######## Create $(basename $Containerrc) ########" + echo "" + echo "{ echo '#! /bin/sh'" +# [ "$Debugmode" = "yes" ] && echo "echo 'set -x'" + echo " echo ''" + echo " echo '# $(basename $Containerrc)'" + echo " echo '# Created startscript for docker run used as container command.'" + echo " echo '# Runs as unprivileged user in container.'" + echo " echo ''" + + +# echo " echo 'exec 6>&1 7>&2'" +# echo " echo 'exec >>$(convertpath share $Containerlogfile) 2>&1'" + echo " echo ''" + + echo " echo '$(declare -f mysleep)'" + echo " echo '$(declare -f rocknroll)'" + echo " echo '$(declare -f saygoodbye)'" + echo " echo '$(declare -f storeinfo)'" + echo " echo '$(declare -f waitforlogentry)'" + echo " echo '$Messagefifofuncs'" + echo " echo 'Messagefile=$(convertpath share $Messagefifo)'" + echo " echo 'Storeinfofile=$(convertpath share $Storeinfofile)'" + echo " echo 'Timetosaygoodbyefile=$(convertpath share $Timetosaygoodbyefile)'" + echo " echo ''" + + echo " echo 'waitforlogentry $(basename $Containerrc) \$Storeinfofile containerrootrc=ready "" infinity'" + + echo " echo 'debugnote \"Running $(basename $Containerrc): Unprivileged user commands in container\"'" + echo " echo ''" + + echo ' echo "Containercommand=\"$Containercommand\""' + echo ' echo "Entrypoint=\"$Entrypoint\""' + echo " echo ''" + echo " echo 'verbose \"$(basename $Containerrc): Container system:'" + echo " echo '\$(cat /etc/os-release 2>&1 ||:)\"'" + echo " echo ''" + + echo "} >> $Containerrc" + + [ "$Switchcontaineruser" = "yes" ] && { ### FIXME try --format '{{json .ContainerConfig.Env}}' + echo "echo '# Environment variables found in image:' >> $Containerrc" + echo "IFS=$'\n'" + echo "while read -r Line; do" + echo " echo \"export \$(escapestring \"\$Line\")\" >> $Containerrc" + echo "done < <($Dockerexe run --rm --entrypoint env $Imagename env 2>>$Containerlogfile | rmcr | grep -v 'HOSTNAME=' )" + echo "IFS=$' \t\n'" + } + + echo "{" + echo " echo ''" + echo " echo '# USER and HOME'" + echo " echo 'Containeruser=\"\$(storeinfo dump containeruser)\"'" + case $Createcontaineruser in + yes) + echo " echo 'Containeruserhome=\"$Containeruserhome\"'" + ;; + no) + case $Sharehome in + no) + echo " echo 'Containeruserhome=\"\$(cat /etc/passwd | grep \"\$Containeruser:.:\" | cut -d: -f6)\"'" + echo " echo 'Containeruserhome=\"\${Containeruserhome:-/tmp/\$Containeruser}\"'" + echo " echo 'mkdir -p \"\$Containeruserhome\"'" + ;; + volume) + echo " echo 'Containeruserhome=\"$Containeruserhome\"'" + ;; + esac + ;; + esac + echo " echo 'export USER=\"\$Containeruser\"'" + echo " echo 'export HOME=\"\$Containeruserhome\"'" + echo " echo ''" + + echo " echo '# XDG_RUNTIME_DIR'" + echo " echo 'Containeruseruid=\$(id -u \$Containeruser)'" + echo " echo 'export XDG_RUNTIME_DIR=/tmp/XDG_RUNTIME_DIR'" + echo " echo '[ -e /run/user/\$Containeruseruid ] && ln -s /run/user/\$Containeruseruid \$XDG_RUNTIME_DIR || mkdir -p -m700 \$XDG_RUNTIME_DIR'" + echo " echo ''" + + while read -r Line; do + Path="$(convertpath container "$Line")" + [ "$(cut -c1-5 <<< "$Line")" != "/dev/" ] && { + case "$Line" in + "$Containeruserhosthome") + echo " echo '# --share: create soft link of shared $Containeruserhosthome to container home'" + echo " echo 'Homesoftlink=\"\$Containeruserhome/home.host.$Containeruser\"'" + echo " echo '[ -e \"\$Containeruserhome/home.host.$Containeruser\" ] || ln -s \"/home.host/\$Containeruser\" \"\$Homesoftlink\"'" + echo " echo ''" + Ungrep="$Ungrep|home.host.$Containeruser" + ;; + *) + Line="$(convertpath container "$Line")" + [ "${Line#$Containeruserhome}" = "$Line" ] || Ungrep="$Ungrep|$(basename "$Line")" + ;; + esac + } + done < <(store_runoption dump volume) + + echo " echo '# Copy files from /etc/skel into empty HOME'" + echo " echo '[ -d /etc/skel ] && [ -z \"\$(ls -A \"\$Containeruserhome\" 2>/dev/null | grep -v -E \"gnupg$Ungrep\")\" ] && {'" + echo " echo ' debugnote \"$(basename $Containerrc): HOME is empty. Copying from /etc/skel\"'" + echo " echo ' cp -n -R /etc/skel/. \$Containeruserhome'" + echo " echo ' :'" + echo " echo '} || {'" + echo " echo ' debugnote \"$(basename $Containerrc): HOME is not empty. Not copying from /etc/skel\"'" + echo " echo '}'" + echo " echo ''" + + [ -n "$Newdisplay" ] && { + echo " echo '# Create softlink to X unix socket'" + echo " echo '[ -e /tmp/.X11-unix/X$Newdisplaynumber ] || ln -s /X$Newdisplaynumber /tmp/.X11-unix'" + echo " echo ''" + } + + [ "$Dbusrunsession" = "yes" ] && { + echo " echo '# Check for dbus user daemon command'" + echo " echo 'command -v dbus-run-session >/dev/null && Dbus=dbus-run-session || note \"Option --dbus: dbus seems to be not installed. + Cannot run a DBus user session. Please install package dbus in image.\"'" + echo " echo ''" + } + + case $Xserver in + --tty) + echo " echo 'unset DISPLAY WAYLAND_DISPLAY XAUTHORITY'" ;; + --weston|--kwin|--hostwayland) + echo " echo 'unset DISPLAY XAUTHORITY'" ;; + *) + echo " echo 'unset WAYLAND_DISPLAY'" ;; + esac + echo " echo ''" + [ "$Setupwayland" = "yes" ] && { + echo " echo '# Wayland environment'" + echo " echo 'export WAYLAND_DISPLAY=$Newwaylandsocket'" + echo " echo 'ln -s /$Newwaylandsocket \$XDG_RUNTIME_DIR/$Newwaylandsocket'" + } || { + echo " echo 'export XDG_SESSION_TYPE=x11'" + } + echo " echo ''" + + echo " echo ''" + echo " echo 'export TERM=xterm'" + + echo " echo 'storeinfo test locale && export LANG=\"\$(storeinfo dump locale)\"'" + + echo " echo '[ -e \"$Hostlocaltimefile\" ] || export TZ=$Hostutctime'" + echo " echo '[ \"\$(date -Ihours)\" != \"$(date -Ihours)\" ] && export TZ=$Hostutctime'" + + echo " echo '[ \"\$DEBIAN_FRONTEND\" = noninteractive ] && unset DEBIAN_FRONTEND && export DEBIAN_FRONTEND'" + echo " echo '[ \"\$DEBIAN_FRONTEND\" = newt ] && unset DEBIAN_FRONTEND && export DEBIAN_FRONTEND'" + + echo " echo '# container environment (--env)'" + while read -r Line ; do ### FIXME '\\\' not transmitted + echo " echo \"export '$Line'\"" +# echo "$Line" >&2 +# echo " echo \"export $(escapestring "$Line")\"" +# echo " echo \"export $(escapestring "$Line")\"" >&2 +# echo " echo \"export \\\"$(escapestring "$Line")\\\"\"" >&2 +# echo " echo \"export \\\"$(escapestring "$Line")\\\"\"" + done < <(store_runoption dump env) + echo " echo ''" + + [ "$Xauthentication" = "yes" ] || echo " echo 'unset XAUTHORITY && export XAUTHORITY'" + + echo " echo 'env >> $(convertpath share $Containerenvironmentfile)'" + echo " echo 'verbose \"Container environment:'" + echo " echo '\$(env | sort)\"'" + echo " echo ''" + + echo " echo 'cd \"\$HOME\"'" + echo ' [ "$Workdir" ] && echo "[ -d \"$Workdir\" ] && cd \"$Workdir\" # WORKDIR in image"' + echo " echo ''" + + [ "$Initsystem" = "systemd" ] && { + echo " echo 'systemctl --user start dbus'" + echo " echo ''" + } + + case $Interactive in + no) + echo " echo 'tail -f $(convertpath share $Cmdstdoutlogfile) 2>/dev/null &'" + echo " echo 'tail -f $(convertpath share $Cmdstderrlogfile) >&2 2>/dev/null &'" + echo " echo \"exec \\\$Dbus sh $(convertpath share $Cmdrc) >>$(convertpath share $Cmdstdoutlogfile) 2>>$(convertpath share $Cmdstderrlogfile)\"" + ;; + yes) + echo " echo \"\$Exec \\\$Dbus \$Entrypoint \$Containercommand\"" + ;; + esac + echo "} >> $Containerrc" + echo "######## End of containerrc ########" + echo "" + + echo "# Write containerrc into x11docker.log" + echo "nl -ba >> $Logfile < $Containerrc" + echo "" + + + + echo "######## Create $(basename $Cmdrc) ########" + echo "{ echo '#! /bin/sh'" + echo " echo '# Created startscript for cmdrc containing final container command'" + echo " echo ''" + echo " echo '$(declare -f storeinfo)'" + echo " echo '$Messagefifofuncs'" + echo " echo 'Messagefile=$(convertpath share $Messagefifo)'" + # --runasuser commands added here + [ "$Runasuser" ] && { + echo " echo '# Custom daemon commands added with option --runasuser'" + for Line in "$Runasuser"; do + echo " echo 'debugnote \"$(basename $Cmdrc): Adding command: + $Line\"'" + echo " echo '$Line'" + done + echo " echo ''" + } + echo " echo \"debugnote \\\"$(basename $Cmdrc): Running container command: + \$Entrypoint \$Containercommand + \\\"\"" + echo " echo ''" + echo " echo \"\$Entrypoint \$Containercommand $( [ "$Forwardstdin" = "yes" ] && echo "<$(convertpath share $Cmdstdinfifo)" ) \"" + echo " echo ''" + echo " echo '[ -h \"\$Homesoftlink\" ] && rm \$Homesoftlink'" + echo " echo \"storeinfo cmdexitcode=\\\$?\"" + echo "} >> $Cmdrc" + echo "######## End of cmdrc ########" + echo "" + + echo "# Write cmdrc into x11docker.log" + echo "nl -ba >> $Logfile < $Cmdrc" + echo "" + + + # check [and create] cgroup mountpoint for systemd or elogind + [ "$Sharecgroup" = "yes" ] && [ "$Dbussystem" = "yes" ] && { + [ "$Initsystem" = "systemd" ] && Wantcgroup=systemd || Wantcgroup=elogind + findmnt /sys/fs/cgroup/$Wantcgroup >/dev/null || { + echo "# Check [and create] cgroup mountpoint for $Wantcgroup" + echo "[ '$Wantcgroup' = 'systemd' ] || $Dockerexe run --rm --entrypoint env $Imagename sh -c 'ls /lib/elogind/elogind || ls /usr/sbin/elogind|| ls /usr/libexec/elogind' && {" + echo ' [ "$(id -u)" = "0" ] && note "Creating cgroup mountpoint for '$Wantcgroup'."' + echo ' [ "$(id -u)" != "0" ] && {' + echo " note 'Want to create and mount a cgroup for $Wantcgroup. + As x11docker currently does not run as root, this will probably fail. + Please either run x11docker as root, or run with option --pw=su or --pw=sudo. + + Alternatively, create cgroup mountpoint yourself with: + mkdir -p /sys/fs/cgroup/$Wantcgroup + mount -t cgroup cgroup /sys/fs/cgroup/$Wantcgroup -o none,name=$Wantcgroup + + If you get a read-only error message, remove write protection with: + mount -o remount,rw cgroup /sys/fs/cgroup + + You can restore write protection after cgroup creation with: + mount -o remount,ro cgroup /sys/fs/cgroup'" + [ "$Wantcgroup" = "elogind" ] && echo "note 'If you do not want or need elogind in container, + just ignore message above.'" + echo " }" + findmnt /sys/fs/cgroup -O ro >/dev/null && { + echo " mount -o remount,rw cgroup /sys/fs/cgroup >>$Containerlogfile 2>&1" + echo " Remounted=yes" + } + echo " mkdir -p /sys/fs/cgroup/elogind >>$Containerlogfile 2>&1" + echo " mount -t cgroup cgroup /sys/fs/cgroup/elogind -o none,name=elogind >>$Containerlogfile 2>&1" + echo ' [ "${Remounted:-}" = "yes" ] && mount -o remount,ro cgroup /sys/fs/cgroup >>'$Containerlogfile' 2>&1' + echo "}" + echo "" + } + } + + echo "# Send signal to run X and wait for X to be ready" + echo 'storeinfo readyforX=ready' + echo "waitforlogentry 'dockerrc' $Xinitlogfile 'xinitrc is ready' '$Xiniterrorcodes'" + echo "" + + echo "rocknroll || exit 64" + echo "" + + [ "$Windowmanagermode" = "none" ] || { + echo "# run window manager (in image or from host)" + echo "Windowmanagermode=\"$Windowmanagermode\"" + echo "Windowmanagercommand=\"$Windowmanagercommand\"" + echo "Wmdockercommand=\"$Wmdockercommand\"" + echo '[ "$Windowmanagermode" = "container" ] && {' + echo " $Dockerexe inspect \"\$(cut -d' ' -f1 <<<\"\$Windowmanagercommand\")\" >>$Containerlogfile 2>&1 && {" + echo ' Wmdockercommand="$Wmdockercommand \ + -- $Windowmanagercommand"' + echo ' debugnote "dockerrc: Window manager container: Generated docker command: +$Wmdockercommand"' + echo " note \"Option --wm: Starting window manager image: $Windowmanagercommand\"" + echo ' Wmcontainerid="$(eval $Wmdockercommand)"' + echo ' [ "$Wmcontainerid" ] && {' + echo ' debugnote "dockerrc: Window manager container: $Wmcontainerid"' + echo " for ((Count=1 ; Count<=10 ; Count++)); do" + echo " Pid1pid=\"\$($Dockerexe inspect --format '{{.State.Pid}}' \$Wmcontainerid 2>>$Containerlogfile | rmcr)\"" + echo " debugnote \"dockerrc: Window manager container: \$Count. check for PID 1: \$Pid1pid\"" + case $Mobyvm in + no) echo ' checkpid "$Pid1pid" && break' ;; + yes) echo ' [ "$Pid1pid" ] && [ "$Pid1pid" != "0" ] && break' ;; + esac + echo " rocknroll || exit 64" + echo " mysleep 0.2" + echo " done" + echo ' }' + echo ' checkpid "$Pid1pid" && storepid "$Pid1pid" wmcontainerpid1' + echo ' checkpid "$Pid1pid" || { note "Option --wm: Failed to run window manager image: $Windowmanagercommand." && Windowmanagermode=host ; }' + echo ' } || {' + echo " note \"Option --wm: Did not find window manager image + \$(cut -d' ' -f1 <<<\"\$Windowmanagercommand\") + to provide a containerized window manager. Please run: + docker pull x11docker/openbox + If you want to use a host window manager instead and avoid this warning, + use option --wm=host or --wm=COMMAND + or provide a local image with e.g. --wm=x11docker/fvwm + To run without a window manager: --wm=none or --desktop + Fallback: Will try to run a host window manager: $Hostwindowmanager\"" + echo " Windowmanagermode=host" + echo " }" + echo "}" + echo '[ "$Windowmanagermode" = "host" ] && {' + echo " command -v $Hostwindowmanager >/dev/null || note 'Did not find a host window manager. + Please pull image x11docker/openbox or provide a recommended one: + $Wm_recommended_nodesktop_light'" + echo " note 'Option --wm: Starting host window manager: ${Hostwindowmanager:-WM_NOT_FOUND}'" + echo " [ \"\$(id -u)\" = '0' ] && su $Hostuser -c 'env $Newxenv ${Hostwindowmanager:-NO_WM_FOUND} >>$Xinitlogfile 2>&1 & storepid \$! windowmanager' || \\" + echo " env $Newxenv ${Hostwindowmanager:-NO_WM_FOUND} >>$Xinitlogfile 2>&1 & storepid \$! windowmanager" + echo '}' + } + echo "" + + echo "rocknroll || exit 64" + echo "" + + echo "" + echo "#### run docker image ####" + case $Interactive in + no) +# echo "read Containerid < <($Dockercommand 2>>$Containerlogfile | rmcr)" + echo "read Containerid < <($Dockercommand | rmcr)" + ;; + yes) + [ "$Winpty" ] && echo "$Winpty bash $Dockercommandfile <&0 &" || echo "$Dockercommand <&0 &" + echo "Containerid=$Containername" + ;; + esac + echo "##########################" + echo "" + echo "" + + echo "[ \"\$Containerid\" ] || { + error \"Startup of docker failed. Did not receive a container ID. + + Last lines of container log: +\$(rmcr < $Containerlogfile | tail)\" +}" + echo 'storeinfo containerid="$Containerid"' + + echo "# Wait for container to be ready" + echo "for ((Count=1 ; Count<=40 ; Count++)); do" + echo " $Dockerexe exec $Containername sh -c : 2>&1 | rmcr >>$Containerlogfile && { debugnote 'dockerrc: Container is up and running.' ; break ; } || debugnote \"dockerrc: Container not ready on \$Count. attempt, trying again.\"" + echo " rocknroll || exit 64" + echo " mysleep 0.1" + echo "done" + echo "" + + [ "$Containersetup" = "no" ] && { +# echo "$Dockerexe logs -f \$Containerid >> $Containerlogfile 2>&1 &" + echo "# Store container output separated for stdout and stderr" + echo "$Dockerexe logs -f \$Containerid 1>>$Cmdstdoutlogfile 2>>$Cmdstderrlogfile &" + echo "Dockerlogspid=\$!" + echo "storepid \$Dockerlogspid dockerlogs" + echo "" + } + + echo "# Wait for pid 1 in container" + echo "for ((Count=1 ; Count<=40 ; Count++)); do" + echo " Pid1pid=\"\$($Dockerexe inspect --format '{{.State.Pid}}' $Containername 2>>$Containerlogfile | rmcr)\"" + echo " debugnote \"dockerrc: \$Count. check for PID 1: \$Pid1pid\"" + case $Mobyvm in + no) echo ' checkpid "$Pid1pid" && break' ;; + yes) echo ' [ "$Pid1pid" ] && [ "$Pid1pid" != "0" ] && break' ;; + esac + echo " rocknroll || exit 64" + echo " mysleep 0.1" + echo "done" + echo '[ "$Pid1pid" = "0" ] && Pid1pid=""' + + echo '[ -z "$Pid1pid" ] && error "dockerrc(): Did not receive PID of PID1 in container. + Maybe the container immediately stopped for unknown reasons. + Just in case, check if host and image architecture are compatible: + Host architecture: '$Hostarchitecture', image architecture: $Containerarchitecture. + Output of \"docker ps | grep x11docker\": +$('$Dockerexe' ps | grep x11docker) + + Content of container log: +$(rmcr < '$Containerlogfile' | uniq )"' + echo 'storeinfo pid1pid="$Pid1pid"' + echo "" + + echo "# Get IP of container" + echo "Containerip=\"\$($Dockerexe inspect --format '{{ .NetworkSettings.IPAddress }}' $Containername 2>>$Containerlogfile)\"" + echo 'storeinfo containerip=$Containerip' + echo "" + + echo "# Check log for startup failure" + echo "Failure=\"\$(rmcr < $Containerlogfile | grep -v grep | grep -E 'Error response from daemon|OCI runtime exec' ||:)\"" + echo "[ \"\$Failure\" ] && {" + echo " echo \"\$Failure\" >>$Containerlogfile" + echo " error \"Got error message from docker daemon: +\$Failure + + Last lines of logfile: +\$(tail $Containerlogfile)\"" + echo "}" + echo "" + + [ "$Switchcontaineruser" = "no" ] && [ "$Containersetup" = "yes" ] && { + echo "debugnote 'dockerrc(): Starting containerrootrc with privileged docker exec'" + echo "# copy containerrootrc inside of container to avoid possible noexec of host home." + echo "$Dockerexe exec --privileged $Containername sh -c 'cp $(convertpath share $Containerrootrc) /tmp/containerrootrc ; chmod 644 /tmp/containerrootrc' 2>&1 | rmcr >>$Containerlogfile" + echo "# run container root setup. containerrc will wait until setup script is ready." + echo "$Dockerexe exec --privileged -u root $Containername /bin/sh /tmp/containerrootrc 2>&1 | rmcr >>$Containerlogfile" + echo "" + } + + echo "storeinfo dockerrc=ready" + echo "" + + case $Mobyvm in + no) + echo '[ "$Containerid" ] || [ "$Wmcontainerid" ] && {' + echo " # wait for signal of finish()" + echo " read Signal <$Dockerstopsignalfifo" + echo ' [ "$Signal" = "stop" ] && {' + echo " [ \"\$Containerid\" ] && $Dockerexe stop \$Containerid >> $Containerlogfile 2>&1 &" + echo " [ \"\$Wmcontainerid\" ] && $Dockerexe stop \$Wmcontainerid >> $Containerlogfile 2>&1 &" + echo " [ \"\$Dockerlogspid\" ] && kill \$Dockerlogspid >> $Containerlogfile 2>&1 &" + echo " }" + echo "} & storepid \$! dockerstopshell" + ;; + esac + + echo "exit 0" + return 0 +} >> $Dockerrc +create_xtermrc() { # create xtermrc: Script to prompt for password (if needed) and to run dockerrc + echo "#! /usr/bin/env bash" + echo "# Ask for password if needed." + echo "# Runs in terminal or in an additional terminal window" + echo "" + declare -f rocknroll + declare -f storeinfo + echo "$Messagefifofuncs" + echo "Timetosaygoodbyefile='$Timetosaygoodbyefile'" + echo "" + #[ "$Debugmode" = "yes" ] && echo "set -x" + echo "Messagefile='$Messagefifo'" + echo "Storeinfofile='$Storeinfofile'" + echo "export TERM=xterm SHELL=/bin/bash" + echo "" + echo "debugnote 'Running xtermrc: Ask for password if needed ($Passwordneeded)'" + echo "" + [ "$Passwordneeded" = "yes" ] && case $Passwordfrontend in + su|sudo) + echo "echo 'x11docker $Imagename $Containercommand:'" + echo "echo 'Please type in your password to run docker on display $Newdisplay'" + echo "echo -n 'Password ($Passwordfrontend): '" + ;; + esac + echo "" + case $Passwordfrontend in + gksudo|lxsudo) echo "$Passwordcommand bash $Dockerrc" ;; + pkexec) echo "pkexec env DISPLAY=\$DISPLAY XAUTHORITY=\$XAUTHORITY bash $Dockerrc" ;; + gksu) echo "$Passwordcommand \"bash $Dockerrc \"" ;; + lxsu) echo "$Passwordcommand bash $Dockerrc" ;; + *) echo "$Passwordcommand \"${Sudo}bash $Dockerrc \"" ;; + esac + echo "" + echo "storeinfo xtermrc=ready" + echo "exit 0" + return 0 +} >> $Xtermrc + +#### final startup routines +start_compositor() { # start Wayland compositor Weston or KWin + local Compositorkeyword + + case $Xserver in + --weston|--weston-xwayland|--xpra-xwayland|--xdummy-xwayland) Compositorkeyword="weston-desktop-shell" ;; + --kwin|--kwin-xwayland) Compositorkeyword="X-Server" ;; + esac + + unpriv "$(command -v dbus-launch) $Compositorcommand >> $Compositorlogfile 2>&1 & echo compositorpid=\$! >>$Storeinfofile" + storeinfo "compositorpid=$(storeinfo dump compositorpid)" + waitforlogentry "start_compositor()" "$Compositorlogfile" "$Compositorkeyword" "$Compositorerrorcodes" + setonwatchpidlist "$(storeinfo dump compositorpid)" compositor + + case $Xserver in + --xpra-xwayland|--xdummy-xwayland) # hide weston window + unpriv "xdotool windowunmap 0x$(printf '%x\n' $(grep 'window id' $Compositorlogfile | rev | cut -d' ' -f1 | rev))" ;; + esac + return 0 +} +start_docker() { # start xtermrc -> dockerrc + # run docker in xtermrc, ask for password if needed + case $Passwordfrontend in + su|sudo) + case $Passwordneeded in + no) /usr/bin/env bash $Xtermrc ;; + yes) $Passwordterminal /usr/bin/env bash $Xtermrc ;; + esac + ;; + *) $Passwordterminal "bash $Xtermrc" ;; + esac + waitforlogentry "start_docker()" "$Storeinfofile" "dockerrc=ready" "" infinity +} +start_hostexe() { # options --exe, --xonly: Run host executeable instead of docker container + local Line + + # generate start script + { echo "#! /usr/bin/env bash" + echo "# --exe: Run host executeable $Hostexe" + echo "" + #[ "$Debugmode" = "yes" ] && echo "set -Eux" + echo "$Messagefifofuncs" + declare -f pspid + declare -f storeinfo + declare -f storepid + echo "" + echo "Messagefile='$Messagefifo'" + echo "Storeinfofile='$Storeinfofile'" + echo "Storepidfile='$Storepidfile'" + echo "Tini=''" + [ "$Dbusrunsession" = "yes" ] && echo "Dbus='$(command -v dbus-run-session >/dev/null)'" + + echo "export $Newxenv" + [ "$Setupwayland" = "no" ] && { + echo "unset WAYLAND_DISPLAY" + echo "unset $Waylandtoolkitvars" + } + echo "" + case $Xserver in + --weston|--kwin|--hostwayland) + echo "unset DISPLAY XAUTHORITY" + ;; + esac + + echo "export HOME='$Containeruserhome'" + echo "cd '$Containeruserhome'" + + [ "$Workdir" ] && echo "cd '$Workdir'" + echo "" + + while read Line; do + echo "export '$Line'" + done < <(store_runoption dump env | grep -v XAUTHORITY | grep -v XDG_RUNTIME_DIR) + echo "" + + echo "env >> $Containerenvironmentfile" + echo "verbose \"Container environment:" + echo "\$(env | sort)\"" + echo "" + + [ "$Windowmanagermode" = "host" ] && { + command -v $Hostwindowmanager >/dev/null && { + echo " note 'Option --wm: Starting host window manager: ${Hostwindowmanager:-WM_NOT_FOUND}'" + echo " ${Hostwindowmanager:-NO_WM_FOUND} >>$Xinitlogfile 2>&1 & storepid \$! windowmanager" + } || note "Did not find a host window manager. Please provide one. Recommended: + $Wm_recommended_nodesktop_light" + } + echo "" + + echo "storeinfo test tini && {" + echo " Tini=\"\$(storeinfo dump tini) --\" " + echo " export TINI_SUBREAPER=1" + echo "}" + echo "" + + echo "# close additional file descriptors" + echo "for i in 3 4 6 7 8 9; do" + echo " { >&\$i ;} 2>/dev/null && exec >&\$i-" + echo "done" + echo "" + + echo "\$Tini \$Dbus $Hostexe $( [ "$Forwardstdin" = "yes" ] && echo "<$Cmdstdinfifo") >>$Cmdstdoutlogfile 2>>$Cmdstderrlogfile &" + echo 'storeinfo pid1pid=$!' + } >> $Containerrc + nl -ba <$Containerrc >> $Containerlogfile + + # Send signal to run X and wait for X to be ready + storeinfo readyforX=ready + waitforlogentry "$Xserver" "$Xinitlogfile" "xinitrc is ready" "$Xiniterrorcodes" + + # run start script + unpriv "/usr/bin/env bash $Containerrc" ### FIXME support --user + + return 0 +} +start_pulseaudiotcp() { # option --pulseaudio=tcp: load Pulseaudio TCP module (authenticated with container IP) + local Containerip + Containerip="$(storeinfo dump containerip)" + Pulseaudiomoduleid="$(unpriv "pactl load-module module-native-protocol-tcp port=$Pulseaudioport auth-ip-acl=${Containerip:-"127.0.0.1"}" )" + [ "$Pulseaudiomoduleid" ] && { + storeinfo "pulseaudiomoduleid=$Pulseaudiomoduleid" + } || note "Option --pulseaudio: command pactl failed. + Is pulseaudio running at all on your host? + You can try option --alsa instead." + return 0 +} +start_xpra() { # options --xpra / --xpra-xwayland: start and watch xpra server and xpra client + local Xpraserverpid Xpraclientpid Xpraenv + + Xpraenv=" NO_AT_BRIDGE=1 \\ + XPRA_EXPORT_ICON_DATA=0 \\ + XPRA_EXPORT_XDG_MENU_DATA=0 \\ + XPRA_ICON_OVERLAY=0 \\ + XPRA_MENU_ICONS=0 \\ + XPRA_UINPUT=0 \\ + XPRA_XDG_EXPORT_ICONS=0 \\ + XPRA_XDG_LOAD_GLOB=0 \\ + $(verlt $Xpraversion v2.1 && echo XPRA_OPENGL_DOUBLE_BUFFERED=1 ||:)" + + # xpra server + Xpraservercommand="env XAUTHORITY=$Xclientcookie \\ + GDK_BACKEND=x11 \\ +$Xpraenv $Xpraservercommand" + debugnote "Running xpra server: +$Xpraservercommand" + echo "x11docker [$(timestamp)]: Starting Xpra server" >> $Xpraserverlogfile + unpriv "$Xpraservercommand ||:" >> $Xpraserverlogfile 2>&1 & + Xpraserverpid=$! + storepid $Xpraserverpid xpraserver + + verlt "$Xprarelease" "r23060" && waitforlogentry "xpra server" $Xpraserverlogfile 'xpra is ready' + rocknroll || return 64 + + # xpra client + Xpraclientcommand="env $Hostxenv \\ +$Xpraenv $Xpraclientcommand" + debugnote "Running xpra client: +$Xpraclientcommand" + echo "x11docker [$(timestamp)]: Starting Xpra client" >> $Xpraclientlogfile + unpriv "$Xpraclientcommand ||:" >> $Xpraclientlogfile 2>&1 & + Xpraclientpid=$! + storepid $Xpraclientpid xpraclient + + # catch possible xpra crashes + while rocknroll; do + ps -p $Xpraserverpid >/dev/null || break + ps -p $Xpraclientpid >/dev/null || break + sleep 1 + done + sleep 1 && rocknroll && note "Option $Xserver: xpra terminated unexpectedly. + Last lines of xpra server log: +$(tail $Xpraserverlogfile) +--------------------------------- + Last lines of xpra client log: +$(tail $Xpraclientlogfile)" + saygoodbye xpra + + return 0 +} +start_xserver() { # start X server + case $Xserver in + --xpra|--xephyr|--xdummy|--xvfb|--xwayland|--nxagent|--weston-xwayland|--kwin-xwayland|--xpra-xwayland|--xdummy-xwayland|--xwin) + unpriv "env WAYLAND_DISPLAY=$Newwaylandsocket xinit $Xinitrc -- $Xcommand >> $Xinitlogfile 2>&1 " ;; + --xorg) + case $Xlegacywrapper in + yes) unpriv " xinit $Xinitrc -- $Xcommand >> $Xinitlogfile 2>&1 " ;; + no) eval " xinit $Xinitrc -- $Xcommand >> $Xinitlogfile 2>&1 " ;; + esac + ;; + --hostdisplay|--hostwayland|--weston|--kwin|--tty) + unpriv " bash $Xinitrc >> $Xinitlogfile 2>&1 " ;; + --runx) unpriv " $Xcommand -- bash $Xinitrc >> $Xinitlogfile 2>&1 " ;; + esac + + [ $? != 0 ] && rocknroll && note "X server $Xserver returned an error code. + Last lines of xinit logfile: +$(tail $Xinitlogfile) + + $( [ -s "$Compositorlogfile" ] && echo "Last lines of compositor log: +$(tail $Compositorlogfile)")" + return 0 +} + +#### main init routines +check_fallback() { + # Option --fallback + case $Fallback in + no) error "Option --fallback=no: Fallbacks are disabled. + x11docker cannot fulfil an option you have chosen, see message above." ;; + esac +} +check_host() { # check host environment + local Drive + + [ "${0:-}" = "${BASH_SOURCE:-}" ] && Runssourced="no" || Runssourced="yes" + + Hostsystem="$(grep '^ID=' /etc/os-release 2>/dev/null | cut -d= -f2 || echo 'unknown')" + Hostarchitecture="$(uname -m)" + case "$Hostarchitecture" in + x86_64|x86-64|amd64|AMD64) Hostarchitecture="amd64 ($Hostarchitecture)" ;; + aarch64|armv8|ARMv8|arm64v8) Hostarchitecture="arm64v8 ($Hostarchitecture)" ;; + aarch32|armv8l|armv7|armv7l|ARMv7|arm32v7|armhf|armv7hl) Hostarchitecture="arm32v7 ($Hostarchitecture)" ;; + arm32v6|ARMv6|armel) Hostarchitecture="arm32v6 ($Hostarchitecture)" ;; + arm32v5|ARMv5) Hostarchitecture="arm32v5 ($Hostarchitecture)" ;; + i686|i386|x86) Hostarchitecture="i386 ($Hostarchitecture)" ;; + ppc64*|POWER8) Hostarchitecture="ppc64le ($Hostarchitecture)" ;; + s390x) Hostarchitecture="s390x ($Hostarchitecture)" ;; + mips|mipsel) Hostarchitecture="mipsel ($Hostarchitecture)" ;; + mips64*) Hostarchitecture="mips64el ($Hostarchitecture)" ;; + *) Hostarchitecture="unknown ($Hostarchitecture)" ;; + esac + + # Check libc from host. If same as in container, it is possible to share timezone file + Hostlibc="unknown" + ldd --version 2>&1 | grep -q 'musl libc' && Hostlibc='musl' + ldd --version 2>&1 | grep -q -E 'GLIBC|GNU libc' && Hostlibc='glibc' + + # Check host time zone + Hostlocaltimefile="$(myrealpath /etc/localtime)" # Find time zone file in /usr/share/zoneinfo + [ -e "$Hostlocaltimefile" ] || Hostlocaltimefile="" + Hostutctime=$(date +%:::z) # Offset of UTC. Used if time zone file cannot be provided + [ "$(cut -c1 <<< "$Hostutctime")" = "+" ] && { + Hostutctime="UTC-$(cut -c2- <<< "$Hostutctime")" + } || { + Hostutctime="UTC+$(cut -c2- <<< "$Hostutctime")" + } + + # Check for MS Windows subsystem + command -v cygcheck.exe >/dev/null && { + cygcheck.exe -V | rmcr | grep -q "(cygwin)" && Winsubsystem="CYGWIN" + cygcheck.exe -V | rmcr | grep -q "(msys)" && Winsubsystem="MSYS2" + } + uname -r | grep -q "Microsoft" && Winsubsystem="WSL1" + uname -r | grep -q "microsoft" && Winsubsystem="WSL2" + case $Winsubsystem in + MSYS2|CYGWIN) + Winsubmount="$(cygpath.exe -u "c:/" | rmcr | sed s%/c/%%)" + Winsubpath="$(convertpath unix "$(cygpath.exe -w "/" | rmcr)" )" + Mobyvm="yes" + ;; + WSL1|WSL2) + command -v "/mnt/c/Windows/System32/cmd.exe" >/dev/null && Winsubmount="/mnt" + command -v "/c/Windows/System32/cmd.exe" >/dev/null && Winsubmount="" + grep -q "Windows" <<< "${PATH:-}" || export PATH="${PATH:-}:$Winsubmount/c/Windows/System32:$Winsubmount/c/Windows/System32/WindowsPowerShell/v1.0" # can miss after sudo in WSL + command -v "$Winsubmount/c/Windows/System32/cmd.exe" >/dev/null || error "$Winsubsystem: Could not find cmd.exe + in /mnt/c/Windows/System32 or /c/Windows/System32. + Do you have a different path to your Windows system partition?" + Winsubpath="$(convertpath unix "$(getwslpath)")" + [ "$Winsubsystem" = "WSL1" ] && Mobyvm="yes" + ;; + esac + Winsubmount="${Winsubmount%/}" + Winsubpath="${Winsubpath%/}" + [ "$Winsubsystem" ] && Hostsystem="MSWindows-$Winsubsystem" + + [ -z "$Mobyvm" ] && Mobyvm="no" + case $Mobyvm in + yes) + command -v docker.exe >/dev/null || export PATH="${PATH:-}:$(convertpath subsystem "C:/Program Files/docker"):$(convertpath subsystem "C:/Program Files/Docker/Docker/resources/bin")" + Dockerexe="docker.exe" + ;; + no) + Dockerexe="docker" + ;; + esac + [ "$Podman" = "yes" ] && Dockerexe="podman" + + # Check host IP. Needed for --pulseaudio=tcp, --printer=tcp, --xoverip and --xwin + case $Winsubsystem in + "") + case $Network in + host) Hostip="127.0.0.1" ;; + *) + #Hostip="$(hostname -I | cut -d' ' -f1)" + [ "$Hostip" ] || Hostip="$(ip -4 -o a | grep 'docker0' | awk '{print $4}' | cut -d/ -f1 | grep "172.17.0.1" ||: )" + #[ "$Hostip" ] || Hostip="$($Dockerexe network inspect bridge --format='{{.IPAM.Config}}' 2>/dev/null | awk '{print $2}')" + [ "$Hostip" ] || Hostip="$(ip -4 -o a | grep 'docker0' | awk '{print $4}' | cut -d/ -f1 | head -n1)" + [ "$Hostip" ] || Hostip="$(ip -4 -o a | awk '{print $4}' | cut -d/ -f1 | grep "^192\.168\.*" | head -n1)" + [ "$Hostip" ] || Hostip="$(ip -4 -o a | awk '{print $4}' | cut -d/ -f1 | grep -v "127.0.0.1" | head -n1)" + ;; + esac + ;; + *) + Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '192\.168\.[0-9]*\.[0-9]*' | head -n1 )" + [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '10\.0\.[0-9]*\.[0-9]*' | head -n1 )" + [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*' | head -n1 )" + ;; + esac + + # Check if docker is installed with snap/snappy + myrealpath "$(command -v "${Dockerexe:-docker_not_found}")" | grep -q snap && Runsinsnap="yes" || Runsinsnap="no" + + # Provide dos->unix newline converter to unpriv() commands + export -f rmcr + + # Check whether x11docker runs over SSH + pstree -ps $$ >/dev/null 2>&1 && { + pstree -ps $$ | grep -q sshd && Runsoverssh="yes" || Runsoverssh="no" + } || { + check_parent_sshd "$$" && Runsoverssh="yes" || Runsoverssh="no" + } + + # Check whether x11docker runs on console + Runsonconsole="$(env LANG=C tty 2>&1)" + case "$Runsonconsole" in + "not a tty") Runsonconsole="" ;; + *) + grep -q tty <<< "$Runsonconsole" && Runsonconsole="yes" || Runsonconsole="no" + ;; + esac + [ "$Winsubsystem" ] && Runsonconsole="no" + [ -z "$Runsonconsole" ] && { + [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] && Runsonconsole="no" || Runsonconsole="yes" + debugnote "check_host(): Command tty failed. Guess if running on console: $Runsonconsole" + } + + # Check whether x11docker runs in a terminal + tty -s && Runsinterminal="yes" || Runsinterminal="no" + + # Check whether x11docker runs in interactive bash mode (--enforce-i) + case $- in + *i*) Runsinteractive="yes" ;; + *) Runsinteractive="no" ;; + esac + + # Check whether ps can watch processes of other users + mount | grep "^proc" | grep -q "hidepid=2" && { + Hosthidepid="yes" + debugnote "check_host(): /proc is mounted with hidepid=2." + } || { + Hosthidepid="no" + } + ps aux | cut -d' ' -f1 | grep -q root && { + Hostcanwatchroot="yes" + } || { + Hostcanwatchroot="no" + case $Winsubsystem in + MSYS2|CYGWIN) Hostcanwatchroot="yes" ;; + esac + } + debugnote "check_host(): ps can watch root processes: $Hostcanwatchroot" + + # Check if host uses proprietary NVIDIA driver + Nvidiaversion=$(head -n1 2>/dev/null /dev/null 2>&1 || { + [ -e /etc/passwd ] || warning "Your system misses /etc/passwd" + warning "Could not find user '$Hostuser' in /etc/passwd." + } + + Hostuser=$(id -un $Hostuser) + Hostuseruid=$(id -u $Hostuser) + Hostusergid=$(id -g $Hostuser) + [ "$Hostuser" = "$Startuser" ] && Hostuserhome="$HOME" + + [ -z "$Hostuserhome" ] && Hostuserhome=$(getent passwd $Hostuser 2>/dev/null | cut -d: -f6) + [ -z "$Hostuserhome" ] && { + Hostuserhome="/tmp/home/$Hostuser" + mkdir -p "$Hostuserhome" + warning "Could not read your home directory from /etc/passwd for user '$Hostuser'. + Please set \$HOME with a valid path. + Fallback: setting HOME=$Hostuserhome" + check_fallback + } + debugnote "host user: $Hostuser $Hostuseruid:$Hostusergid $Hostuserhome" + + [ "$Hostuser" = "root" ] && warning "Running as user root. + Maybe \$(logname) did not provide an unprivileged user. + Please use option --hostuser=USER to specify an unprivileged user. + Otherwise, new X server runs as root, and container user will be root." + + id | grep -q "(docker)" && warning "User $Hostuser is member of group docker. + That allows unprivileged processes on host to gain root privileges." + + # How to run as unprivileged user in unpriv() + case "$Hostuser" in + "$Startuser") Unpriv="eval" ;; # alternatively: bash -c + *) Unpriv="su $Hostuser -c" ;; + esac + + return 0 +} +check_hostxenv() { # check environment variables for host X display + + Hostdisplay="${DISPLAY:-}" + Hostdisplaynumber="$(echo $Hostdisplay | cut -d: -f2 | cut -d. -f1)" # display number without ":" and ".0" + [ -n "$Hostdisplay" ] && Hostxsocket="/tmp/.X11-unix/X$Hostdisplaynumber" || Hostxsocket="" # X socket from host, needed for --hostdisplay + [ -e "$Hostxsocket" ] || Hostxsocket="" # can miss in SSH session + + # Check whether host X server has MIT-SHM enabled. + command -v xdpyinfo >/dev/null && xdpyinfo >/dev/null 2>&1 && { + xdpyinfo | grep -q "MIT-SHM" && Hostmitshm="yes" || Hostmitshm="no" + } + [ "$Winsubsystem" ] && Hostmitshm="no" + + # get cookie from host display + XAUTHORITY=${XAUTHORITY:-} + [ -z "$XAUTHORITY" ] && command -v systemctl >/dev/null && XAUTHORITY="$(systemctl --user show-environment | grep XAUTHORITY= | cut -d= -f2)" + [ -z "$XAUTHORITY" ] && [ -e "$Hostuserhome/.Xauthority" ] && XAUTHORITY="$Hostuserhome/.Xauthority" + [ "$Runsoverssh" = "yes" ] && [ -e "$Hostuserhome/.Xauthority" ] && XAUTHORITY="$Hostuserhome/.Xauthority" + [ "${XAUTHORITY:-}" ] && { + unpriv "xauth -i -f ${XAUTHORITY:-} nlist $Hostdisplay 2>/dev/null | xauth -f $Hostxauthority nmerge - 2>/dev/null" + chown $Hostuser $Hostxauthority + chmod 600 $Hostxauthority + export XAUTHORITY + } || { + Hostxauthority="" + unset XAUTHORITY + } + [ "$Hostdisplay" ] || { + Hostxsocket="" + Hostxauthority="" + XAUTHORITY="" + } + [ -s "${XAUTHORITY:-}" ] && [ ! -s "$Hostxauthority" ] && cp "${XAUTHORITY:-}" "$Hostxauthority" + + # create $Hostxenv + Hostxenv="DISPLAY=$Hostdisplay" + [ -s "$Hostxauthority" ] && { + Hostxenv="$Hostxenv XAUTHORITY=$Hostxauthority" + export XAUTHORITY=$Hostxauthority + } || { + Hostxauthority= + unset XAUTHORITY + } + [ -n "$Hostxsocket" ] && Hostxenv="$Hostxenv XSOCKET=$Hostxsocket" + [ -n "$Hostwaylandsocket" ] && Hostxenv="$Hostxenv WAYLAND_DISPLAY=$Hostwaylandsocket" + Hostxenv="$Hostxenv XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" + [ -n "$Hostdisplay" ] && [ -z "$Hostxauthority" ] && warning "Your host X server runs without cookie authentication." + + [ -z "$GDK_BACKEND" ] && { + [ -n "$Hostwaylandsocket" ] && export GDK_BACKEND="wayland" + [ -n "$Hostdisplay" ] && export GDK_BACKEND="x11" + [ -z "$Hostdisplay$Hostwaylandsocket" ] && unset GDK_BACKEND + } + + return 0 +} +check_option_interferences() { # check multiple option interferences, change settings if needed + local Message + + [ "$Desktopmode" = "no" ] && case $Xserver in + --xephyr|--weston-xwayland|--kwin-xwayland|--xorg) Windowmanagermode="auto" ;; + esac + + case $Xserver in + --xorg) # check if --xorg can run + [ "$Autochooseserver" = "yes" ] && [ "$Codename" = "xonly" ] && error "Will not run an empty Xorg in auto-choosing mode. + If you want this, please use option --xorg explicitly." + + [ -e "/etc/X11/Xwrapper.config" ] && sed 's/ //g' /etc/X11/Xwrapper.config | grep -xq "allowed_users=anybody" && sed 's/ //g' /etc/X11/Xwrapper.config | grep -xq "needs_root_rights=yes" && { + Xlegacywrapper="yes" + } || { + Xlegacywrapper="no" + [ "$Startuser" != "root" ] && [ "$Runsonconsole" != "yes" ] && warning "Your configuration seems not to allow to start + a second core Xorg server from within X. Option --xorg may fail. + (Per default, only root or console users are allowed to run an Xorg server). + + Possible solutions: + 1.) Install one of nested X servers 'Xephyr', 'Xnest' or 'nxagent'. + For --gpu support: install 'weston' and 'Xwayland'. + 2.) Switch to console tty1...tty6 with ... + and start x11docker there. + 3.) Run x11docker as root. + 4.) Edit file '/etc/X11/Xwrapper.config' and replace line: + allowed_users=console + with lines + allowed_users=anybody + needs_root_rights=yes + If the file does not exist already, you can create it. + On Debian and Ubuntu you need package xserver-xorg-legacy. + + Be aware that switching directly between Xorg servers can crash them. + Always switch to a black console first before switching to Xorg." + } + ;; + --xpra) # check vfb for xpra + { [ -z "$Xpravfb" ] || [ "$Xpravfb" = "Xvfb" ] ; } && ! command -v Xvfb >/dev/null && note "Option --xpra: Xvfb not found. + Will try to use dummy video driver Xdummy. + If you encounter xpra startup errors, please install 'Xvfb'. + $Wikipackages" && Xpravfb="Xdummy" + [ "$Xpravfb" ] || { command -v Xvfb >/dev/null && Xpravfb="Xvfb" || Xpravfb="Xdummy" ; } + ;; + --tty) + [ "$Interactive" = "no" ] && { + tput lines >/dev/null 2>&1 && store_runoption env "LINES=$(tput lines)" + tput cols >/dev/null 2>&1 && store_runoption env "COLUMNS=$(tput cols)" + #verbose "Option --tty: Setting LINES and COLUMNS to terminal size ${COLUMNS}x${LINES}." + } + ;; + --hostdisplay) + [ "$Winsubsystem" ] && Trusted="yes" || Trusted="no" + [ -n "$(cut -d: -f1 <<< "$Hostdisplay")" ] && Xoverip="yes" + [ -z "$Hostxauthority" ] && { + note "Option --hostdisplay: You host X server seems to run + without cookie authentication. Cannot set up a cookie for X access. + Fallback: Enabling option --no-auth." + check_fallback + Xauthentication="no" + } + case $Xtest in + yes|no) + note "Option --xtest: Cannot enable or disable X extension XTEST + with option --hostdisplay." + Xtest="" + ;; + esac + ;; + --xdummy|--xvfb) + Showdisplayenvironment="yes" + ;; + esac + case $Xserver in + -xpra|--xpra-xwayland) + # check for version with cookie bug + ! verlt "$Xpraversion" "v2.3" && verlt "$Xprarelease" "r19606" && { + command -v xhost >/dev/null || { + warning "Your xpra version has a cookie authentication issue. + also, 'xhost' is not available on your host. + Fallback: Disabling cookie authentication on new X server." + check_fallback + Xauthentication="no" + } + } ;; + esac + + # check if a host window manager is needed + [ "$Desktopmode" = "no" ] && [ -z "$Windowmanagermode" ] && case $Xserver in + --xephyr|--weston-xwayland|--kwin-xwayland|--xorg|--xwayland) + note "Option $Xserver: x11docker assumes that you need + a window manager. If you don't want this, run with option --desktop. + Enabling option --wm to provide a window manager." + Windowmanagermode="auto" + [ "$Autochooseserver" = "yes" ] && [ "$Runsonconsole" = "no" ] && { + case $Sharegpu in + no) note "Did not find a nice solution to run a seamless application + on your desktop. (Only insecure option --hostdisplay would work). + It is recommended to install xpra or nxagent + to allow a seamless mode without the need of a window manager from host." ;; + yes) note "Did not find a nice solution to run a seamless application with + option --gpu on your desktop. (Only insecure option --hostdisplay would work). + It is recommended to install xpra, weston, Xwayland and xdotool + to allow a seamless mode without the need of a window manager from host." ;; + esac + } + ;; + *) + Windowmanagermode="none" + ;; + esac + + # check xauth + [ "$Xauthentication" = "yes" ] && case $Xserver in + --tty) ;; + --weston|--kwin|--hostwayland) Xauthentication="no" ;; + *) + command -v xauth >/dev/null || { + case $Xoverip in + yes) + [ -z "$Hostxauthority" ] && [ "$Xserver" = "--hostdisplay" ] && Message=warning || Message=error + $Message "Command 'xauth' not found. + SECURITY RISK! + Your X server would be accessable over network without authentication! + That could be abused to take control over your system. + Please install 'xauth' to allow X cookie authentication. + You can disable cookie authentication with discouraged option --no-auth." + ;; + no|"") + warning "Command 'xauth' not found. + Please install 'xauth' to allow X cookie authentication. + Securing X access with cookie authentication is not possible. + Fallback: Disabling X authentication protocol. (option --no-auth) + $Wikipackages" + check_fallback + ;; + esac + Xauthentication="no" + } + ;; + esac + + # --fullscreen is nonsense on tty at all. Avoids weston error on tty. + [ "$Runsonconsole" = "yes" ] && Fullscreen="no" + + # --gpu + [ "$Sharegpu" = "yes" ] && { + warning "Option --gpu degrades container isolation. + Container gains access to GPU hardware. + This allows reading host window content (palinopsia leak) + and GPU rootkits (compare proof of concept: jellyfish)." + case $Xoverip in + yes) + [ "$Network" != "host" ] && note "Option --gpu: With X over IP the host network stack must + be shared to allow GPU access. Enabling option --network=host." + Network="host" + ;; + esac + } + + # --hostdisplay --gpu + [ "$Xserver" = "--hostdisplay" ] && [ "$Sharegpu" = "yes" ] && [ "$Trusted" = "no" ] && { + note "Option --gpu: To allow GPU acceleration with --hostdisplay, + x11docker will allow trusted cookies." + Trusted="yes" + } + + # --hostdisplay with SSH + [ "$Xserver" = "--hostdisplay" ] && [ "$Runsoverssh" = "yes" ] && { + [ "$Trusted" = "no" ] || [ "$Network" != "host" ] && { + note "For SSH connection with option --hostdisplay + x11docker must enable option --network=host and allow trusted cookies. + It is recommended to use other X server options + like --xpra, --xephyr or --nxagent." + Network="host" + Trusted="yes" + } + } + + # --hostdisplay with untrusted cookies: check xdpyinfo + [ "$Xserver" = "--hostdisplay" ] && [ "$Trusted" = "no" ] && { + command -v xdpyinfo >/dev/null && { + xdpyinfo | grep -q SECURITY || { + note "Your X server does not support untrusted cookies. + Have to allow trusted cookies. + Consider to use options --xpra or --nxagent instead of --hostdisplay." + Trusted="yes" + } + } || note "Command 'xdpyinfo' not found. Need it to check + whether Xorg supports untrusted cookies for --hostdisplay + and whether extension MIT-SHM for shared memory is enabled. + Please install 'xdpyinfo'. + $Wikipackages" + } + + # --clipboard + case "$Shareclipboard" in + yes) + case $Xserver in + --weston|--kwin) note "Sharing clipboard with $Xserver is not supported" ;; + --hostwayland) note "Sharing clipboard may or may not work. + Cannot enable or disable it, it depends on your Wayland compositor." ;; + --hostdisplay) + [ "$Trusted" = "no" ] && warning "Option --clipboard: To allow clipboard sharing with + option --hostdisplay, trusted cookies will be enabled. + No protection against X security leaks is left! + Consider to use another X server option." + Trusted="yes" + ;; + esac + case $Xserver in + --xpra|--xpra-xwayland|--hostdisplay|--kwin|--weston|--tty|--xwin) ;; + *) note "Sharing picture clips with option --clipboard + is only possible with options --xpra, --xpra-xwayland and --hostdisplay." ;; + esac + ;; + esac + [ "$Trusted" = "no" ] && warning "Clipboard isolation may fail." + + case $Containersetup in + no) + case $Initsystem in + none|tini) ;; + *) note "Option --no-setup: Option --init is not supported" ;; + esac + Initsystem="none" + Dbusrunsession="no" + #Createcontaineruser="no" + Langwunsch="" + Noentrypoint="no" + Runasroot="" + # --stdin? + # --sudouser? + # --hostdbus + # nvidia installer + ;; + esac + + # --dbus [=system] + case $Dbusrunsession in + yes|user|session) Dbusrunsession="yes" ;; + no) ;; + system) + Dbusrunsession="yes" + Dbussystem="yes" + ;; + *) + note "Option --dbus: Unknown argument '$Dbusrunsession'. + Fallback: Enabling --dbus user session." + check_fallback + Dbusrunsession="yes" + ;; + esac + + + # --cap-default + [ "$Capdropall" = "no" ] && { + warning "Option --cap-default disables security hardening + for containers done by x11docker. Default docker capabilities are allowed. + This is considered to be less secure." + [ "$Allownewprivileges" = "auto" ] && { + note "Option --cap-default: Enabling option --newprivileges. + You can avoid this with --newprivileges=no" + Allownewprivileges="yes" + } + } + + # --newprivileges + case $Allownewprivileges in + yes|no|auto) ;; + *) + note "Option --newprivileges: Unknown argument '$Allownewprivileges'. + Fallback: Setting --newprivileges=auto" + check_fallback + Allownewprivileges="auto" + ;; + esac + + # --hostipc: Check auto-enabling + [ "$Xserver" = "--hostdisplay" ] && [ "$Trusted" = "yes" ] && [ "$Hostmitshm" = "yes" ] && [ "$Sharehostipc" = "no" ] && [ "$Runsoverssh" = "no" ] && { + note "Option --hostdisplay: To allow --hostdisplay with trusted cookies, + x11docker must share host IPC namespace with container (option --hostipc) + to allow shared memory for X extension MIT-SHM." + Sharehostipc="yes" + } + + # --scale + [ "$Scaling" ] && { + case $Xserver in + --weston|--weston-xwayland) + [[ $Scaling =~ ^[1-9]$ ]] || { + note "The scale factor for option $Xserver must be + one of 1 2 3 4 5 6 7 8 9 + Fallback: disabling option --scale" + check_fallback + Scaling="" + } + ;; + --xpra|--xpra-xwayland|--xorg) + isnum $Scaling || { + note "Option --scale needs a number. '$Scaling' is not allowed. + Fallback: disabling option --scale" + check_fallback + Scaling="" + } + ;; + *) + note "Option $Xserver does not support option --scale. + Available for --xpra, --xpra-xwayland and --xorg (float values possible) + and for --weston and --weston-xwayland (full integer values only). + Fallback: disabling option --scale" + check_fallback + Scaling="" + ;; + esac + case $Xserver in + --xpra|--xpra-xwayland) + verlt "$Xpraversion" "v0.16" && { + note "Your xpra version is quite old and does not support --scale. + You need at least xpra version 0.16 + Fallback: disabling option --scale" + check_fallback + Scaling="" + } + ;; + esac + case $Xserver in + --weston-xwayland) note "Weston does not work well with Xwayland in scaled mode. + In summary, Xwayland does not get the right screen resolution from Weston. + (Bug report at https://bugzilla.redhat.com/show_bug.cgi?id=1498669 ). + Try out if it works for you. Otherwise, you can combine + '--xpra-xwayland --desktop --scale $Scaling' for better desktop scaling support. + --scale for single applications works best with --xpra / --xpra-xwayland. + --scale in desktop mode works best with option --xorg." + ;; + --xpra-xwayland) + [ "1" = "$(awk -v a="${Scaling:-1}" 'BEGIN {print (a < 1)}')" ] && { + command -v weston >/dev/null || { + note "Option --xpra-xwayland needs weston + for scale factor smaller than 1. + Fallback: disabling option --scale" + check_fallback + Scaling="" + } + } + ;; + --xorg) + [ "1" = "$(awk -v a="$Scaling" 'BEGIN {print (a < 1)}')" ] && [ -n "$Rotation" ] && note "--xorg does not work well with combination + of --scale smaller than 1 and rotation diferent from 0." + ;; + esac + } + + # --rotate + [ -n "$Rotation" ] && { + case $Xserver in + --weston|--weston-xwayland|--xorg) + echo "0 90 180 270 flipped flipped-90 flipped-180 flipped-270" | grep -q "$Rotation" || { # fuzzy test, have been lazy + note "Unsupported value '$Rotation' for option --rotate. + Must be one of 0 90 180 270 flipped flipped-90 flipped-180 flipped-270 + Fallback: disabling option --rotate" + check_fallback + Rotation="" + } + ;; + *) + note "Option $Xserver does not support option --rotate. + Rotation is possible for --xorg, --weston and --weston-xwayland. + Fallback: disabling option --rotate" + check_fallback + Rotation="" + ;; + esac + } + [ "$Rotation" = "0" ] && Rotation="normal" + + # xrandr: --scale --size --rotate + command -v xrandr >/dev/null || case $Xserver in + --xorg) { [ "$Scaling" ] || [ -n "$Rotation" ] || [ -n "$Screensize" ] ; } && note "Option --xorg needs 'xrandr' to support + options --size, --scale and --rotate. + Please install 'xrandr'. + $Wikipackages" + ;; + esac + + # --dpi + [ -n "$Dpi" ] && case $Xserver in + --weston|--kwin|--hostwayland|--hostdisplay) + note "Option --dpi has no effect with option $Xserver" + Dpi= + ;; + esac + + # --output-count + [ "$Outputcount" != "1" ] && { + case $Xserver in + --xephyr|--weston|--kwin|--weston-xwayland|--kwin-xwayland|--xwin) + [[ "$Outputcount" =~ ^[1-9]$ ]] || { + note "Option --output-count: Value must be one of 1 2 3 4 5 6 7 8 9 + Disabling invalid value $Outputcount" + Outputcount="1" + } + [ "$Runsonconsole" = "yes" ] && { + note "Option --outputcount only works in nested/windowed mode, + but not on tty. Fallback: disabling --outputcount" + check_fallback + Outputcount="1" + } + ;; + *) note "$Xserver does not support option --output-count. + Only available for Weston, KWin and Xephyr, thus for options + --weston, --weston-xwayland, --kwin, --kwin-xwayland, --xephyr." + Outputcount="1" + ;; + esac + } + + # --xfishtank: fish tank + [ "$Xfishtank" = "yes" ] && { + command -v xfishtank >/dev/null || { + note "xfishtank not found. Can not show a fish tank. + Please install 'xfishtank' for option --xfishtank to show a fish tank. + $Wikipackages" + Xfishtank="no" + } + case $Xserver in + --xpra|--xpra-xwayland|--nxagent) + [ "$Desktopmode" = "no" ] && [ -z "$Windowmanagermode" ] && Windowmanagermode="auto" && Desktopmode="yes" ;; + --weston|--kwin|--hostwayland|--hostdisplay|--tty) + note "Option --xfishtank is not supported for $Xserver." + Xfishtank="no" + ;; + esac + } + + # MSYS2, Cygwin, WSL + case $Winsubsystem in + WSL2) note "WSL2 support is experimental and barely tested yet. + Feedback and bug reports are appreciated!" ;; + esac + case $Mobyvm in + yes) + case "$Winsubsystem" in + WSL1|WSL2) + grep -q "/c/" <<< "$Cachebasefolder" && [ -z "$Hosthomebasefolder" ] && note "With MobyVM and WSL x11docker stores its cache files on drive C: + to allow cache file sharing. + Your Docker setup might not allow to share files from drive C:. + If startup fails with an 'access denied' error, + please either allow access to drive C: or specify a custom folder for + cache storage with option '--cachebasedir D:/some/cache/folder'. + Same issue can occur with option '--home'. + Use option '--homebasedir D:/some/home/folder' in that case." + ;; + esac + [ "$Initsystem" = "systemd" ] && { + note "Option --init=systemd is not supported with MobyVM. + You can try another init option instead, e.g. --init=openrc. + Fallback: Disabling option --init=systemd" + check_fallback + Initsystem="tini" + } + [ "$Sharecgroup" = "yes" ] && { + note "Option --sharecgroup is not supported with MobyVM. + Fallback: Disabling option --sharecgroup." + Sharecgroup="no" + } + ;; + esac + case $Winsubsystem in + MSYS2|CYGWIN|WSL1|WSL2) + [ "$Pulseaudiomode" ] && { + note "Option --pulseaudio is not supported on MS Windows. + Fallback: Disabling option --pulseaudio" + check_fallback + Pulseaudiomode="" + } + case $Xserver in + --xwin) note "Windows firewall settings can forbid application access + to the X server. If no application window appears, but no obvious error + is shown, please check your firewall settings. Compare issue #108 on github." ;; + esac + ;; + esac + + # check XDG_RUNTIME_DIR + case $Xserver in + --weston|--kwin|--weston-xwayland|--kwin-xwayland) + [ -z "$XDG_RUNTIME_DIR" ] && [ -e "/run/user/${Hostuseruid:-unknownuid}" ] && export XDG_RUNTIME_DIR="/run/user/$Hostuseruid" + [ -z "$XDG_RUNTIME_DIR" ] && { + export XDG_RUNTIME_DIR="$Cachefolder/XDG_RUNTIME_DIR" + unpriv "mkdir -p $XDG_RUNTIME_DIR" + unpriv "chmod 700 $XDG_RUNTIME_DIR" + } + ;; + esac + + # --wayland + [ "$Setupwayland" = "yes" ] && case $Xserver in + --weston|--kwin|--hostwayland) ;; + *) + note "Option --wayland: Sharing Wayland socket is not supported + for X server option $Xserver. + You can try --weston, --kwin or --hostwayland instead. + Fallback: Disabling option --wayland." + check_fallback + Setupwayland="no" + ;; + esac + [ "$Setupwayland" = "yes" ] && { + Dbusrunsession="yes" + for Line in $Waylandtoolkitenv; do + store_runoption env $Line + done + } + + # check --westonini + [ -n "$Customwestonini" ] && [ ! -e "$Customwestonini" ] && { + warning "Custom weston.ini (option --westonini) not found. + $Customwestonini" + Customwestonini="" + } + + # --interactive + case $Interactive in + yes) + case $Winsubsystem in + MSYS2|CYGWIN|WSL1) + Winpty="$(command -v winpty)" + Winpty="$(escapestring "$Winpty")" + [ "$Winpty" ] || error "Option -i, --interactive: On MS Windows you need 'winpty' + to run x11docker in interactive mode. MSYS2 provides winpty as a package. + On Cygwin it can be compiled from source. WSL1 isn't supported yet. + WSL2 might work, but is not tested yet." + ;; + esac + [ "$Forwardstdin" = "yes" ] && { + note "You cannot use --stdin along with --interactive. + Fallback: Disabling option --stdin." + check_fallback + Forwardstdin="no" + } + [ "$Runsinteractive" = "yes" ] && { + note "Option -i, --interactive: Does not work in interactive + bash mode (option --enforce-i). + Fallback: Disabling option --interactive." + check_fallback + Interactive="no" + } + case $Initsystem in + systemd|openrc|runit|sysvinit) note "Option --interactive: Interactive mode with option + --init=$Initsystem is not well integrated yet. + Shells do not have job control and CTRL-C can behave different than expected." ;; + esac + ;; + esac + [ "$Interactive" = "yes" ] && Showcontaineroutput="no" + + # --limit N + [ "$Limitresources" ] && { + [ "1" = "$(awk -v a=$Limitresources "BEGIN {print (a <= 1)}")" ] && [ "1" = "$(awk -v a=$Limitresources "BEGIN {print (a > 0)}")" ] || { + warning "Option --limit: Specified value $Limitresources is out of range. + Allowed is a factor greater than 0 and less than or equal to 1. 0/dev/null || { + note "Option --pulseaudio: pactl not found. + Is pulseaudio installed and running on your host system? + Fallback: Disabling --pulseaudio, enabling option --alsa" + check_fallback + Pulseaudiomode="" + Sharealsa="yes" + } + ;; + *) + note "Option --pulseaudio: Unknown pulseaudio mode: $Pulseaudiomode + Allowed are --pulseaudio=socket, --pulseaudio=tcp or --pulseaudio=auto. + Fallback: Enabling --pulseaudio=auto" + check_fallback + Pulseaudiomode="auto" + ;; + esac + + # --printer + case $Sharecupsmode in + auto) + Sharecupsmode="socket" + [ "$Runsinsnap" = "yes" ] && Sharecupsmode="tcp" + [ "$Runtime" = "kata-runtime" ] && Sharecupsmode="tcp" + ;; + ""|socket|tcp) ;; + *) + note "Option --printer: Invalid argument $Sharecupsmode + Fallback: Setting --printer=socket" + check_fallback + Sharecupsmode="socket" + ;; + esac + + # --pull + case "$Pullimage" in + yes|no|always|ask) ;; + *) note "Option --pull: Invalid argument: $Pullimage + Allowed arguments: yes|no|always|ask + Fallback: Setting --pull=ask" + check_fallback + ;; + esac + + case "$Runtime" in + ""|runc|crun|oci) ;; + nvidia) Sharegpu="yes" ;; + kata-runtime) + note "Option --runtime=kata-runtime: Be aware not to share + the same files with runc and kata-runtime containers at the same time. + Otherwise container startup may fail." + [ "$Sharealsa" = "yes" ] && { + note "Option --alsa: ALSA sound is not possible with + --runtime=kata-runtime. Fallback: Enabling option --pulseaudio." + Sharealsa="no" + Pulseaudiomode="tcp" + } + [ "$Sharewebcam" = "yes" ] && { + note "Option --webcam: Webcam support does not work with + --runtime=kata-runtime. Fallback: Disabling option --webcam." + check_fallback + Sharewebcam="no" + } + [ "$Network" = "host" ] && { + note "Option --network=host: Sharing host network stack does not work + with --runtime=kata-runtime. Fallback: Setting --network=bridge." + check_fallback + Network="bridge" + } + [ "$Sharehostipc" = "yes" ] && { + note "Option --hostipc: Only IPC of the the qemu VM is shared with + --runtime=kata-runtime." + } + ;; + *) + note "Option --runtime: x11docker does not know runtime: $Runtime" + ;; + esac + + case "$Podman" in + yes) + # /proc/sys/kernel/unprivileged_userns_clone might exist on debian only. + # https://github.com/mviereck/x11docker/issues/255#issuecomment-758014962 + [ "$(cat /proc/sys/kernel/unprivileged_userns_clone)" = "0" ] && error "Option --podman: Linux kernel disallows unprivileged + user namespace setup. Please run as root: + sysctl -w kernel.unprivileged_userns_clone=1" + store_runoption cap "CHOWN" + ;; + esac + + # Docker installed in Ubuntu snap + [ "$Runsinsnap" = "yes" ] && { + note "Docker was installed with snap. That causes some restrictions. + Option --newprivileges=yes is enabled. + Option --hostdisplay is not available because X can only be accessed over TCP. + Option --gpu only works with --xorg and --hostnet. + It is recommended to install Docker natively instead of running it in snap." + [ "$Allownewprivileges" = "auto" ] && Allownewprivileges="yes" + } + + return 0 +} +check_passwordfrontend() { # check password prompt frontend (pkexec, su, sudo, ...) (also option --pw) + # check if x11docker can run docker without prompting for password + [ "$Passwordfrontend" = "none" ] && Passwordneeded="no" + [ "$X11dockermode" = "exe" ] && Passwordneeded="no" + case $Mobyvm in + yes) Passwordneeded="no" && Passwordfrontend="none" ;; + esac + [ -z "$Passwordfrontend" ] && $Dockerexe info >/dev/null 2>&1 && Passwordfrontend="none" && Passwordneeded="no" + [ -z "$Passwordfrontend" ] && sudo -n env >/dev/null 2>&1 && Passwordfrontend="sudo" && Passwordneeded="no" + + # check sudo. Check is not reliable, compare https://unix.stackexchange.com/questions/383918/su-or-sudo-how-to-know-which-one-will-work + ### FIXME: just guessing that members of group sudo or wheel are allowed to run commands docker and env as root + [ -z "$Passwordfrontend" ] && { sudo -ln $Dockerexe >/dev/null 2>&1 || id | grep -q '(sudo)' || id | grep -q '(wheel)' ; } && command -v sudo >/dev/null && { + [ -z "$Hostdisplay$Newdisplay" ] && Passwordfrontend="sudo" + sudo -ln env >/dev/null 2>&1 || id | grep -q '(sudo)' || id | grep -q '(wheel)' && { + [ -z "$Passwordfrontend" ] && [ "$Runsinterminal" = "yes" ] && Passwordfrontend="sudo" + [ -z "$Passwordfrontend" ] && command -v gksudo >/dev/null && Passwordfrontend="gksudo" + [ -z "$Passwordfrontend" ] && command -v lxsudo >/dev/null && Passwordfrontend="lxsudo" + [ -z "$Passwordfrontend" ] && command -v kdesudo >/dev/null && Passwordfrontend="kdesudo" + } + [ -z "$Passwordfrontend" ] && Passwordfrontend="sudo" + } + + # check su + [ -n "$Hostdisplay$Newdisplay" ] && { + [ -z "$Passwordfrontend" ] && [ "$Runsinterminal" = "yes" ] && Passwordfrontend="su" + [ -z "$Passwordfrontend" ] && command -v gksu >/dev/null && Passwordfrontend="gksu" + [ -z "$Passwordfrontend" ] && command -v lxsu >/dev/null && Passwordfrontend="lxsu" + [ -z "$Passwordfrontend" ] && command -v kdesu >/dev/null && Passwordfrontend="kdesu" + [ -z "$Passwordfrontend" ] && command -v beesu >/dev/null && Passwordfrontend="beesu" + } + [ -z "$Passwordfrontend" ] && Passwordfrontend="su" # default if everything else fails + + # Passwordcommand: prefix to start dockerrc. Sudo: prefix to start docker in dockerrc + case $Passwordfrontend in + pkexec|"") Passwordcommand="bash -c" ; Passwordterminal="bash -c" ;; + su) Passwordcommand="su -c" ;; + sudo) Passwordcommand="bash -c" ; Sudo="sudo -E " ;; + gksu) Passwordcommand="gksu --message 'x11docker $Imagename' --disable-grab" ; Passwordterminal="bash -c" ;; + gksudo) Passwordcommand="gksudo --message 'x11docker $Imagename' --disable-grab" ; Passwordterminal="bash -c" ;; + lxsu) Passwordcommand="lxsu" ; Passwordterminal="bash -c" ;; + lxsudo) Passwordcommand="lxsudo" ; Passwordterminal="bash -c" ;; + kdesu) Passwordcommand="kdesu -c" ; Passwordterminal="bash -c" ;; + kdesudo) Passwordcommand="kdesudo --comment 'x11docker $Imagename'" ; Passwordterminal="bash -c" ;; + beesu) Passwordcommand="beesu -c" ; Passwordterminal="bash -c" ;; + none) Passwordcommand="bash -c" ; Passwordterminal="bash -c" ;; + *) warning "Unknown password prompt '$Passwordfrontend' (option --pw). + Possible: su sudo gksu gksudo lxsu lxsudo kdesu kdesudo beesu pkexec none" + Passwordcommand="$Passwordfrontend" ; Passwordterminal="bash -c" ;; + esac + [ "$Passwordneeded" = "yes" ] && { + command -v $(echo $Passwordcommand|cut -d' ' -f1) >/dev/null || { + warning "Password prompt frontend $(echo $Passwordcommand|cut -d' ' -f1) not found. + Fallback: using no password prompt (--pw=none)." + check_fallback + Passwordcommand="bash -c" ; Passwordfrontend="none" ; Passwordneeded="no" ; Passwordterminal="bash -c" + } + } + [ "$Passwordcommand" = "bash -c" ] && Passwordcommand="eval" + return 0 +} +check_runmode() { # check run/--exe/--xonly + # Basically x11docker divides between + # default: run docker image + # --exe: run host executeable + # --xonly: run X server only (changes here to --exe with sleep) + # + [ -z "$Imagename" ] && X11dockermode="xonly" + case $X11dockermode in + run) + Imagebasename="$(echo $Imagename | tr / - | cut -d: -f1)" + Codename="$Imagename $Containercommand" + + command -v $Dockerexe >/dev/null || error "docker is not installed. + To run docker images, you need to install docker. + $Dockerexe" + verbose "Image name: $Imagename + Container command: $Containercommand" + ;; + exe) + Hostexe="$Imagename $Containercommand" + [ "$Customdockeroptions" ] && Hostexe="$Customdockeroptions -- $Hostexe" # might be a command like 'grep -- 'expr' + Imagename="" + Containercommand="" + Imagebasename="$(basename "$Hostexe" | cut -d' ' -f1)" + Codename="$Hostexe" + command -v $Hostexe >/dev/null || error "Command '$Hostexe' not found." + verbose "Host application to execute: $Hostexe" + Runsinsnap="no" + ;; + xonly) + X11dockermode="exe" + Hostexe="sleep infinity" + Imagename="" + Containercommand="" + Codename="xonly" + Imagebasename="xonly" + Showdisplayenvironment="yes" + Runsinsnap="no" + ;; + esac + + Codename="$(unspecialstring "$Codename" | cut -c1-40)" + Codename="${Codename:-noname}" + Imagebasename="$(unspecialstring "$Imagebasename")" # must be - for backwards compatibility of --home + Imagebasename="${Imagebasename:-noname}" + + return 0 +} +check_terminalemulator() { # check terminal for password prompt of su or sudo + # $Passwordterminal: To prompt for su or sudo password + + # Not working: pangoterm lilyterm fbterm + # Makes problems if X and Wayland are independently available at same time: xfce4-terminal + # Works, but does not appear: 'guake -te' + + local Terminallist + + Terminallist="xterm mintty lxterm lxterminal stterm sakura termit pterm terminator terminology Eterm konsole qterminal gnome-terminal mate-terminal mrxvt rxvt xvt kterm mlterm xfce4-terminal bash" + [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && Terminallist="konsole qterminal gnome-terminal bash" + [ "$Runsinterminal" = "yes" ] && Terminallist="bash" + + for Passwordterminal in $Terminallist ; do command -v $Passwordterminal >/dev/null && break ; done + + [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && { + case $Passwordterminal in + qterminal) Passwordterminal="env QT_QPA_PLATFORM=wayland $Passwordterminal -e" ;; + konsole) Passwordterminal="env QT_QPA_PLATFORM=wayland dbus-run-session $Passwordterminal --nofork -e" ;; + esac + } + [ -z "$Hostdisplay$Hostwaylandsocket" ] && Passwordterminal="bash" + + case $Passwordterminal in + xfce4-terminal) Passwordterminal="$Passwordterminal --disable-server -x" ;; + mate-terminal) Passwordterminal="dbus-run-session $Passwordterminal -x" ;; + gnome-terminal) Passwordterminal="dbus-launch $Passwordterminal --" ;; + terminator) Passwordterminal="dbus-run-session $Passwordterminal --no-dbus -x" ;; + konsole) Passwordterminal="dbus-run-session $Passwordterminal --nofork -e" ;; + bash) Passwordterminal="eval" ;; + *) Passwordterminal="$Passwordterminal -e" ;; + esac + + return 0 +} +create_cachefiles() { # create empty cache files owned by unprivileged user + local Line + # create base cache folder + [ "$Cachebasefolder" ] || { + #Cachebasefolder="$Hostuserhome/.cache/x11docker" ### FIXME really a good idea for MS Windows? WSL cache provides performance, but maybe must not be shared with container to avoid file access errors. + case $Winsubsystem in + ""|MSYS2|CYGWIN) Cachebasefolder="$Hostuserhome/.cache/x11docker" ;; + WSL1|WSL2) + case $Mobyvm in + yes) + Cachebasefolder="$(convertpath subsystem "$(wincmd "echo %userprofile%")")/x11docker/cache" + mkdir -p "$Hostuserhome/.cache/x11docker/symlink" + [ -e "$Hostuserhome/.cache/x11docker/symlink" ] || ln -s -T "$Cachebasefolder" "$Hostuserhome/.cache/x11docker/symlink" + mkfile "$Hostuserhome/.cache/x11docker/symlink/symlink.txt" + echo "x11docker: With MobyVM x11docker cache in WSL is stored in +$Cachebasefolder +to allow file sharing with containers. +A symbolic link is created in WSL at +$Hostuserhome/.cache/x11docker/symlink +" >> "$Hostuserhome/.cache/x11docker/symlink/symlink.txt" + ;; + no) + Cachebasefolder="$Hostuserhome/.cache/x11docker" + ;; + esac + ;; + esac + } + [ "$Cachebasefolder" = "/x11docker/cache" ] && error "Failed to find a valid path for cache directory. + Please report at https://github.com/mviereck/x11docker + As a workaround you can specify a cache folder with --cachebasedir DIR." + + Cachebasefolder="$(convertpath subsystem "$Cachebasefolder")" + [ "$Cachebasefolder" != "$(echo $Cachebasefolder | sed -e 's/ *//g')" ] && error "Cache root folder must not contain whitespaces. + $Cachebasefolder" + unpriv "mkdir -p $Cachebasefolder" || error "Could not create cache folder + $Cachebasefolder" + writeaccess $Hostuseruid $Cachebasefolder || error "User $Hostuser does not have write access to cache folder + $Cachebasefolder" + + # Create cache subfolders + Cachefolder="$Cachebasefolder/$Codename-$Cachenumber" + [ -d "$Cachefolder" ] && error "Cache folder already exists: + $Cachefolder" + + [ "$Cachefolder" != "$(escapestring "$Cachefolder")" ] && error "Invalid name created for cache folder: + $Cachefolder + Most probably provided image name (or --exe command) is invalid in some way: + $(escapestring "$Imagename") + For special setups like command chains use a syntax like: + x11docker IMAGENAME -- sh -c \"cd /etc && xterm\"" + + Sharefolder="$Cachefolder/$Sharefolder" + unpriv "mkdir -p $Sharefolder" + + # Files in $Cachefolder: host only access + Compositorlogfile="$Cachefolder/$Compositorlogfile" && mkfile $Compositorlogfile + Dockercommandfile="$Cachefolder/$Dockercommandfile" && mkfile $Dockercommandfile + Dockerinfofile="$Cachefolder/$Dockerinfofile" && mkfile $Dockerinfofile + Dockerrc="$Cachefolder/$Dockerrc" && mkfile $Dockerrc + Dockerstopsignalfifo="$Cachefolder/$Dockerstopsignalfifo" + Hostxauthority="$Cachefolder/$Hostxauthority" && mkfile $Hostxauthority + Messagelogfile="$Cachefolder/$Messagelogfile" && mkfile $Messagelogfile + Nxagentclientrc="$Cachefolder/$Nxagentclientrc" && mkfile $Nxagentclientrc + Nxagentkeysfile="$Cachefolder/$Nxagentkeysfile" && mkfile $Nxagentkeysfile + Nxagentoptionsfile="$Cachefolder/$Nxagentoptionsfile" && mkfile $Nxagentoptionsfile + Pulseaudioconf="$Cachefolder/$Pulseaudioconf" && mkfile $Pulseaudioconf + Clipboardrc="$Cachefolder/$Clipboardrc" && mkfile $Clipboardrc + Storepidfile="$Cachefolder/$Storepidfile" && mkfile $Storepidfile + Systemdconsoleservice=$Cachefolder/$Systemdconsoleservice && mkfile $Systemdconsoleservice + Systemdenvironment=$Cachefolder/$Systemdenvironment && mkfile $Systemdenvironment + Systemdjournallogfile=$Sharefolder/$Systemdjournallogfile && mkfile $Systemdjournallogfile + Systemdjournalservice=$Cachefolder/$Systemdjournalservice && mkfile $Systemdjournalservice + Systemdtarget=$Cachefolder/$Systemdtarget && mkfile $Systemdtarget + Systemdwatchservice=$Cachefolder/$Systemdwatchservice && mkfile $Systemdwatchservice + Watchpidfifo="$Cachefolder/$Watchpidfifo" + Westonini="$Cachefolder/$Westonini" && mkfile $Westonini + Xdummyconf="$Cachefolder/$Xdummyconf" && mkfile $Xdummyconf + Xinitlogfile="$Cachefolder/$Xinitlogfile" && mkfile $Xinitlogfile + Xinitrc="$Cachefolder/$Xinitrc" && mkfile $Xinitrc + Xkbkeymapfile="$Cachefolder/$Xkbkeymapfile" && mkfile $Xkbkeymapfile + Xorgwrapper="$Cachefolder/$Xorgwrapper" && mkfile $Xorgwrapper + Xpraclientlogfile="$Cachefolder/$Xpraclientlogfile" && mkfile $Xpraclientlogfile + Xpraserverlogfile="$Cachefolder/$Xpraserverlogfile" && mkfile $Xpraserverlogfile + Xservercookie="$Cachefolder/$Xservercookie" && mkfile $Xservercookie + Xtermrc="$Cachefolder/$Xtermrc" && mkfile $Xtermrc + + # Files in $Sharefolder: shared to /x11docker in container + Cmdrc="$Sharefolder/$Cmdrc" && mkfile $Cmdrc + Cmdstderrlogfile="$Sharefolder/$Cmdstderrlogfile" && mkfile $Cmdstderrlogfile 666 + Cmdstdinfifo="$Sharefolder/$Cmdstdinfifo" + Cmdstdoutlogfile="$Sharefolder/$Cmdstdoutlogfile" && mkfile $Cmdstdoutlogfile 666 + Containerrc="$Sharefolder/$Containerrc" && mkfile $Containerrc + Containerenvironmentfile="$Sharefolder/$Containerenvironmentfile" && mkfile $Containerenvironmentfile 666 + Containerlocaltimefile="$Sharefolder/$Containerlocaltimefile" + Containerlogfile="$Sharefolder/$Containerlogfile" && mkfile $Containerlogfile 666 + Containerrootrc="$Sharefolder/$Containerrootrc" && mkfile $Containerrootrc + Logfile="$Sharefolder/x11docker.log" && mkfile $Logfile 666 + Messagefifo="$Sharefolder/$Messagefifo" + Pulseaudiocookie="$Sharefolder/$Pulseaudiocookie" + Pulseaudiosocket="$Sharefolder/$Pulseaudiosocket" + Storeinfofile="$Sharefolder/$Storeinfofile" && mkfile $Storeinfofile 666 + Timetosaygoodbyefile="$Sharefolder/$Timetosaygoodbyefile" && mkfile $Timetosaygoodbyefile + Timetosaygoodbyefifo="$Sharefolder/$Timetosaygoodbyefifo" + Xclientcookie="$Sharefolder/$Xclientcookie" && mkfile $Xclientcookie + + # Files in $Cachebasefolder + Dockerimagelistfile="$Cachebasefolder/$Dockerimagelistfile" && mkfile $Dockerimagelistfile + Logfilebackup="$Cachebasefolder/x11docker.log" + Modelinefile="$Cachebasefolder/$Modelinefile" + + # file to store display numbers in use today + Numbersinusefile="$Cachebasefolder/$Numbersinusefile" + for Line in $(find $Cachebasefolder/displaynumbers.* 2>/dev/null ||:) ; do + [ "$Line" != "$Numbersinusefile" ] && rm "$Line" + done + [ -e "$Numbersinusefile" ] || mkfile "$Numbersinusefile" + + # libc timezone file + [ -e "$Hostlocaltimefile" ] && cp "$Hostlocaltimefile" "$Containerlocaltimefile" + + storeinfo "cache=$Cachefolder" + storeinfo "stdout=$Cmdstdoutlogfile" + storeinfo "stderr=$Cmdstderrlogfile" + + return 0 +} +drop_cachefiles() { # remove some cache files that are not needed in current setup + case $Initsystem in + systemd) ;; + *) rm $Systemdconsoleservice $Systemdenvironment $Systemdjournallogfile $Systemdjournalservice $Systemdtarget $Systemdwatchservice ;; + esac + case $Xserver in + --xpra|--xpra-xwayland) ;; + *) rm $Xpraclientlogfile $Xpraserverlogfile ;; + esac + case $Xserver in + --weston|--weston-xwayland|--kwin|--kwin-xwayland|--xpra-xwayland|--xdummy-xwayland) ;; + *) rm $Compositorlogfile $Westonini ;; + esac + case $Xserver in + --weston|--kwin|--hostwayland|--tty) rm $Xclientcookie $Xservercookie ;; + *) [ "$Xauthentication" = "no" ] && rm $Xclientcookie $Xservercookie ;; + esac + case $Xserver in + --nxagent) ;; + *) rm $Nxagentclientrc $Nxagentkeysfile $Nxagentoptionsfile ;; + esac + case $Xserver in + --xdummy) ;; + --xpra) [ "$Xpravfb" = "Xdummy" ] || rm $Xdummyconf $Xorgwrapper ;; + *) rm $Xdummyconf $Xorgwrapper ;; + esac + case $X11dockermode in + exe) rm $Containerrootrc $Dockercommandfile $Dockerinfofile $Dockerrc ;; + esac + case $Xserver in + --xephyr|--xorg|--xdummy|--xdummy-xwayland|--xvfb|--xwayland|--weston-xwayland) + [ "$Shareclipboard" = "no" ] && rm $Clipboardrc ;; + *) rm $Clipboardrc ;; + esac + case $Xserver in + --hostdisplay|--xwin|--nxagent|--hostwayland|--weston|--kwin|--tty) rm $Xkbkeymapfile ;; + esac +} +option_messages() { # some messages depending on options, but not changing settings + # X server specific messages + case $Xserver in + --hostdisplay) + [ "$Autochooseserver" = "yes" ] && [ -z "$Winsubsystem" ] && case "$Sharegpu" in + yes) + case $Nvidiaversion in + "") note "To allow protection against X security leaks, + please install 'xinit' and one or more of: + xpra, weston+Xwayland or kwin_wayland+Xwayland, + or run a second Xorg server with option --xorg." ;; + *) note "To allow protection against X security leaks + while using --gpu with NVIDIA, please use option --xorg." ;; + esac + ;; + no) note "To allow protection against X security leaks, + please install 'xinit' and one or more of: + xpra, Xephyr, nxagent, weston+Xwayland, kwin_wayland+Xwayland or Xnest, + or run a second Xorg server with option --xorg." ;; + esac + case "$Trusted" in + no) + warning "Option --hostdisplay provides only low container isolation! + It is recommended to use another X server option like --nxagent or --xpra. + + To improve security with --hostdisplay x11docker uses untrusted cookies. + This can lead to strange behaviour of some applications. + + If you encounter application ${Colredbg}errors${Colnorm}, enable option --clipboard + that disables security restrictions for --hostdisplay as a side effect." ;; + yes) + case $Winsubsystem in + "") warning "Option --hostdisplay with trusted cookies provides + QUITE BAD CONTAINER ISOLATION ! + Keylogging and controlling host applications is possible! + Clipboard sharing is enabled (option --cliboard). + It is recommended to use another X server option like --nxagent or --xpra." ;; + MSYS2) ;; + CYGWIN) warning "Option --hostdisplay allows less security hardening. + It is recommended to use option --xwin instead." ;; + WSL1|WSL2) warning "Option --hostdisplay allows less security hardening. + It is recommended to use another X server option like --nxagent or --xephyr." ;; + esac + ;; + esac + [ "$Desktopmode" = "yes" ] && note "Can not avoid to use host window manager + along with option --hostdisplay. + You may get strange interferences with your host desktop. + Can be interesting though, having two overlapping desktops." + ;; + + --xorg) + [ "$Hostsystem" = "opensuse" ] && [ "$Runsonconsole" = "no" ] && [ "$Startuser" != "root" ] && warning "openSUSE does not support starting a second Xorg server + from within X. Possible solutions: + 1.) Install nested X server 'Xephyr', 'nxagent' or 'Xnest', + or for --gpu support: install 'Weston' and 'Xwayland'. + 2.) Switch to console tty1...tty6 with ... + and start x11docker there. + 3.) Run x11docker as root." + + case $Xlegacywrapper in + yes) warning "Although x11docker starts Xorg as unprivileged user, + most system setups wrap Xorg to give it root permissions (setuid). + Evil containers may try to abuse this. + Other x11docker X server options like --xephyr are more secure at this point." ;; + no) [ "$Startuser" = "root" ] && warning "x11docker will run Xorg as root." ;; + esac + + [ "$Runsoverssh" = "yes" ] && warning "x11docker can run Xorg on another tty (option --xorg), + but you won't see it in your SSH session. + Rather install e.g. Xephyr on ssh server and use option --xephyr." + ;; + + --xpra|--xpra-xwayland) + verlt "$Xpraversion" "v1.0" && { + note "Your xpra version $Xpraversion is out of date. It is + recommended to install at least xpra v1.0. Look at: www.xpra.org" + [ "$Desktopmode" = "yes" ] && { + note "Your xpra version does not support desktop mode. + Please use another X server option like --xephyr or --nxagent." + } ||: + } + [ "$Desktopmode" = "yes" ] && verlt "$Xpraversion" "v2.2-r17117" && note "Xpra desktop mode works best since xpra v2.2-r17117. + You have installed lower version xpra $Xpraversion. + It is recommended to use --xephyr or --nxagent instead. + Rendering issues can be reduced disabling OpenGL in Xpra tray icon. Screen + size issues can be avoided with non-integer scaling (e.g. --scale=1.01)." + [ "$Desktopmode" = "no" ] && verlt $Xprarelease r23066 && note "Xpra startup can be slow. For faster startup + with seamless applications, try --nxagent. + If security is not a concern, try --hostdisplay. + Xpra version v3.0-r23066 and higher starts up faster." + [ "$Sharegpu" = "yes" ] && note "If performance of GPU acceleration with $Xserver + is not satisfying, you can try insecure '--hostdisplay --gpu'." + note "Option --xpra: If you encounter issues with xpra, + you can try --nxagent instead. + Rather use xpra from www.xpra.org than from distribution repositories." + ;; + + # --xephyr) note "Xephyr is a quite stable nested X server. + #Less stable, but directly resizeable is nxagent with option --nxagent. + #Resizing of the Xephyr window is possible with xrandr, arandr or lxrandr." + # ;; + + --nxagent) + [ "$Hostsystem" = "mageia" ] && { + [ "$Desktopmode" = "no" ] && [ "$Autochooseserver" = "yes" ] && Desktopmode="yes" && Windowmanagermode="auto" + [ "$Desktopmode" = "no" ] && warning "nxagent version 3.5.0 on Mageia 6 is known to crash + in seamless mode. (Detected version: '$(strings --bytes 20 /usr/libexec/nx/nxagent | grep "NXAGENT - Version")'). + If you encounter issues, please try seamless --xpra (secure), + --hostdisplay (insecure), or run --nxagent in desktop mode with a + host window manager (--wm=WINDOWMANAGER or --wm=auto or short -wm)." + } + note "A few applications do not work well with --nxagent. + In that case, please try another X server option like --xephyr or --xpra." + ;; + + --weston|--kwin|--hostwayland) + note "You are running a pure Wayland environment. + X applications without Wayland support will fail." + [ "$Xserver" = "--kwin" ] && note "kwin_wayland (option --kwin) does not support the xdg_shell + interface in all versions. Some GTK3 Wayland applications depend on it. + If application startup fails, try --weston instead." + ;; + esac + + # NVIDIA without --gpu + [ "$Nvidiaversion" ] && [ "$Sharegpu" = "no" ] && case $Xserver in + --hostdisplay|--xorg) note "Option $Xserver may fail with proprietary NVIDIA driver + on host. In that case try other X server options like + --nxagent, --xpra or --xephyr." ;; + esac + + # --fullscreen + [ "$Fullscreen" = "yes" ] && { + case $Xserver in + --xephyr|--weston|--weston-xwayland|--nxagent|--xpra|--xpra-xwayland|--xwin) ;; + --xdummy|--xdummy-xwayland|--xvfb|--xorg) ;; + *) note "$Xserver does not support option --fullscreen" ;; + esac + } + + # --output-count + [ "$Outputcount" != "1" ] && { + case $Xserver in + --weston-xwayland) note "Xwayland sometimes does not position itself well + at origin 0+0 of first virtual screen, and some screens appear to be unused. + You may need to move Xwayland manually with [META]+[LeftMouseButton]. + (Bug report at https://bugzilla.redhat.com/show_bug.cgi?id=1498665 )" ;; + --xephyr) note "Xinerama support would be best for multiple outputs, + but is disabled in Xephyr because Xephyr does not handle it well. + Different window managers handle this different. Just try out." ;; + esac + } + + # --hostipc + [ "$Sharehostipc" = "yes" ] && warning "Option --hostipc severely degrades + container isolation. IPC namespace remapping is disabled." + + # --network + [ "$Network" = "host" ] && warning "Option --network=host severly degrades + container isolation. Network namespacing is disabled. + Container shares host network stack. + Spying on network traffic may be possible. + Access to host X server $Hostdisplay may be possible + through abstract unix socket." + + # --webcam + [ "$Sharewebcam" = "yes" ] && warning "Option --webcam: Container applications might look + at you and also might take screenshots of your Desktop." + + # --user=RETAIN / keep container defined in image + case $Createcontaineruser in + no) + [ "$Sudouser" = "yes" ] && note "Option --sudouser has limited support with --user=RETAIN. + x11docker will only set needed capabilities. + User setup and /etc/sudoers won't be touched. + Option --group-add=sudo might be useful." + ;; + esac + + [ "$Sudouser" = "yes" ] && [ "$Containeruseruid" != "0" ] && [ "$Network" != "host" ] && [ -z "$Xoverip" ] && note "Option --sudouser: If you want to run GUI application + with su or sudo, you might need to add either option --xoverip + or (discouraged) option --network=host." + + [ "$Customdockeroptions" ] && { + warning "Found custom DOCKER_RUN_OPTIONS. + x11docker will add them to 'docker run' command without + a serious check for validity or security. Found options: + $Customdockeroptions" + grep -q -- '--privileged' <<< "$Customdockeroptions" && warning "Found option --privileged + in custom docker run options. That is A VERY BAD IDEA. + A privileged setup allows unrestriced access from container to host. + Malicious applications can cause arbitrary harm." + grep -q -i -- '--cap-add.ALL' <<< "$Customdockeroptions" && warning "Found option --cap-add=ALL + in custom docker run options. That is A VERY BAD IDEA. + That is a very privileged setup. + Malicious applications may harm to the host." + grep -q -i -- '--cap-add.SYS_ADMIN' <<< "$Customdockeroptions" && warning "Found option --cap-add=SYS_ADMIN + in custom docker run options. That is A VERY BAD IDEA. + That is a very privileged setup. + Malicious applications may harm to the host." + grep -q -- '--entrypoint' <<< "$Customdockeroptions" && warning "Found option --entrypoint + in custom docker run options. x11docker uses this option, too. + This setup will probably fail. Use x11docker option --no-entrypoint instead + and add desired command as container command after the image name." + grep -q -- "--user" <<< "$Customdockeroptions" && warning "Found option --user in custom DOCKER_RUN_OPTIONS. + This might lead to errors or unexpected behaviour. + Please use x11docker option --user instead." + grep -q -- "--runtime" <<< "$Customdockeroptions" && note "Found option --runtime in custom DOCKER_RUN_OPTIONS. + Please use x11docker option --runtime instead." + grep -q -- "--network" <<< "$Customdockeroptions" && note "Found option --network in custom DOCKER_RUN_OPTIONS. + Please use x11docker option --network instead." + grep -q -- "--name" <<< "$Customdockeroptions" && note "Found option --name in custom DOCKER_RUN_OPTIONS. + Please use x11docker option --name instead." + grep -q -- "--group-add" <<< "$Customdockeroptions" && note "Found option --group-add in custom DOCKER_RUN_OPTIONS. + Please use x11docker option --group-add instead." + } + + return 0 +} +setup_fifo() { # set up fifo channels (also option --stdin) + # setup fifos to allow messages from within container, dockerrc and xinitrc + # and to send pids to watch to watchpidlist() thread + + # file descriptors in use: + # FDstderr stderr for warnings and notes redirected to &2, with --silent redirected to /dev/null + # FDmessage $Messagefifo for messages from other threads to watchmessagefifo() + # FDcmdstdin stdin>>$Cmdstinfile --stdin with catstdin, redirection of &0 + # FDtimetosaygoodbye $Timetosaygoodbyefifo for saygoodbye() and waitfortheend() + # FDwatchpid $Watchpidfifo for watchpidlist() + # FDdockerstop $Dockerstopsignalfifo to send docker stop singnal to dockerrc in finish() + + case "$Mobyvm" in + yes) Usemkfifo="no" ;; + no) Usemkfifo="yes" ;; + esac + [ "$Runtime" = "kata-runtime" ] && Usemkfifo="no" + + # redirect stdin to named pipe. Named pipe is shared with container and used as stdin of container command in containerrc + [ "$Forwardstdin" = "yes" ] && { + case $Usemkfifo in + yes) unpriv "mkfifo $Cmdstdinfifo" ;; + no) mkfile $Cmdstdinfifo ;; + esac + exec {FDcmdstdin}<>$Cmdstdinfifo + cat <&0 >${FDcmdstdin} & storepid $! catstdin + storeinfo "stdin=$Cmdstdinfifo" + } + + case $Usemkfifo in + yes) + unpriv "mkfifo $Watchpidfifo" + unpriv "mkfifo $Messagefifo && chmod 666 $Messagefifo" + unpriv "mkfifo $Timetosaygoodbyefifo" + ;; + no) # Windows, kata + mkfile $Watchpidfifo + mkfile $Messagefifo 666 + mkfile $Timetosaygoodbyefifo 666 + ;; + esac + + case $Mobyvm in + no) + # used by finish() and dockerrc + unpriv "mkfifo $Dockerstopsignalfifo" + exec {FDdockerstop}<>$Dockerstopsignalfifo + ;; + esac + + # used by waitfortheend() + exec {FDtimetosaygoodbye}<>$Timetosaygoodbyefifo + + # start watching important pids, e.g. xinit, container. + exec {FDwatchpid}<>$Watchpidfifo + watchpidlist & storepid $! watchpidlist + + # start watching for messages out of container or dockerrc + exec {FDmessage}<>$Messagefifo + watchmessagefifo & storepid $! watchmessagefifo + + return 0 +} +setup_verbosity() { # options --verbose, --stdout, --stderr + local Line Logfiles + # create summary logfile + Logfiles=" + $Cmdstderrlogfile + $Cmdstdoutlogfile + $Compositorlogfile + $Containerlogfile + $Systemdjournallogfile + $Messagelogfile + $Xinitlogfile + $Xpraclientlogfile + $Xpraserverlogfile + " + for Line in $Logfiles; do + [ -e "$Line" ] && grep -q "/" <<< "$Line" && Logfiles="$Logfiles $Line" + done + { + trap '' SIGINT + tail --pid="$$" --retry -n +1 -F $Logfiles 2>/dev/null >>$Logfile ||: + } & + + # option --verbose + [ "$Verbose" = "yes" ] && { + trap '' SIGINT + case $Verbosecolors in + no) tail --pid="$$" --retry -n +1 -F $Logfile 2>/dev/null >&${FDstderr} ;; + yes) tail --pid="$$" --retry -n +1 -F $Logfile 2>/dev/null | sed " + /\(Failed to add fd to store\|Failed to set invocation ID\|Failed to reset devices.list\)/d; + s/\(ERROR\|Error\|error\|FAILURE\|FATAL\|Fatal\|fatal\)/${Colredbg}\1${Colnorm}/g; + s/\(Failed\|failed\|Failure\|failure\)/${Colred}\1${Colnorm}/g; + s/\(WARNING\|Warning\|warning\)/${Colyellow}\1${Colnorm}/g; + s/\(DEBUGNOTE\)/${Colblue}\1${Colnorm}/g; + s/^==>.*/${Coluline}\0${Colnorm}/; + s/\(Starting\|Activating\)/${Colgreen}\0${Colnorm}/; + s/\(Started\|Reached target\|activated\)/${Colgreenbg}\0${Colnorm}/; + s/^\(+\|++\|+++\)/${Colgreenbg}\0${Colnorm}/ ; + s/^x11docker/${Colgreen}\0${Colnorm}/ " >&${FDstderr} + ;; + esac + } & + + [ "$Showcontaineroutput" = "yes" ] && { + { + waitforlogentry tailstdout "$Storeinfofile" "x11docker=ready" "" infinity ||: + trap '' SIGINT + tail --pid="$$" -n +1 -f $Cmdstdoutlogfile 2>/dev/null ||: + } & + { + waitforlogentry tailstderr "$Storeinfofile" "x11docker=ready" "" infinity ||: + trap '' SIGINT + tail --pid="$$" -n +1 -f $Cmdstderrlogfile >&2 2>/dev/null ||: + } & + } + + return 0 +} + +#### main +declare_variables() { # declare global variables + export IFS=$' \n\t' # set IFS to default + + # Global environment variables used in x11docker + ALSA_CARD=$(check_envvar "${ALSA_CARD:-}") + CUPS_SERVER=$(check_envvar "${CUPS_SERVER:-}") + DBUS_SESSION_BUS_ADDRESS=$(check_envvar "${DBUS_SESSION_BUS_ADDRESS:-}") + DISPLAY=$(check_envvar "${DISPLAY:-}") + GDK_BACKEND=$(check_envvar "${GDK_BACKEND:-}") + HOME=$(check_envvar "${HOME:-}") + LANG=$(check_envvar "${LANG:-}") + LC_ALL=$(check_envvar "${LC_ALL:-}") + PATH="$(check_envvar -w "${PATH:-}")" + WAYLAND_DISPLAY=$(check_envvar "${WAYLAND_DISPLAY:-}") + XAUTHORITY=$(check_envvar "${XAUTHORITY:-}") + XDG_CURRENT_DESKTOP=$(check_envvar "${XDG_CURRENT_DESKTOP:-}") + XDG_RUNTIME_DIR=$(check_envvar "${XDG_RUNTIME_DIR:-}") + XDG_VTNR=$(check_envvar "${XDG_VTNR:-}") + + # Add possibly missing PATH entries + PATH="${PATH:-"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/games:/usr/games"}" + grep -q ':/sbin:' <<< ":$PATH:" || PATH="$PATH:/sbin" + grep -q ':/usr/sbin:' <<< ":$PATH:" || PATH="$PATH:/usr/sbin" + grep -q ':/usr/local/bin:' <<< ":$PATH:" || PATH="$PATH:/usr/local/bin" + grep -q ':/usr/bin:' <<< ":$PATH:" || PATH="$PATH:/usr/bin" + grep -q ':/usr/local/games:' <<< ":$PATH:" || PATH="$PATH:/usr/local/games" + grep -q ':/usr/games:' <<< ":$PATH:" || PATH="$PATH:/usr/games" + export PATH + + # File descriptors + FDcmdstdin="" # --stdin channel to forward stdin to container. Previously &7 + FDdockerstop="" # message channel to send docker stop signal to dockerrc. Previously &4 + FDmessage="" # message channel for notes, warnings and verbosity across threads and container. Previously &6 + #FDstderr="" # internal stderr >&2, redirected to null with --silent. Alread declared in main(). Previously &3 + FDtimetosaygoodbye="" # message channel to send termination signal from or to containers. Previously &8 + FDwatchpid="" # message channel for watchpidlist(). Previously &9 + + # Terminal colors used for messages and -V + Esc="$(printf '\033')" + Colblue="${Esc}[35m" + Colyellow="${Esc}[33m" + Colgreen="${Esc}[32m" + Colgreenbg="${Esc}[42m" + Colred="${Esc}[31m" + Colredbg="${Esc}[41m" + Coluline="${Esc}[4m" + Colnorm="${Esc}[0m" + + # x11docker startup environment + Runsinsnap="" # docker runs in Ubuntu snap yes/no + Runsinteractive="" # --enforce-i: Script runs in bash interactive mode (bash -i) yes/no. + Runsinterminal="" # x11docker runs in a terminal yes/no + Runsonconsole="" # x11docker runs on tty yes/no + Runsoverssh="" # x11docker runs over SSH yes/no. Makes a difference for --hostdisplay + Runssourced="" # x11docker has been sourced yes/no + + # Generated scripts + Clipboardrc="clipboardrc" # --clipboard: Generated script for text clipboard sharing + Cmdrc="cmdrc" # Generated script starting container command + Containerrc="containerrc" # Generated script starting cmdrc + Containerrootrc="containerrootrc" # Generated script to set up container, e.g. user creation. Runs as root in container. + Dockerrc="dockerrc" # Generated script to check image, set up host and create $Containerrc + Xinitrc="xinitrc" # Generated script to set up X, e.g. cookie and xrandr + Xtermrc="xtermrc" # Generated script for password prompt + + # Internal messages + Dockerstopsignalfifo="$Dockerrc.stopfifo" # finish() send 'docker stop' signal to dockerrc + Logmessages="" # Stores messages until logfile is available, needed by logentry() + Messagefifo="message.fifo" # Message channel for warning/verbose/debugnote/note/error within container, dockerrc, containerrootrc and others + Storeinfofile="store.info" # File to store some info like id, pid, name, exit code + Storepidfile="store.pids" # File to store pids and names of background processes that should be terminated on exit + Timetosaygoodbyefile="timetosaygoodbye" # File giving term signal to all parties + Timetosaygoodbyefifo="timetosaygoodbye.fifo" # Message channel for --init=openrc|runit|sysvinit to shut down on x11docker signal + Usemkfifo="" # Not on Windows nor with kata-runtime + Watchpidfifo="watchpid.fifo" # Message channel to transfer pids to watchpidlist() + X11dockererrorfile="x11docker.error" # Store error exit code + + # Logfiles + Compositorlogfile="compositor.log" # Logfile for weston or kwin_wayland + Containerlogfile="container.log" # Logfile for container output other than container command output + Logfile="" # $Cachefolder/x11docker.log (current log) + Logfilebackup="" # $Cachebasefolder/x11docker.log (latest terminated log) + Messagelogfile="message.log" # Logfile for warning/verbose/debugnote/note/error + Xinitlogfile="xinit.log" # Logfile for xinit/X server + Xpraclientlogfile="xpra.client.log" # Logfile for xpra client + Xpraserverlogfile="xpra.server.log" # Logfile for xpra server + + Dockercommandfile="docker.command" # File to store generated docker command, needed for --interactive + Dockerimagelistfile="docker.imagelist" # File with list of local images.Used by x11docker-gui, too + Dockerinfofile="docker.info" # File to store outpu of 'docker info' + + # Generated commands + Compositorcommand="" # Command to start Weston or KWin + Dockercommand="" # Command to run docker + Wmdockercommand="" # --wm: Command to run x11docker/openbox + Xcommand="" # Command to start X server + Xpraclientcommand="" # xpra client command + Xpraservercommand="" # xpra server command + + # Users + Hostuser="" # $Lognameuser or --hostuser. Unprivileged user for non-root commands. Compare unpriv() + Hostusergid="" + Hostuserhome="" + Hostuseruid="" + Containeruser="" # --user: Container user. Default: same as $Hostuser. + Containeruseruid="" + Containerusergid="" + Containerusergroup="" + Containerusergroups="" # --group-add: Additional groups for container user + Containeruserhome="" # HOME path within container + Containeruserhosthome="" # HOME path of container user on host + Containeruserpassword="sac19FwGGTx/A" # Encrypted password "x11docker", suits /etc/shadow. Generated with: perl -e 'print crypt("x11docker", "salt"),"\n"' + Createcontaineruser="yes" # exception: --user=RETAIN + Lognameuser="" # $(logname) or $SUDO_USER or $PKEXEC_USER + Persistanthomevolume="" # --home: Path to shared host folder or docker volume used as HOME in container. + Startuser="" # User who started x11docker + Unpriv="" # Command to run commands as unprivileged user + Containerusershell="auto" # --shell: Preferred user shell + + # Hostsystem + Hostarchitecture="" # uname -m, checked + Hostcanwatchroot="" # x11docker can watch root processes yes/no. Related to $Hosthidepid + Hostdisplay="" # Environment variable DISPLAY + Hostdisplaynumber="" # DISPLAY without : (and without possible IP) + Hosthidepid="" # /proc is mounted with hidepid=2 yes/no. Seen on NixOS. + Hostip="" # An IP address to access host. Preferred: IP of docker daemon + Hostlibc="" # glibc or musl. Can be important for locale and timezone. + Hostlocaltimefile="" # Time zone from host, myrealpath /etc/localtime + Hostmitshm="yes" # X on host has extension MIT-SHM enabled yes/no. Assume yes, check later + Hostsystem="" # $ID from /etc/os-release + Hostutctime="" # Time zone from host as offset to UTC + Hostwaylandsocket="$WAYLAND_DISPLAY" # Store host wayland socket name + Hostxauthority="Xauthority.host.$(unspecialstring "${DISPLAY:-unknown}")" # File to store copy of $XAUTHORITY + Hostxenv="" # Collection of host X environment variables + Hostxsocket="" # Socket of DISPLAY in /tmp/.X11-unix + Nvidiacontainerfile="/usr/local/bin/NVIDIA-installer.run" # --gpu: Path to nvidia installer in container + Nvidiaversion="" # --gpu: Proprietary nvidia driver version on host + Nvidiainstallerfile="" # --gpu: Proprietary nvidia driver installer for container in [...]local/share/x11docker + + # MS Windows + Winpty="" # Path to winpty for --interactive on MS Windows + Winsubmount="" # Path within subsystem to mounted MS Windows drives + Winsubpath="" # Path within MS Windows to subsystem files + Winsubsystem="" # MS Windows subsystem WSL1, WSL2, MSYS2 or CYGWIN + Mobyvm="" # MS Windows: Use MobyVM yes/no (No only for WSL2 possible) + + # Cache folders + Cachebasefolder="" # --cachebasedir Base cache folder + Cachefolder="" # Subfolder of $Cachebasefolder for current container + Sharefolder="share" # Subfolder of $Cachefolder for cache files shared with container + Sharefoldercontainer="/x11docker" # Mountpoint of $Sharefolder in container + + # stdin stdout stderr + Cmdstdinfifo="stdin" # stdin for container command. fifo/named pipe to forward stdin of x11docker to container command + Cmdstderrlogfile="stderr" # stderr for container command + Cmdstdoutlogfile="stdout" # stdout for container command + Forwardstdin="no" # --stdin: forward stdin to container command yes/no + + # X and Wayland configuration + Autochooseserver="yes" # --auto: automatical choice of X server (default) + Cleanxhost="no" # --clean-xhost: remove xhost access policies on host X + Compositorerrorcodes="Failed to process Wayland|failed to create display|] fatal:" + Desktopmode="no" # --desktop: image contains a desktop enironment. + Dpi="" # --dpi: dots per inch. Influenxes font size + Fullscreen="no" # --fullscreen: Fullscreen mode + Iglx="" # --iglx: Enable indirect rendering yes/no/"" + Lastcheckedxserver="" # check_xdepends(): Last X server option that was checked + Lastcheckedxserverresult="" # check_xdepends(): Result of last check. Avoids double-checking. + Maxxaxis="" # Maximal X screen size of display + Maxyaxis="" # Maximal Y screen size of display + Modelinefile="modelines" + Newdisplay="" # --display: New DISPLAY for new X server + Newdisplaynumber="" # --display: New display number for new X server. + Newwaylandsocket="" # Wayland socket of $Compositorcommand + Newxenv="" # Environment variables for new X server: DISPLAY XAUTHORITY XSOCKET WAYLAND_DISPLAY + Newxlock="" # .Xn-lock - exists for running X server with socket n + Newxsocket="" # New X unix socket + Newxvt="" # --vt: number of virtual console to use for --xorg + Numbersinusefile="displaynumbers.$(date +%y_%m_%d)" # File to store display numbers used today. Helps to avoid race conditions on simultuanous startups + Nxagentclientrc="nxagent.nxclientrc" # --nxagent NX_CLIENT script to catch nxagent messages + Nxagentkeysfile="nxagent.keys" # --nxagent keyboard shortcut config + Nxagentoptionsfile="nxagent.options" # --nxagent options not available on cli, but possible in config file + Modeline="" # Screen modeline describing display size, see "man cvt". Needed for Xdummy + Outputcount="1" # --output-count: quantum of virtual screens for Weston or Xephyr + Rotation="" # --rotate: Rotation for --weston, --weston-xwayland or --xorg: 0/90/180/270/flipped/flipped-90/.. + Scaling="" # --scale: Scaling factor for xpra and weston + Screensize="" # --size XxY: Display size + Setupwayland="no" # --wayland, --kwin, --weston --hostwayland: Provide a Wayland environment + Trusted="yes" # Create trusted or untrusted cookies, --hostdisplay uses untrusted cookies by default + Waylandtoolkitenv="XDG_SESSION_TYPE=wayland GDK_BACKEND=wayland QT_QPA_PLATFORM=wayland CLUTTER_BACKEND=wayland SDL_VIDEODRIVER=wayland ELM_DISPLAY=wl ELM_ACCEL=opengl ECORE_EVAS_ENGINE=wayland_egl" + Waylandtoolkitvars="XDG_SESSION_TYPE GDK_BACKEND QT_QPA_PLATFORM CLUTTER_BACKEND SDL_VIDEODRIVER ELM_DISPLAY ELM_ACCEL ECORE_EVAS_ENGINE" + Xauthentication="yes" # --no-auth: use cookie authentication and disable xhost yes/no + Xaxis="" # Virtual screen width + Xcomposite="" # --xcomposite: +extension COMPOSITE yes/no + Xkblayout="" # --keymap: Layout for keymap, compare /usr/share/X11/xkb/symbols + Xfishtank="no" # --xfishtank: Show a fish tank on new X server + Xhost="" # --xhost: custom xhost setting on new X server + Xiniterrorcodes="xinit: giving up|unable to connect to X server|Connection refused|server error|Only console users are allowed" + Xlegacywrapper="" # --xorg: /etc/X11/Xwrapper.config is configured to run within X yes/no + Xpraborder="" # --border: Colored border for xpra clients + Xpracontainerenv="UBUNTU_MENUPROXY= QT_X11_NO_NATIVE_MENUBAR=1 MWNOCAPTURE=true MWNO_RIT=true MWWM=allwm GTK_OVERLAY_SCROLLING=0 GTK_CSD=0 NO_AT_BRIDGE=1" # environment variables + Xprahelp="" # Output of 'xpra --help' + Xprarelease="" # Release number from $Xpraversion + Xprashm="" # Content XPRA_XSHM=0 disables usage of MIT-SHM in xpra + Xpraversion="" # $(xpra --version) to decide some xpra options and messages + Xpravfb="" # --xpra, --xdummy, --xvfb: vfb for --xpra: Xdummy or Xvfb + Xserver="" # X server option to use + Xoverip="" # --xoverip: Connect to X over TCP yes/no + Xserveroptions="" # --xopt: Custom X server options + Xtest="" # --xtest: Enable extension Xtest yes/no. If empty, yes for --xpra/--xdummy/--xvfb, otherwise no + Yaxis="" # Virtual screen height + + # X and Wayland config and cookie files + Customwestonini="" # --westonini: Custom config file for weston + Westonini="weston.ini" # Generated config file for weston + Xdummyconf="xorg.xdummy.conf" # --xdummy, --xpra: Generated xorg.conf for dummy video driver + Xclientcookie="Xauthority.client" # Generated X client cookie. Normally same as $Xservercookie, except for --hostdisplay and --nxagent + Xkbkeymapfile="xkb.keymap" # --keymap: File to store output of host keymap in xinitrc + Xorgconf="" # --xorgconf: custom xorg.conf + Xservercookie="Xauthority.server" # Generated X server cookie + Xorgwrapper="Xorg.xdummy.wrapper" # --xpra, --xdummy: Fork from xpra to wrap Xorg for Xdummy + + # Window manager + Windowmanagermode="none" # --wm: Window manager to use: container/host/auto + Windowmanagercommand="" # --wm: Argument for --wm, host command or docker image + Hostwindowmanager="" # --wm: A window manager from host, given or autodetected + + # Host integration + Alsacard="$ALSA_CARD" # --alsa: Specified ALSA card + Hosthomebasefolder="" # --homebasedir: Base directory for container home with --home + Langwunsch="" # --lang: Search or create UTF-8 locale in container and set LANG + Pulseaudioconf="pulseaudio.client.conf" # --pulseaudio: Client config in container + Pulseaudiocookie="pulseaudio.cookie" # --pulseaudio: possible pulse cookie from host to share + Pulseaudiomode="" # --pulseaudio: 'tcp', 'socket' or 'auto' + Pulseaudiomoduleid="" # --pulseaudio: module ID, stored for unload in finish() + Pulseaudioport="" # --pulseaudio: TCP port for --pulseaudio=tcp + Pulseaudiosocket="pulseaudio.socket" # --pulseaudio: unix socket for --pulseaudio=socket + Sharealsa="no" # --alsa: enable ALSA sound, share /dev/snd + Shareclipboard="no" # --clipboard: Enable clipboard sharing + Sharecupsmode="" # --printer: Share access to CUPS printer server: socket|tcp|"" + Sharegpu="no" # --gpu: Use hardware accelerated OpenGL, share files in /dev/dri + Sharehome="no" # --home: Share a folder ~/.local/share/x11docker/Imagename with created container + Sharevolumes="" # --share: Host files or folders or devices to share, array + Sharevolumescount="0" # --share: Counts shared folders in array + Sharewebcam="no" # --webcam: Share webcam device /dev/video* + + # Container setup + Adminusercaps="no" # --cap-default, --sudouser, --user=root, --init=systemd: add capabilities for general container system administration + Allownewprivileges="auto" # --newprivileges: Docker run option --security-opt=no-new-privileges. Default: no. Enabled by options --newprivileges, --cap-default, --sudouser and --user=root. + Capabilities="" # Capabilities to add. Default: none, exceptions for --init, --sudouser + Capdropall="yes" # --cap-default: Drop all container capabilities and set --securty-opt=no-new-privileges yes/no + Containercommand="" # Container command [+args] + Containerenvironment="" # --env: Environment variables + Containerenvironmentcount="0" + Containerenvironmentfile="container.environment" # file to store final container environment + Containerlocaltimefile="libc.localtime" # localtime file from host shared to container + Containername="" # --name: Container name + Containersetup="yes" + Customdockeroptions="" # -- [...] -- : Custom options for "docker run". + Imagename="" # Image to run + Interactive="no" # --interactive: Run docker with interactive tty yes/no + Limitresources="" # --limit: Limit access to CPU and RAM, 0.1 ... 1.0 + Network="" # --network + Noentrypoint="no" # --no-entrypoint: Disable entrypoint in image yes/no + Podman="no" # --podman: Use podman instead of docker + Runtime="" # Runtime to use. runc|nvidia|kata-runtime|crun + Sharehostipc="no" # --hostipc: Set --ipc=host. + Stopsignal="" # Signal to send on 'docker stop' + Sudouser="no" # --sudouser: Create user with sudo permissions and root user with password 'x11docker' + Switchcontaineruser="no" # --init=systemd|openrc|runit|sysvinit: User switching to trigger login services yes/no + Switchcontainerusercaps="no" # --init=systemd|openrc|runit|sysvinit, --sudouser, --user=root: Add capabilities for su/sudo user switching + Systemdconsoleservice="systemd.console-getty.service" # --init=systemd + Systemdenvironment="systemd.environment.conf" + Systemdjournallogfile="systemd.journal.log" + Systemdjournalservice="systemd.journal.service" + Systemdtarget="systemd.x11docker.target" + Systemdwatchservice="systemd.watch.service" + Workdir="" # --workdir: Set working directory in container + + # Init and DBus + Dbusrunsession="no" # --dbus, --wayland, --init=systemd|openrc|runit|sysvinit: Run container command with dbus-run-session / DBus user session + Dbussystem="no" # --init=systemd|openrc|runit|sysvinit: Run DBus system daemon in container + Initsystem="tini" # --init: Init system in container + Sharecgroup="no" # --sharecgroup, --init=systemd: share /sys/fs/cgroup. Also needed for elogind + Sharehostdbus="no" # --hostdbus: Connect to DBus user daemon on host + Tinibinaryfile="" # --init=tini (default): Binary of tini; either /usr/bin/docker-exec or provided by user in [...]/share/x11docker + Tinicontainerpath="/usr/local/bin/init" # --init=tini: Path of tini (or catatonit) in container + + # Gaining root privileges to run docker + Passwordcommand="" # --pw: Generated command for password prompt + Passwordfrontend="" # --pw: Frontend for password. One of pkexec, su, sudo, gksu, gksudo, kdesu, kdesudo, lxsu, lxsudo, beesu, auto, none + Passwordneeded="yes" # Password needed to run docker? assume yes, check later + Sudo="" # "sudo", "sudo -n", or empty. Added as prefix to some privileged commands, especially docker. + + # Custom additional commands + Runasuser="" # --runasuser: Add container command to containerrc + Runasroot="" # --runasroot: Add container command to container setup script running as root + Runfromhost="" # --runfromhost: Add host command to xinitrc + + # Miscellanous + Cachenumber="$(date +%s%N | cut -c6-16)" # Number to use for cache folder + [ -z "$Cachenumber" ] && Cachenumber="$(makecookie)" + Codename="" # created from image name and command without special chars for use with container name and cache folder + Dockerexe="" # Can be docker.exe on MS Windows + Fallback="yes" # --fallback: Allow or deny fallbacks for failing options. + Hostexe="" # --exe: Host command + Imagebasename="" # Image name without tags and / replaced with -. For use of --home folders. + Parsedoptions_global="" # Parsed options + Passwordterminal="" # Terminal emulator to use for password prompt (if no terminal emulator is needed, it will be 'bash -c') + Presetdirlocal="$HOME/.config/x11docker/preset" + Presetdirsystem="/etc/x11docker/preset" + Preservecachefiles="no" # If yes, don't delete cache files on exit. For few failure cases only. + Pullimage="ask" # --pull: Allow 'docker pull' yes|no|always|ask + X11dockermode="run" # --exe, --xonly: Can be either "run" (default), "exe", or "xonly". + + # Verbosity options + Debugmode="no" # --debug: Excerpt of --verbose, also bash error checks + Showcache="no" # --showcache: Output of $Cachefolder on stdout (x11docker-gui only) + Showcontainerid="no" # --showid: Output of container ID on stdout + Showcontaineroutput="yes" # Show container command stdout + Showcontainerpid1pid="no" # --showpid1: Output of host PID of container PID 1 on stdout + Showdisplayenvironment="no" # --showenv: Output of environment variables of new display on stdout + Showinfofile="no" # --showinfofile: Show path of $Storeinfofile + Silent="no" # --quiet: Do not show x11docker messages + Verbose="no" # --verbose: Be verbose yes/no + Verbosecolors="no" # -V: colored output for --verbose (and delete some noisy systemd error messages) + Wikipackages="You can look for the package name of this command at: + https://github.com/mviereck/x11docker/wiki/dependencies#table-of-all-packages" + + # Special options not starting X or docker + Cleanup="no" # --cleanup: Remove orphaned containers and cache files + Createlauncher="no" # --launcher: Create application launcher on desktop and exit yes/no + Installermode="" # --install/--update/--update-master/--remove + + # Lists of window managers + # - these window managers are known to work well with x11docker (alphabetical order)(excluding $Wm_not_recommended and $Wm_ugly): + Wm_good="amiwm blackbox cinnamon compiz ctwm enlightenment fluxbox flwm fvwm" + Wm_good="$Wm_good jwm kwin kwin_x11 lxsession mate-session mate-wm marco metacity notion olwm olvwm openbox ororobus pekwm" + Wm_good="$Wm_good sawfish twm wmaker w9wm xfwm4" + # - these wm's are recommended and lightweight, but cannot show desktop options. best first: + Wm_recommended_nodesktop_light="xfwm4 metacity marco openbox sawfish" + # - these wm's are recommended and heavy, but cannot show desktop options (especially exiting themselves). best first: + Wm_recommended_nodesktop_heavy="kwin compiz" + # - these wm's are recommended, lightweight AND desktop independent. best first: + Wm_recommended_desktop_light="flwm blackbox fluxbox jwm mwm wmaker afterstep amiwm fvwm ctwm pekwm olwm olvwm openbox" + # - these wm's are recommended, heavy AND desktop independent. best first: + Wm_recommended_desktop_heavy="lxsession mate-session enlightenment cinnamon cinnamon-session plasmashell" + # - these wm's are not really useful (please don't hit me) (best first): + Wm_not_recommended="awesome evilwm herbstluftwm i3 lwm matchbox miwm mutter spectrwm subtle windowlab wmii wm2" + # - these wm's cannot be autodetected by wmctrl if they are already running + Wm_nodetect="aewm aewm++ afterstep awesome ctwm mwm miwm olwm olvwm sapphire windowlab wm2 w9wm" + # - these wm's can cause problems (they can be beautiful, though): + Wm_ugly="icewm sapphire aewm aewm++" + # - these wm's doesn't work: + Wm_bad="budgie-wm clfswm tinywm tritium muffin gnome-shell" + # List of all working window managers, recommended ones first, excluding $Wm_bad: + Wm_all="$Wm_recommended_nodesktop_light $Wm_recommended_nodesktop_heavy $Wm_recommended_desktop_light $Wm_recommended_desktop_heavy $Wm_good $Wm_ugly $Wm_not_recommended $Wm_nodetect" + + # x11docker communication functions to integrate into generated scripts + Messagefifofuncs=' +warning() { + echo "$*:WARNING" | sed "s/\$/ /" >>$Messagefile +} +note() { + echo "$*:NOTE" | sed "s/\$/ /" >>$Messagefile +} +verbose() { + echo "$*:VERBOSE" | sed "s/\$/ /" >>$Messagefile +} +debugnote() { + echo "$*:DEBUGNOTE" | sed "s/\$/ /" >>$Messagefile +} +error() { + echo "$*:ERROR" | sed "s/\$/ /" >>$Messagefile + exit 64 +} +stdout() { + echo "$*:STDOUT" | sed "s/\$/ /" >>$Messagefile +}' + Messagefifofuncs_escaped=' +warning() { + echo \"\$*:WARNING\" | sed \"s/\\\$/ /\" >>\$Messagefile +} +note() { + echo \"\$*:NOTE\" | sed \"s/\\\$/ /\" >>\$Messagefile +} +verbose() { + echo \"\$*:VERBOSE\" | sed \"s/\\\$/ /\" >>\$Messagefile +} +debugnote() { + echo \"\$*:DEBUGNOTE\" | sed \"s/\\\$/ /\" >>\$Messagefile +} +error() { + echo \"\$*:ERROR\" | sed \"s/\\\$/ /\" >>\$Messagefile + exit 64 +} +stdout() { + echo \"\$*:STDOUT\" | sed \"s/\\\$/ /\" >>\$Messagefile +}' +} +parse_options() { # parse cli options + local Shortoptions Longoptions Parsedoptions Presetoptions Presetfile Parsererror Parsererrorfile + Shortoptions="aAcdDefFghHiIKlmnpPqtTvVwWxXyY" + Longoptions="exe,xonly" # Alternate setups of x11docker + Longoptions="$Longoptions,auto,desktop,tty,wayland,wm::" # Influencing auto-setup of X/Wayland/x11docker + Longoptions="$Longoptions,hostdisplay,nxagent,xdummy,xephyr,xpra,xorg,xvfb,xwin" # X servers + Longoptions="$Longoptions,kwin-xwayland,weston-xwayland,xpra-xwayland,xwayland" # X servers depending on a Wayland compositor + Longoptions="$Longoptions,hostwayland,kwin,weston" # Wayland compositors without X + Longoptions="$Longoptions,border::,dpi:,fullscreen,output-count:,rotate:,scale:,size:,xfishtank" # X/Wayland appearance options + Longoptions="$Longoptions,clean-xhost,display:,keymap:,no-auth,vt:,westonini:,xhost:,xoverip,xtest::" # X/Wayland config + Longoptions="$Longoptions,enforce-i,preset:,pull::,pw:" # x11docker config + Longoptions="$Longoptions,cachebasedir:,home::,homebasedir:,share:" # Host folders + Longoptions="$Longoptions,alsa::,clipboard,gpu,lang::,printer::,pulseaudio::,webcam" # Host integration features + Longoptions="$Longoptions,env:,mobyvm,name:,no-entrypoint,runtime:,workdir:" # Container config + Longoptions="$Longoptions,cap-default,hostipc,limit::,newprivileges::,network::" # Container capabilities + Longoptions="$Longoptions,group-add:,hostuser:,sudouser,user:,shell:" # Container user + Longoptions="$Longoptions,dbus::,init::,hostdbus,sharecgroup" # Container init and DBus + Longoptions="$Longoptions,stdin,interactive" # Container interaction + Longoptions="$Longoptions,runasuser:,runfromhost:,runasroot:" # Additional commands to execute + Longoptions="$Longoptions,showenv,showid,showinfofile,showpid1,showcache" # Output of vars on stdout + Longoptions="$Longoptions,debug,quiet,verbose" # Verbose options + Longoptions="$Longoptions,cleanup,help,launcher,licence,license,version,wmlist" # Special options without starting X or container + Longoptions="$Longoptions,install,remove,update,update-master" # Installation + # + Longoptions="$Longoptions,iglx,keepcache,no-setup,podman,xcomposite,xopt:,xorgconf:,runx" # Experimental + Longoptions="$Longoptions,dbus-system,homedir:,hostnet,no-internet,no-xhost,sharedir:,sharessh,systemd" # Deprecated + Longoptions="$Longoptions,cachedir:,no-init,nothing,no-xtest,openrc,ps,runit,silent,starter,stderr,stdout" # Removed + Longoptions="$Longoptions,sys-admin,sysvinit,tini,trusted,untrusted,vcxsrv" # Removed + + Parsererrorfile="/tmp/x11docker.parserserror.$Cachenumber" + Parsedoptions="$(getopt --options "$Shortoptions" --longoptions "$Longoptions" --name "$0" -- "$@" 2>$Parsererrorfile)" + [ -e $Parsererrorfile ] && Parsererror=$(cat $Parsererrorfile) && rm $Parsererrorfile + [ "$Parsererror" ] && error "$Parsererror" + eval set -- "$Parsedoptions" + [ -z "$Parsedoptions_global" ] && Parsedoptions_global="$Parsedoptions" + + [ "$*" = "-h --" ] && usage && exit 0 # Catch single -h for usage info, otherwise it means --hostdisplay + [ "$*" = "--" ] && usage && exit 0 # x11docker without options + [ "$*" = "-- --" ] && usage && exit 0 # x11docker-gui without options + + while [ $# -gt 0 ]; do + case "${1:-}" in + --hostdisplay|--hostwayland|--kwin|--kwin-xwayland|--nxagent|--tty|--weston|--weston-xwayland|--xpra-xwayland|--xephyr|--xorg|--xwayland|--xwin|-h|-H|-K|-n|-t|-T|-Y|-y|-A|-x|-X) + [ -n "$Xserver" ] && note "Please use only one X server or Wayland option at a time. + You have set option '${1:-}' after option '$Xserver'. + ${1:-} will take effect." + ;; + --xpra|--xvfb|--xdummy|-a) + case $Xserver in + "") ;; + --xpra|-a) ;; + --xvfb|--xdummy) + [ "${1:-}" != "--xpra" ] && note "Please use only one X server or Wayland option at a time. + You have set option '${1:-}' after option '$Xserver'. + ${1:-} will take effect." + ;; + *) + note "Please use only one X server or Wayland option at a time. + You have set option '${1:-}' after option '$Xserver'. + ${1:-} will take effect." + ;; + esac + ;; + esac + case "${1:-}" in + --help) usage ; exit 0 ;; # Show help/usage and exit + --license|--licence) license ; exit 0 ;; # Show MIT license and exit + --version) echo $Version ; exit 0 ;; # Output version number and exit + --wmlist) echo $Wm_all ; exit 0 ;; # Special option for x11docker-gui to retrieve list of window managers + + -e|--exe) X11dockermode="exe" ;; # Execute application from host instead of running docker image + --xonly) X11dockermode="xonly" ;; # Only create X server + + #### Predefined option sets + --preset) Presetoptions="" + Presetfile="" + [ -f "$Presetdirsystem/${2:-}" ] && Presetfile="$Presetdirsystem/${2:-}" + [ -f "$Presetdirlocal/${2:-}" ] && Presetfile="$Presetdirlocal/${2:-}" + [ -f "/${2:-}" ] && Presetfile="/${2:-}" + [ -f "$Presetfile" ] || error "Option --preset: File not found: ${2:-} + Searching as an absolute path and in: + $Presetdirlocal + $Presetdirsystem" + Presetoptions="$(sed '/^#/d' < "$Presetfile" | tr '\n' ' ')" + debugnote "--preset ${2:-}: + $Presetoptions" + [ "$Presetoptions" ] && eval parse_options $Presetoptions + shift ;; + + #### Choice of X servers and Wayland compositors + --auto) Autochooseserver="yes" ;; # Default: auto-choose X server or Wayland compositor + -h|--hostdisplay) Xserver="--hostdisplay" ;; # Host display :0 with shared X socket + -H|--hostwayland) Xserver="--hostwayland" ;; # Host wayland. Allows coexistence with option + -K|--kwin) Xserver="--kwin" ;; # KWin, Wayland only + --kwin-xwayland) Xserver="--kwin-xwayland" ;; # KWin + Xwayland + -n|--nxagent) Xserver="--nxagent" ;; # nxagent + --runx) Xserver="--runx" ;; # MS Windows: Will be Xwin or VcXsrv + -t|--tty) Xserver="--tty" ;; # Do not provide any X nor Wayland + -T|--weston) Xserver="--weston" ;; # Weston, Wayland only + -Y|--weston-xwayland) Xserver="--weston-xwayland" ;; # Weston + Xwayland + --xdummy) [ "$Xserver" = "--xpra" ] && Xpravfb="Xdummy" || Xserver="--xdummy" ;; # Xdummy. Invisible on host. + -y|--xephyr) Xserver="--xephyr" ;; # Xephyr + -a|--xpra) [ "$Xserver" = "--xdummy" ] && Xpravfb="Xdummy" + [ "$Xserver" = "--xvfb" ] && Xpravfb="Xvfb" + Xserver="--xpra" ;; # xpra + -A|--xpra-xwayland) Xserver="--xpra-xwayland" ;; # Xpra with vfb Xwayland + -x|--xorg) Xserver="--xorg" ;; # Xorg + --xvfb) [ "$Xserver" = "--xpra" ] && Xpravfb="Xvfb" || Xserver="--xvfb" ;; # Xvfb. Invisible on host. + -X|--xwayland) Xserver="--xwayland" ;; # Xwayland on already running Wayland + --xwin) Xserver="--xwin" ;; # XWin, MS Windows only + + #### Influencing automatical choice of X server or Wayland compositor + -d|--desktop) Desktopmode="yes" ;; # image contains a desktop environment. + -g|--gpu) Sharegpu="yes" ;; # share files in /dev/dri, allow GPU usage + -W|--wayland) Setupwayland="yes" ;; # set up wayland environment, regards --desktop + -w) Windowmanagermode="auto" ;; + --wm) case "${2:-}" in # choose window manager + "n"|"none") Windowmanagermode="none" ;; + "host") Windowmanagermode="host" ;; + "container") Windowmanagermode="container" ;; + ""|"auto"|"m") Windowmanagermode="auto" ;; + *) Windowmanagermode="auto"; Windowmanagercommand="${2:-}" ;; + esac + shift ; Desktopmode="yes" ;; + + #### X and Wayland appearance + --border) Xpraborder="${2:-"blue,1"}"; shift ;; # Colored border for xpra clients + --dpi) Dpi=${2:-} ; shift ;; # Dots per inch. Influences font size + -f|--fullscreen) Fullscreen="yes" ;; # Fullscreen mode for Xephyr and Weston + --output-count) Outputcount="${2:-}" ; shift ;; # Number of virtual outputs + --rotate) Rotation=${2:-} ; shift ;; # Rotation and mirroring + --scale) Scaling=${2:-} ; shift ;; # Zoom + --size) Screensize="${2:-}" ; shift ;; # Screen size + -F|--xfishtank) Xfishtank="yes" ;; # Run xfishtank on new X server + + #### X and Wayland configuration + --display) Newdisplaynumber="${2:-}" # Display number to use for new X server or Wayland compositor + [ "$(echo $Newdisplaynumber | cut -c1)" = ":" ] && Newdisplaynumber="$(echo $Newdisplaynumber | cut -c2-)" + shift ;; + --keymap) Xkblayout="${2:-}" ; shift ;; # Keymap layout for xkbcomp. Compare /usr/share/X11/xkb/symbols + --vt) Newxvt="${2:-}" ; shift ;; # Virtual console to use for --xorg + --xoverip) Xoverip="yes" ;; # Use X over TCP/IP instead of sharing X socket + --xtest) case "${2:-}" in # X extension XTEST + yes|"") Xtest="yes" ;; + no) Xtest="no" ;; + *) warning "Invalid argument for option --xtest [=yes|no]: ${2:-}" ;; + esac; shift ;; + --westonini) Customwestonini="${2:-}" ; shift ;; # Custom weston.ini + + #### X Authentication + --clean-xhost|--no-xhost) Cleanxhost="yes" # Disable xhost credentials on host X + [ "${1:-}" = "--no-xhost" ] && note "Option --no-xhost is deprecated. + Please use --clean-xhost instead." ;; + --no-auth) Xauthentication="no" ;; # Disable cookie authentication on new X, set xhost +. Use for debugging only + --xhost) Xhost="$2" ; shift ;; # Custom xhost setting on new X server + + #### Host integration options + --alsa) Sharealsa="yes" # ALSA sound (shares /dev/snd) + Alsacard="${2:-$Alsacard}" ; shift ;; + -c|--clipboard) Shareclipboard="yes" ;; # Clipboard sharing + -l) Langwunsch="$Langwunsch +${LANG:-}" # Locale/language setting + Langwunsch="${Langwunsch:-$LC_ALL}" + [ "$Langwunsch" ] || note "Option --lang: Environment variable \$LANG is empty. + Please specify desired language locale with e.g. --lang=en_US or --lang=zh_CN." ;; + --lang) Langwunsch="$Langwunsch +${2:-${LANG:-}}" ; shift # Locale/language setting + Langwunsch="${Langwunsch:-$LC_ALL}" + [ "$Langwunsch" ] || note "Option --lang: Environment variable \$LANG is empty. + Please specify desired language locale with e.g. --lang=en_US or --lang=zh_CN." ;; + -P|--printer) Sharecupsmode="${2:-auto}" ; shift ;; # Printer sharing with CUPS + -p) Pulseaudiomode="auto" ;; # Pulseaudio sound + --pulseaudio) Pulseaudiomode="${2:-auto}"; shift ;; # Pulseaudio sound + --webcam) Sharewebcam="yes" ;; # Webcam sharing + + #### Special options + --enforce-i) ;; # Run in bash interactive mode. Parsed at begin of script, nothing to do here. + -i|--interactive) Interactive="yes" ;; # Interactive terminal + --pull) Pullimage="${2:-yes}" ; shift ;; # Allow 'docker pull' + --pw) Passwordfrontend="${2:-}" ; shift ;; # Password prompt frontend + --runasroot) Runasroot="$Runasroot +${2:-}" ; shift ;; # Add custom root command in container setup script + --runasuser) Runasuser="$Runasuser +${2:-}" + shift ;; # Add custom user command in cmdrc + --runfromhost) Runfromhost="$Runfromhost +${2:-}" ; shift ;; # Add custom host command in xinitrc + + #### User settings + --group-add) Containerusergroups="$Containerusergroups ${2:-}" ; shift ;; # Additional groups for container user + --hostuser) Hostuser="${2:-}" ; shift ;; # Set host user different from logged in user + --shell) Containerusershell="${2:-}" ; shift ;; # Set preferred user shell + --sudouser) Sudouser="yes" ;; # su and sudo for container user with password x11docker + --user) Containeruser="${2:-}" ; shift # Set container user other than host user + [ "$Containeruser" = "RETAIN" ] && Createcontaineruser="no" ;; + + #### Init system and DBus + --dbus) Dbusrunsession="${2:-yes}" ; shift ;; # DBus in container, Default: user session, =system: with system daemon + --hostdbus) Sharehostdbus="yes" ;; # Connect to host DBus + --init) Initsystem="${2:-tini}" ; shift ;; # init in container + --sharecgroup) Sharecgroup="yes" ;; # Share /sys/fs/cgroup. Default for --init=systemd, possible use with --init=openrc or elogind. + --systemd) Initsystem="systemd" ; note "Option --systemd is deprecated. Please use: --init=systemd" ;; + + #### Container configuration + --cap-default) Capdropall="no" ;; # Don't use --cap-drop=ALL --security-opt=no-new-privileges + --env) store_runoption env "${2:-}" # Set container environment variables + shift ;; + --hostipc) Sharehostipc="yes" ;; # docker run option --ipc=host + --limit) Limitresources="${2:-0.5}" ; shift ;; # Limited CPU and RAM access + --mobyvm) Mobyvm="yes" ;; # Use MobyVM in WSL2 + --name) Containername="${2:-}" ; shift ;; # Set container name + -I) Network="" ;; + --network) Network="${2:-}" ; shift ;; + --newprivileges) Allownewprivileges="${2:-yes}" ; shift ;; # [Don't] set --security-opt=no-new-privileges + --no-entrypoint) Noentrypoint="yes" ;; # Disable ENTRYPOINT of image + --runtime) Runtime="${2:-}" ; shift # Runtime=runc|nvidia|kata-runtime|crun + [ "$Runtime" = "kata" ] && Runtime="kata-runtime" ;; + --stdin) Forwardstdin="yes" ;; # Forward stdin to container command + --workdir) Workdir="${2:-}" ; shift ;; # Set working directory + + #### host folders and docker volumes + -m) Sharehome="host" ;; + --home|--homedir) Sharehome="yes" # Share host folder as HOME in container, ~/x11docker/imagename or $2 + [ "${1:-}" = "--homedir" ] && note "Option --homedir is deprecated. + Please use --home=DIR instead." + Persistanthomevolume="${2:-}" ; shift ;; + --share|--sharedir) store_runoption volume "${2:-}" # Share host file, device or directory + [ "${1:-}" = "--sharedir" ] && note "Option --sharedir is deprecated. + Please use option --share=PATH instead." + shift ;; + --homebasedir) Hosthomebasefolder="${2:-}" ; shift ;; # Set base folder for --home instead of ~/.local/share/x11docker + --cachebasedir) Cachebasefolder="${2:-}" ; shift ;; # Set base folder for cache instead of ~/.cache/x11docker + + #### Verbosity options + -D|--debug) Debugmode="yes" ;; # Debugging mode + --showinfofile) Showinfofile="yes" ;; # Show path to $Storeinfofile + -v|--verbose) Verbose="yes" ;; # Be verbose + -V) Verbose="yes"; Verbosecolors="yes";; # Be verbose with colored output + -q|--quiet) Silent="yes" ;; # Do not show warnings or errors + --showcache) Showcache="yes" ;; # Output of $Cachefolder. For x11docker-gui only + --showenv) Showdisplayenvironment="yes" ;; # Output of display number and cookie file on stdout. Catch with: read xenv < <(x11docker --showenv) + --showid) Showcontainerid="yes" ;; # Output of container id on stdout + --showpid1) Showcontainerpid1pid="yes" ;; # Output of host PID of container PID 1 + + #### Special options not starting X or docker + --cleanup) Cleanup="yes" ;; # Remove orphaned containers and cache files + --install|--update|--update-master|--remove) Installermode="${1:-}" ;; # Installer + --launcher) Createlauncher="yes" ;; # Create application launcher on desktop and exit + + #### Experimental options + --iglx) Iglx="yes" ; note "Option --iglx: experimental option." ;; # Indirect rendering; broken since Xorg ~18.2 + --keepcache) Preservecachefiles="yes" ; note "Option --keepcache: experimental option." ;; + --no-setup) Containersetup="no" ; note "Option --no-setup: experimental option." ;; + --podman) Podman="yes" ; note "Option --podman: experimental option. + Please report issues at: https://github.com/mviereck/x11docker/issues/255" ;; + --xcomposite) Xcomposite="yes" ; note "Option --xcomposite: experimental option." ;; # Enable X extension COMPOSITE + --xopt) Xserveroptions="${2:-}" ; note "Option --xopt: experimental option." ; shift ;; # Custom X server options + --xorgconf) Xorgconf="${2:-}" ; note "Option --xorgconf: experimental option." ; shift ;; # Custom xorg.conf + + #### Deprecated options + --dbus-system) note "Option --dbus-system is deprecated. + Please use one of --init=systemd|openrc|runit|sysvinit instead. + Fallback: Enabling options --dbus=system --cap-default" + check_fallback + Dbusrunsession="system" + Capdropall="no" ;; + --hostnet) Network="host" + note "Option --hostnet is deprecated. + Please use --network=host instead." ;; + --no-internet) Network="none" + note "Option --no-internet is deprecated. + Please use --network=none instead." ;; + --sharessh) [ -e "${SSH_AUTH_SOCK:-}" ] && { # SSH socket sharing + store_runoption volume "$(dirname $SSH_AUTH_SOCK)" + store_runoption env "SSH_AUTH_SOCK=$(escapestring "${SSH_AUTH_SOCK:-}")" + } || note "Option --sharessh: environment variable \$SSH_AUTH_SOCK not set:" ; + note "Option --sharessh is deprecated. + Please use (directly or with help of option --preset): + --share \$(dirname \$SSH_AUTH_SOCK) --env SSH_AUTH_SOCK=\"\$SSH_AUTH_SOCK\"" ;; + + #### Removed options + --vcxsrv) error "Option --vcxsrv is no longer supported. + Please use either option --xwin in Cygwin/X + or run x11docker with runx in WSL or MSYS2. + For 'runx' look at: https://github.com/mviereck/runx" ;; + --no-init|--openrc|--runit|--sysvinit|--tini) + error "Option ${1:-} has been removed. + Please use option --init=INITSYSTEM instead." ;; + --cachedir|--nothing|--no-xtest|--ps|--silent|--starter|--stderr|--stdout|--sys-admin|--trusted|--untrusted) + error "Option ${1:-} has been removed. + Please have a look at 'x11docker --help' for possible replacements + or search for '${1:-}' in /usr/share/doc/x11docker/CHANGELOG.md." ;; + + ##### Custom docker options / image name + container command. Everything after -- + --) + shift + [ "$(cut -c1 <<< "${1:-}")" = "-" ] && grep -q " -- " <<< " $* " && { + while [ $# -gt 0 ] ; do + [ "${1:-}" = "--" ] && shift && break + Customdockeroptions="$Customdockeroptions '${1:-}'" + shift + done + } + while [ $# -gt 0 ] ; do + [ -n "${1:-}" ] && [ -z "$Imagename" ] && [ "$(echo "${1:-}" | cut -c1)" = "-" ] && Customdockeroptions="$Customdockeroptions ${1:-}" + [ -n "${1:-}" ] && [ -z "$Imagename" ] && [ "$(echo "${1:-}" | cut -c1)" != "-" ] && Imagename="${1:-}" && shift + [ -n "${1:-}" ] && [ -n "$Imagename" ] && Containercommand="$Containercommand '${1:-}'" + shift + done + ;; + '') ;; + *) error "Unknown option ${1:-} + Parsed options: + $Parsedoptions" ;; + esac + shift + done + + Customdockeroptions="$(sed "s/--cap-add' '/--cap-add=/" <<< "$Customdockeroptions")" + Customdockeroptions="$(sed "s/--runtime' '/--runtime=/" <<< "$Customdockeroptions")" + grep -q -- "--runtime.kata" <<< "$Customdockeroptions" && Runtime="kata-runtime" + grep -q -- "--runtime.nvidia" <<< "$Customdockeroptions" && Runtime="nvidia" + grep -q -- "--runtime.runc" <<< "$Customdockeroptions" && Runtime="runc" + grep -q -- "--runtime.crun" <<< "$Customdockeroptions" && Runtime="crun" + + [ -n "$Xserver" ] && Autochooseserver="no" + + return 0 +} +unpriv() { # run a command as unprivileged user. Needed if x11docker was started by root or with sudo. + # $Unpriv is declared in check_hostuser: 'eval' or 'su $Hostuser -c' + $Unpriv "${1:-}" +} +main() { + trap finish EXIT + trap finish_sigint SIGINT + + exec {FDstderr}>&2 # stderr channel for warning(), error(), note(), debugnote() and --verbose + + declare_variables + parse_options "$@" + + [ "$Silent" = "yes" ] && exec {FDstderr}>/dev/null # --quiet + [ "$Debugmode" = "yes" ] && { # --debug + set -Eu + trap 'traperror $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]})' ERR + } + + # check host, create cache + check_host # get some infos about host system #time0,234 + check_runmode # modes: run image, or host command, or X only # --exe, --xonly + check_hostuser # find unprivileged host user # --hostuser + create_cachefiles # create cache files owned by unprivileged user # --cachebasedir + check_hostxenv # check X environment from host + storeinfo "x11dockerpid=$$" # store pid of x11docker + + debugnote " +x11docker version: $Version +docker version: $($Dockerexe --version 2>&1) +Host system: $(grep '^PRETTY_NAME' /etc/os-release 2>/dev/null | cut -d= -f2 || echo "$Hostsystem") +Host architecture: $Hostarchitecture +Command: '$0' $(for Line in "$@"; do echo -n "'$Line' " ; done) +Parsed options: $Parsedoptions_global" + + # Special x11docker jobs + [ "$Createlauncher" = "yes" ] && { create_launcher ; exit ; } # --launcher: Create application launcher icon on desktop + [ "$Cleanup" = "yes" ] && { cleanup ; exit ; } # --cleanup: Clean up cache and orphaned x11docker containers + [ "$Installermode" ] && { # --install, --update, --update-master, --remove + [ "$Startuser" = "root" ] || [ "$Winsubsystem" = "CYGWIN" ] || [ "$Winsubsystem" = "MSYS2" ] || error "Must run as root to install, update or remove x11docker." + installer $Installermode + exit + } + + # check options + check_xserver # check chosen X server or auto-choose one + check_option_interferences # check options, change settings if needed + option_messages # some messages depending on options, but not changing anything + + # container user + check_containeruser # unprivileged user in container # --user + check_containerhome # create persistant container home # --home, --homebasedir + + # some checks and setup + drop_cachefiles # remove cachefiles not needed for current setup + setup_verbosity # create [and show] summary logfile # --verbose + setup_fifo # open message channels for container, dockerrc, xinitrc and watchpidlist() + check_screensize # size of host X and of new X server # --size #time0,213 + [ "$Windowmanagermode" ] && check_windowmanager # --wm + [ "$Sharegpu" = "yes" ] && setup_gpu # --gpu + [ "$Sharewebcam" = "yes" ] && setup_webcam # --webcam + [ "$Sharecupsmode" ] && setup_printer # --printer + [ "$Pulseaudiomode" ] && setup_sound_pulseaudio # --pulseaudio + [ "$Sharealsa" = "yes" ] && setup_sound_alsa # --alsa + [ "$Cleanxhost" = "yes" ] && clean_xhost # --clean-xhost + + #### Create command to run X server [and/or Wayland compositor] + [ "$Xserver" != "--xorg" ] && [ -n "$Newxvt" ] && note "Option --vt only takes effect with option --xorg." + [ "$Xserver" = "--xorg" ] && [ -z "$Newxvt" ] && check_vt # --vt: find free tty/virtual terminal for Xorg + { [ "$Xserver" = "--xdummy" ] || [ "$Xpravfb" = "Xdummy" ] ; } && create_xdummyxorgconf && create_xdummywrapper + check_newxenv # find free display, create $Newxenv + create_xcommand # set up start command for X server # all X server and Wayland options + [ "$Xcommand" ] && debugnote "X server command: + $Xcommand" + [ "$Compositorcommand" ] && debugnote "Compositor command: + $Compositorcommand" + [ "$Shareclipboard" = "yes" ] && setup_clipboard # --clipboard + + check_terminalemulator # find terminal emulator like xterm for error messages and 'docker pull' + check_passwordfrontend # check for su/sudo/gksu/pkexec etc. # --pw #time0,230 + setup_initsystem # init in container. Default: tini # --init + [ "$Runsinterminal" = "no" ] && [ "$Passwordneeded" = "yes" ] && warning "You might need to run x11docker in terminal + for password prompt if prompting for password with a GUI fails." + + debugnote "Users and terminal: + x11docker was started by: $Startuser + As host user serves (running X, storing cache): $Hostuser + Container user will be: $( [ "$Createcontaineruser" = "yes" ] && echo $Containeruser || echo "(retaining USER of image)") + Container user password: $( [ "$Createcontaineruser" = "yes" ] && echo x11docker || echo "(unknown)") + Getting permission to run docker with: $Passwordcommand $Sudo + Terminal for password frontend: $Passwordterminal + Running in a terminal: $Runsinterminal + Running on console: $Runsonconsole + Running over SSH: $Runsoverssh + Running sourced: $Runssourced + bash \$-: $-" + [ "$Winsubsystem" ] && debugnote " + Running on Windows subsystem: $Winsubsystem + Path to subsystem: $(convertpath windows $Winsubpath)/ + Mount path in subsystem: $Winsubmount/ + Using MobyVM: $Mobyvm" + + #### Create docker command + [ "$X11dockermode" = "run" ] && { + # core setup of docker command + setup_capabilities # add linux capabilities if needed for some options. Default: --cap-drop=ALL + [ "$Sharehostdbus" = "yes" ] && setup_hostdbus # --hostdbus + create_dockercommand # create 'docker run' command #time0,631 + echo "$Dockercommand" >> $Dockercommandfile + + debugnote "Docker command: + $Dockercommand" + + #### Create helper scripts to set up container + ## dockerrc runs as root (or member of group docker) on host. + # Main jobs: check image, pull image if needed, create script containerrc to run container command + create_dockerrc + verbose "Generated dockerrc: +$(nl -ba <$Dockerrc)" + ## containerrootrc runs as root in container. + # Main jobs: create unprivileged container user, disable possible privilege leaks, set local time. + # Optional jobs: run init system, run DBus daemon, install nvidia driver, create language locale. + [ "$Containersetup" = "yes" ] && { + create_containerrootrc + verbose "Generated containerrootrc: +$(nl -ba <$Containerrootrc)" + } + create_xtermrc # xtermrc to prompt for password if needed. + } + + #### Create helper script xinitrc to set up X + ## xinitrc is started by xinit and does some setup within new X server. + # Main job: create cookie, check xhost, set keyboard layout. + # Optional jobs: run window manager, run xfishtank, run host command, share clipboard, scale/rotate --xorg, create set of screen resolutions. + create_xinitrc + verbose "Generated xinitrc: +$(nl -ba <$Xinitrc)" + [ -s "$Westonini" ] && verbose "Generated weston.ini: +$(nl -ba <$Westonini)" + + { #### Run docker image + # For code flow logic, start_xserver() should run here first and be moved to background. + # For technical reasons, xinit must not run in a subshell: + # --xorg on tty only works if xinit runs in foreground to grab the tty. + # Otherwise, Xwrapper.config must be edited to 'allowed_users=anybody' even on console. + # Thus docker runs in this subshell after X server is ready to accept connections. + # Waiting for X is done in dockerrc. + + trap '' SIGINT + + # start container + case $X11dockermode in + run) start_docker ;; # (default) + exe) start_hostexe ;; # --exe, --xonly + esac + Pid1pid="$(storeinfo dump pid1pid)" + + # watch container + case $X11dockermode in + run) + case $Mobyvm in + no) setonwatchpidlist "${Pid1pid:-NOPID}" pid1pid ;; + yes) setonwatchpidlist "CONTAINER$Containername" ;; + esac + ;; + exe) setonwatchpidlist "${Pid1pid:-NOPID}" pid1pid ;; + esac + + # watch xinit and X server + case $Xserver in + --tty|--hostdisplay|--hostwayland|--weston|--kwin) ;; + *) + Xinitpid="$(pgrep -a xinit 2>/dev/null | grep "xinit $Xinitrc" | awk '{print $1}')" + checkpid "$Xinitpid" && setonwatchpidlist $Xinitpid xinit + echo $Xcommand | grep -q Xorgwrapper && Line="Xorg $Newdisplay" || Line="$(head -n1 <<< "$Xcommand" | tr -d '\\')" + Xserverpid=$(ps aux | rmcr | grep "$(echo "${Line:-nothingtolookfor}" | cut -d' ' -f1-2)" | grep -v grep | grep -v xinit | awk '{print $2}') + checkpid "$Xserverpid" && setonwatchpidlist "$Xserverpid" Xserver + ;; + esac + + [ "$Pulseaudiomode" = "tcp" ] && start_pulseaudiotcp # --pulseaudio=tcp + + # some debug output + checkpid "$Pid1pid" && debugnote "Process tree of ${Hostexe:-container}: (maybe not complete yet) +$(pstree -cp $Pid1pid 2>&1 ||:)" + debugnote "Process tree of x11docker: +$(pstree -p $$ 2>&1 ||:) + $(storepid test dockerstopshell && echo "Lost child of dockerrc (dockerstopshell): + $(pstree -p $(storepid dump dockerstopshell) 2>&1 ||:)")" + debugnote "storeinfo(): Stored info: +$(cat $Storeinfofile)" + debugnote "storepid(): Stored pids: +$(cat $Storepidfile)" + + # optional info on stdout + [ "$Showinfofile" = "yes" ] && echo "$Storeinfofile" # --showinfofile + [ "$Showcache" = "yes" ] && echo "$Cachefolder" # --showcache (x11docker-gui only) + [ "$Showdisplayenvironment" = "yes" ] && echo "$(storeinfo dump Xenv)" # --showenv + [ "$Showcontainerid" = "yes" ] && echo "$(storeinfo dump containerid)" # --showid + [ "$Showcontainerpid1pid" = "yes" ] && echo "$Pid1pid" # --showpid1 + + storeinfo "x11docker=ready" + } <&0 & storepid $! containershell + + #### Start X server [and/or Wayland compositor] [and xpra] + waitforlogentry "start_xserver()" $Storeinfofile "readyforX=ready" "" infinity + [ "$Xpraservercommand" ] && { + { + waitforlogentry xpra $Storeinfofile "xinitrc=ready" infinity + rocknroll || exit 0 + start_xpra # --xpra, --xpra-xwayland + } & storepid $! xpraloop + } + rocknroll && [ "$Compositorcommand" ] && start_compositor + rocknroll && start_xserver + + saygoodbye main +} + +main "$@"