#! /usr/bin/env bash # x11docker # Run GUI applications and desktop environments in docker # # - Runs additional X servers to circumvents common X security leaks. # - Restricts container capabilities to improve container security. # - Container user is same as host user to avoid root in container. # - Features e.g. sound, hardware acceleration and data storage. # # Type 'x11docker --help' or scroll down to read usage information. # More documentation at: https://github.com/mviereck/x11docker Version="5.4.4-beta" usage() { # --help: show usage information echo " x11docker: Run GUI applications and desktop environments in docker. Usage: To run a docker image with new X server: 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 a new empty X server: x11docker [OPTIONS] --xonly Optional features: * Hardware acceleration for OpenGL * Pulseaudio and ALSA sound * Clipboard sharing * Printer access * Webcam access * Persistent home folders * Wayland support * Language locale creation * Init system in container (systemd, SysVinit, OpenRC, runit, tini) * DBus in container 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. Note that some applications might behave different than with a regular docker run command due to security restrictions set by x11docker. Unrestricted container setup is possible with: --user=root --cap-default Dependencies on host: Depending on chosen options, x11docker needs some packages to be installed. It will check for them on startup and show messages if some are missing. List of possibly needed packages: * most recommended to allow security and convenience: X servers: Xephyr xpra nxagent (on Windows: VcXsrv or Xwin) X tools: xauth xrandr xhost xinit * advanced GPU support: Desktop mode: weston Xwayland Seamless mode: weston Xwayland xpra xdotool * less important: xclip xdpyinfo xdg-utils pulseaudio * least important: unzip wget xfishtank kwin_wayland Xvfb xserver-xorg-legacy xserver-xorg-video-dummy Dependencies in image: Doesn't have dependencies inside of docker images, except for options: --gpu: OpenGL packages, often already installed as package dependencies. --pulseaudio: sound with pulseaudio needs pulseaudio on host and in image. --lang: localedef to create missing language locales. --dbus, --dbus-system, --hostdbus: needs dbus in image. --systemd, --runit,--openrc, --sysvinit: init systems need to be installed. Options: --help display this message and exit. -e, --exe execute host application on new X server (no docker). --xonly only create empty X server. Basic settings: (especially influencing auto choosing X server) -d, --desktop Indicate desktop environment in image. -g, --gpu Hardware accelerated OpenGL rendering. Shares files in /dev/dri. Works best with open source drivers installed on host and OpenGL/Mesa in image. For closed source nvidia drivers regard terminal output. Degrades container isolation. Container access to GPU. -w, --wm COMMAND Host window manager to use for single applications in nested X server options like --xephyr. To autodetect a host wm, use --wm=auto or short: -wm To set default autodetected window manager: update-alternatives --config x-window-manager Shared folders: -m, --home Share a host folder ~/.local/share/x11docker/imagename as home folder in container to store persistent data. ~/.local/share/x11docker has a softlink to ~/x11docker --homedir DIR Specify custom host folder DIR for option --home. --homebasedir DIR Custom base folder for option --home. (Compare --cachebasedir below). --sharedir DIR Share host folder (or file) DIR. DIR:ro sets read-only. Clipboard, sound, printer, language: -c, --clipboard Share clipboard between X servers (works best with xpra. Most other X servers need xclip to be installed). -p, --pulseaudio [=MODE] Sound with pulseaudio. Degrades isolation. Needs 'pulseaudio' on host and in image. Optional arg MODE can be 'socket' (default) or 'tcp'. --alsa [=CARDNAME] Sound with ALSA. Shares devices in /dev/snd. You can define a desired sound card with CARDNAME. Get a list of available sound cards with: aplay -l Degrades isolation, container access to sound hardware. --lang LOCALE Language setting: search for utf8 LOCALE in image and create it if missing. Needs package 'locale' im image. LOCALE can be e.g. ru, en, de, zh_CN, cz, fr, fr_BE. Same as host: --lang=\$LANG. --printer Share host printers through CUPS server. --webcam Share host webcam device files. Special options: --env VAR=value Set custom environment variable VAR=value Special use case for user shell: '--env SHELL=/bin/sh' -i, --interactive Run with an interactive tty to allow shell commands. --name NAME Specify container name NAME. --no-internet Disable internet access for container. --no-entrypoint Disable ENTRYPOINT in image to allow other commands, too --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). no: Do not run '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 --runfromhost CMD Run host command CMD on new X server (you may need & ). --runasroot CMD Run command CMD as root in container on startup. --sharessh Share SSH agent authentication socket from host. --showenv Echo new \$DISPLAY, \$XAUTHORITY and \$WAYLAND_DISPLAY. For custom access to new X server. Get environment with: read xenv < <(x11docker --showenv [...]) --showid Echo container ID on stdout. --showpid1 Echo host PID of container PID 1 on stdout. --stdin Forward stdin of x11docker to image command. --workdir DIR Set working directory DIR. X server options: --auto Auto choose X server (default). (Regards options --desktop, --gpu, --wayland and --wm). -a, --xpra Nested X server supporting seamless and --desktop mode. Needs 'xpra' on host. For faster startup see --nxagent. -y, --xephyr Nested X server for --desktop mode. Without --desktop, a host window manager will be provided (option --wm). (Needs 'Xephyr' or 'Xnest'). -n, --nxagent Nested X server supporting seamless and --desktop mode. Faster than --xpra and more flexible than --xephyr, but some compositing applications have issues. (Needs 'nxagent', best since nxagent version 3.5.99). -A, --xpra-xwayland Like --xpra, but supports option --gpu. (Needs 'xpra', 'Xwayland', 'weston' and 'xdotool'). -Y, --weston-xwayland Desktop mode like --xephyr, but supports option --gpu. Runs from console, within X and within Wayland. (Needs 'weston' and 'Xwayland'.) -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 by the way with option --clipboard. -x, --xorg Core Xorg server. Runs ootb from console. Switch tty with ..... To run from within X, edit '/etc/X11/Xwrapper.conf' and replace line: allowed_users=console with lines allowed_users=anybody needs_root_rights=yes Debian 9 and Ubuntu 16.04: Install xserver-xorg-legacy. Special X server options: -t, --tty TTY only. Do not provide any X or Wayland server. --kwin-xwayland Like --weston-xwayland, but using kwin_wayland (Needs 'kwin_wayland' and 'Xwayland'). -X, --xwayland Blanc Xwayland, needs a running Wayland compositor. (Needs 'Xwayland' to be installed.) --xdummy Invisible X server. (Needs Xorg's dummy video driver) --xvfb Invisible X server. (Needs '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). --xwin X server for Windows to run with Cygwin/X. --vcxsrv X server for Windows to run with MSYS2, Cygwin or WSL. Wayland without X: -W, --wayland Automatically set up a Wayland environment. Chooses one of following options and regards --desktop. Sets some Wayland environment variables and runs DBus system daemon (--dbus-system) in container. -T, --weston Weston without X for pure Wayland applications. Runs in X or from console. (Needs package weston.) -K, --kwin KWin without X for pure Wayland applications. Runs in X or from console. (Needs kwin_wayland.) -H, --hostwayland Share host Wayland without X for pure Wayland apps. (Needs already running Wayland compositor like Gnome 3.) (Can be combined with --hostdisplay.) X and Wayland appearance options: --border[=COLOR] Draw a colored border in windows from --xpra[-xwayland]. Optional COLOR can be e.g. 'orange' or '#F00'. Thickness can be specified, too, e.g. 'red,3'. Default: 'blue,1' -f, --fullscreen Run Xephyr, nxagent or Weston in fullscreen mode. --size XxY Screen size of new X server (e.g. 800x600). --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). --rotate N Rotate display (--xorg, --weston and --weston-xwayland) Allowed values: 0, 90, 180, 270, flipped, flipped-90, flipped-180, flipped-270. (flipped = mirrored) --dpi N dpi value (dots per inch) to submit to clients. Influences font size of some applications. --output-count N Multiple outputs for Weston, KWin or Xephyr. --xfishtank Show fish tank on new X server (needs 'xfishtank'). X and Wayland configuration: --xhost STR Set \"xhost STR\" on new X server (see 'man xhost'). (Use with care. '--xhost +' allows access for everyone). -o, --no-xhost Disable any access to host X server granted by xhost. --no-auth Allow access to X for everyone. Security risk! --display N Use display number N for new X server. --vt N Use vt / tty N (affects --xorg, --xdummy, --xpra). --keymap LAYOUT Set keyboard layout for new X server, e.g. de, us, ru. For possible LAYOUT look at /usr/share/X11/xkb/symbols. --westonini FILE Custom weston.ini for --weston and --weston-xwayland. User settings: --sudouser Allow su and sudo for container user. Use with care, severe reduction of default x11docker security! Password: x11docker --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. --hostuser USER Run X (and container user) as user USER. Default is result of \$(logname). (x11docker must run as root). --group-add GROUP Additional group GROUP for container user. Init system and DBus daemon: --tini Default: init system tini (built-in of docker). --no-init No init system in container. Image command is PID 1. --runit Init system runit. Degrades container isolation. Needs 'runit' installed in image. 'dbus' is recommended. --openrc Init system OpenRC. Degrades container isolation a bit, but needs less capabilities than --runit and --systemd. Needs 'openrc' installed in image. 'dbus' recommended. --sysvinit Init system SysVinit. Degrades container isolation a bit, but needs less capabilities than others. Needs 'sysvinit' installed in image. 'dbus' recommended. --systemd Init system systemd. Degrades container isolation. For faster startup mask services that fail in container. Needs 'systemd' installed in image. Old systemd versions in image need --sys-admin, too. --sharecgroup Share /sys/fs/cgroup. Allows elogind in container if used with --dbus-system or init options. -b, --dbus Run DBus user session daemon for image command. --dbus-system Run DBus system daemon in container (includes --dbus). Default for --wayland and for init options except --tini. --hostdbus Connect to DBus session from host. Container capabilities: Custom capabilities can be added with --cap-add=CAP after -- --cap-default Allow default docker container capabilities and disable container security hardening of x11docker. --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. --hostnet Set docker run option --net=host, disables network namespacing. Severe reduction of container isolation! Shares host network stack. --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 without argument FACTOR is 0.5 --sys-admin Add capability SYS_ADMIN. Please avoid that. Miscellaneous: --launcher Create application launcher on desktop and exit. You can move the created .desktop file to ~/.local/share/applications to get a menu entry. --cachebasedir DIR Custom base folder for cache files. --license Show license of x11docker (MIT) and exit. --ps Preserve container and cache files on exit. --cleanup Clean up orphaned containers and cache files. Verbosity options: -v, --verbose [=c] Be verbose. Output of logfile on stderr. Optional argument '=c' generates colored output. -D, --debug Debug mode: Show some debug output, -q, --quiet Suppress x11docker terminal messages. Installation options (need root permissions): --install Install x11docker and x11docker-gui from current folder. --update Download and install latest release from github. --update-master Download and install latest master version from github. --remove Remove x11docker from your system. 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 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" ] && [ -e "$Cachefolder" ] && [ "$Xserver" != "--tty" ] && { 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 } || return 1 return 0 } traperror() { # trap ERR: --debug: Output for 'set -o errtrace' debugnote "Command at Line ${2:-} returned with error code ${1:-}: ${4:-} ${3:-} - ${5:-}" } 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: $Logbackup 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 " [ -e "$Cachefolder/error" ] && waitfortheend # Avoid double alert boxes [ -d "$Cachefolder" ] && mkfile "$Cachefolder/error" # also regarded by finish() for exit code # output to X dialogbox if not running in terminal [ "$Runsinterminal" = "no" ] && [ "$Silent" = "no" ] && export ${Terminalxenv:-DISPLAY} && alertbox "x11docker ERROR" "$Message" & finish 1 } warning() { # show warning messages [ "$Verbose" = "no" ] && echo "${Colyellow}x11docker WARNING:${Colnorm} $* " >&3 logentry "x11docker WARNING: $* " } note() { # show notice messages [ "$Verbose" = "no" ] && echo "${Colgreen}x11docker note:${Colnorm} $* " >&3 logentry "x11docker note: $* " } verbose() { # show verbose messages # only logfile notes here, terminal output is done with tail in part:verbose # if $1 = -d then show debug note, too [ "$1" = "-d" ] && shift && debugnote "$*" logentry "x11docker[$(date +%s.%2N | cut -c8-)]: $* " } debugnote() { # show debug output $* [ "$Debugmode" = "yes" ] && { logentry "DEBUGNOTE[$(date +%s.%2N | cut -c8-)]: $*" [ "$Verbose" = "no" ] && echo "${Colblue}DEBUGNOTE[$(date +%s.%2N | cut -c8-)]:${Colnorm} $*" >&3 } ||: } logentry() { # write into logfile [ "$Logfile" ] && { [ "$Logmessages" ] && echo "$Logmessages" >>$Messagelogfile && Logmessages="" echo "$*" >>$Messagelogfile } || Logmessages="$Logmessages $*" } setup_verbosity() { # options --verbose, --stdout, --stderr # create summary logfile tail --pid=$$ --retry -n +1 -F $Messagelogfile $Containerlogfile \ $Cmdstdoutlogfile $Cmdstderrlogfile ${Journallogfile:-} \ $Xpraserverlogfile $Xpraclientlogfile \ $Xinitlogfile $Compositorlogfile 2>/dev/null >>$Logfile & # option --verbose [ "$Verbose" = "yes" ] && { case $Verbosecolors in no) tail --pid=$$ --retry -n +1 -F $Logfile 2>/dev/null >&3 & ;; 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\)/${Colgreen}\0${Colnorm}/; s/\(Started\|Reached target\)/${Colgreenbg}\0${Colnorm}/; s/^\(+\|++\|+++\)/${Colgreenbg}\0${Colnorm}/ ; s/^x11docker/${Colgreen}\0${Colnorm}/ " >&3 & [ "$Initsystem" = "systemd" ] && note "x11docker deletes some systemd error messages that are harmless but numerous to get a better readable output. It deletes all lines with: 'Failed to add fd to store', 'Failed to set invocation ID' and 'Failed to reset devices.list'." ;; esac } # options --stdout, --stderr [ "$Showstdout" = "yes" ] && { { waitforfilecontent $Containerpid1pidfile infinity tail --pid=$$ --retry -n +1 -F $Cmdstdoutlogfile 2>/dev/null } & storepid $! tailstdout } [ "$Showstderr" = "yes" ] && { { tail --pid=$$ --retry -n +1 -F $Cmdstderrlogfile >&4 2>/dev/null } & storepid $! tailstderr } return 0 } #### exit finish_sigint() { # trap SIGINT to activate debug mode on finish() Debugmode="yes" debugnote "Received SIGINT" finish } finish() { # trap EXIT routine to clean up background processes and cache local Pid= Name= Zeit= Catlogfilepid= Exitcode= Pid1pid= trap - EXIT verbose -d "Terminating x11docker." debugnote "List of stored background processes: $(cat $Bgpidfile 2>/dev/null)" [ "$$" = "$BASHPID" ] || saygoodbye finish-subshell [ "$Sudo" ] && Sudo="sudo -n" # no password prompt here, rather fail # check for background processes stored in $Bgpidfile [ -s "$Bgpidfile" ] && { while read -r Line ; do Pid=$(echo $Line | awk '{print $1}') Name=$(echo $Line | awk '{print $2}') debugnote "Checking: $Pid ($Name): $(pspid $Pid ||:)" checkpid $Pid && { case $Name in xinit) killpid $Pid $Name xinit ;; Xserver) killpid $Pid $Name 'Xorg|Xephyr|Xvfb|Xwayland|nxagent|vcxsrv.exe|Xwin' ;; containershell) killpid $Pid $Name x11docker ;; windowmanager) killpid $Pid $Name ;; xpraserver|xpraclient) killpid $Pid $Name xpra ;; compositor) killpid $Pid $Name 'weston|kwin_wayland' ;; waitfortheend|xpraloop|watchpidlist) killpid $Pid $Name x11docker ;; watchmessagefifo) [ "$$" = "$BASHPID" ] && killpid $Pid $Name x11docker || Pid= ;; tailstdout|tailstderr) Pid= ;; # tail terminates itself after exit of $$ catstdin) killpid $Pid $Name ;; hostexe|shareclipboard) killpid $Pid $Name bash ;; xfishtank) killpid $Pid $Name xfishtank ;; containerpid1) Pid1pid=$Pid Pid= # Send TERM to PID1 of container checkpid $Pid1pid 2>&1 && { verbose -d "Sending TERM to PID 1 of container: $(pspid $Pid1pid)" killpid $Pid1pid containerPID1 2>/dev/null || note "Could not terminate PID 1 of container: $(pspid $Pid1pid)" # Give container a bit time for graceful shutdown case $Initsystem in systemd) ;; *) 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 sleep $( awk "BEGIN { print $Count * 0.1 }") debugnote "Waiting for container PID 1: $Pid1pid to terminate" done ;; esac } # Try 'docker stop' if TERM failed checkpid $Pid1pid && { verbose -d "Container still running. Executing 'docker stop'." $Sudo $Dockerexe stop $Containername >>$Containerlogfile 2>&1 || verbose -d "docker stop failed" } ;; *) # should never happen (tm) note "Found remaining background process $Line ($Pid) $(pspid $Pid)" killpid $Pid $Name ;; esac checkpid $Pid && { sleep 1 checkpid $Pid && verbose -d "Issue terminating $Line: $(pspid $Pid)" && kill -s KILL $Pid } } done < <(tac $Bgpidfile) } # Possibly stored Windows pids to terminate. Currently only VcXsrv in WSL. [ "$Winpidlist" ] && for Line in $Winpidlist ; do verbose -d "Terminating Windows PID $Line: $(taskkill.exe /F /PID $Line 2>&1 | rmcr)" ; done saygoodbye "finish" # Check if container is still running. # if kill of PID 1 failed in loop above, most times terminating X server will already have stopped GUI applications. checkpid $Pid1pid && { $Sudo $Dockerexe stop $Containername >>$Containerlogfile 2>&1 || { note "Found remaining container process. Most probably the X session was interrupted. Can not stop container because x11docker does not run as root. Will wait up to 10 seconds for docker to finish." Zeit=$(date +%s) while checkpid $Pid1pid ; do note "Waiting for container to terminate ..." sleep 1 [ 10 -lt $(($(date +%s) - $Zeit)) ] && break done if checkpid $Pid1pid ; then note "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" else note "Container terminated successfully" fi } } # different stop behaviour on Windows because $Pid1pid is not in ps. [ "$Winsubsystem" ] && [ "$Containername" ] && { $Dockerexe ps -a | grep -q $Containername && { verbose -d "Container still running. Executing 'docker stop'." $Dockerexe stop $Containername >>$Containerlogfile 2>&1 || { warning "Failed to stop container $Containername. Will not clean cache to avoid file permission issues." Preservecachefiles="yes" } } } # check for possible error Exitcode=${1:-0} [ -e "$Cachefolder/error" ] && Exitcode=1 verbose -d "Exitcode $Exitcode" # backup of logfile in $Cachebasedir [ -e "$Logfile" ] && { [ "$Verbose" = "yes" ] && sleep 1 rmcr $Logfile $Mksu "cp '$Logfile' '$Logbackup'" } # close additional file descriptors for i in 3 4 6 7 8 9; do { >&$i ;} 2>/dev/null && exec >&$i- done # remove cache files [ "$Preservecachefiles" = "no" ] && grep -q cache <<<$Cachefolder && grep -q x11docker <<<$Cachefolder && [ "x11docker" != "$(basename "$Cachefolder")" ] && $Mksu "rm -f -R '$Cachefolder'" exit $Exitcode } saygoodbye() { # create file signaling watching processes to terminate verbose -d "time to say goodbye ($*)" [ -e "$Sharefolder" ] && { echo timetosaygoodbye >> $Timetosaygoodbye echo timetosaygoodbye >> $Timetosaygoodbyefifo } } #### watching processes and messages rocknroll() { # check whether x11docker session is still running [ -s "$Timetosaygoodbye" ] && return 1 [ -e "$Timetosaygoodbye" ] || 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 $Winsubsystem in "") while rocknroll; do bash -c "read -n1 -t1 <&8" && saygoodbye timetosaygoodbyefifo done ;; *) # Reading from fifo fails on Windows, workaround while rocknroll; do sleep 2 done ;; esac } 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 >&9 (setonwatchpidlist()) local Pid= Containername= Line= Watchpidlist= while rocknroll ; do read -t1 Pid <&9 ||: [ "$Pid" ] && { [ "${Pid:0:9}" = "CONTAINER" ] && { Containername=${Pid#CONTAINER} verbose -d "Watching Container: $Containername" } || { Watchpidlist="$Watchpidlist $Pid" verbose -d "Watching pids: $(for Line in $Watchpidlist; do pspid $Line ; done)" } } for Pid in $Watchpidlist; do [ -e /proc/$Pid ] || { debugnote "watchpidlist: PID $Pid has terminated" saygoodbye "watchpidlist $Pid" } done [ "$Containername" ] && { # Container PID not watchable in MSYS2/Cygwin/WSL. $Dockerexe inspect $Containername >/dev/null 2>&1 || { debugnote "watchpidlist: Container $Containername has terminated" saygoodbye "watchpidlist $Containername" } } [ "$Winsubsystem" ] && sleep 2 done saygoodbye "watchpidlist" } setonwatchpidlist() { # add PID $1 to watchpidlist() #echo ${1:-} >&9 debugnote "Set pid ${1:-} on watchlist: ${2:-}" echo "${1:-}" >>$Watchpidfifo } watchmessagefifo() { # watch for messages out of container or dockerrc # message in fifo must end with :$Messagetype local Line= Message= Messagetype= while rocknroll ; do IFS= read -r Line <&6 ||: [ "$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)" ] && verbose -d "$(tail -c +4 <<< "$Message")" || verbose "$Message" ;; :STDOUT) echo "$Message" ;; esac Message= Messagetype= } done } storepid () { # store pid $1 and name $2 of background process in file $Bgpidfile. # store Pid and process name of background processes in file # for use on exit / with trap to clean up with background processes # this subroutine has a twin in xinitrc echo ${1:-} ${2:-} >> $Bgpidfile verbose -d "Stored background pid ${1:-} of ${2:-}" disown ${1:-} 2>/dev/null ||: } mywatch() { # repeat $1 untils its output changes local Watchoutput= Watchoutput="$(sh -c "${1:-}" 2>&1)" verbose -d "Watching output of: sh -c \"'${1:-}'\" 2>&1 Current output: $Watchoutput" while rocknroll; do [ "$Watchoutput" = "$(sh -c "${1:-}" 2>&1)" ] || break sleep 1 done verbose -d "Stopped watching output of: sh -c \"'${1:-}'\" 2>&1 Current output: $(sh -c "${1:-}" 2>&1)" } checkpid() { # check if PID $1 is active #ps -p ${1:-} >/dev/null 2>&1 [ -e "/proc/${1:-NONSENSE}" ] } 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 ps -p ${1:-} 2>/dev/null || echo "ps -p not supported" | tail -n+2 } killpid() { # kill PID $1 with codename $2 [and process name $3] verbose -d "Terminating ${1:-} (${2:-}) ${3:-}: $(pspid ${1:-})" # ps -p ${1:-} >/dev/null && kill ${1:-} 2>/dev/null checkpid "${1:-}" && kill ${1:-} 2>/dev/null } #### more or less general routines verlte() { # version number check $1 less than or equal $2 [ "${1:-}" = "$(echo -e "${1:-}\n${2:-}" | sort -V | head -n1)" ] && return 0 || return 1 } verlt() { # version number check $1 less than $2 [ "${1:-}" = "${2:-}" ] && return 1 || { verlte "${1:-}" "${2:-}" && return 0 || return 1 ; } } check_parent_sshd() { # check whether pid $1 runs in SSH session local Wanted_pid="${1:-}" Process_line ps -p 1 >/dev/null 2>&1 || { note "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 done return 1 } 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)}')" ] } 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; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/' } 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 } download() { # download file at URL $1 and store it in file $2 # Uses wget or curl. If both are missing, returns 1. Follows redirects. # With no arguments it checks for curl/wget only. 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 } #### file routines waitforfilecreation() { # similar to inotify-wait: wait up to 15s for file $1 to be created # $1 file to wait for # $2 time to wait (optional). default: 15s. possible: infinity local Zeit= Warten= Dauer= Count= Zeit=$(date +%s) verbose -d "Waiting for file creation of ${1:-}" case ${2:-} in "") Warten=15 ;; infinity|inf) Warten=32000 ;; # nearly infinity in fast-moving today ... *) Warten=${2:-} ;; esac while [ ! "$(find "${1:-}" 2>/dev/null)" ] ; do Count=$(( Count + 1 )) Dauer=$(( $(date +%s) - $Zeit )) sleep $(awk "BEGIN { print $Count * 0.1 }") [ $Warten -lt $Dauer ] && { warning "Failed to wait for file creation of ${1:-}" return 1 } verbose "Waiting since ${Dauer}s for ${1:-} to be created, will wait up to $Warten seconds." rocknroll || { verbose -d "Stopped waiting for ${1:-} due to terminating signal." return 1 } done verbose "Found new created file $(ls ${1:-})" return 0 } waitforfilecontent() { # wait for file $1 to be not empty # $1 file to look at local Zeit= Warten= Dauer= Count= Zeit=$(date +%s) case "${2:-}" in infinity) Warten=32000 ;; "") Warten=15 ;; *) Warten="${2:-}" ;; esac verbose -d "Waiting for file content in ${1:-}" while [ ! -s "${1:-}" ] ; do Count=$(( Count + 1 )) Dauer=$(( $(date +%s) - $Zeit )) sleep $(awk "BEGIN { print $Count * 0.1 }") [ $Warten -lt $Dauer ] && return 1 verbose "Waiting since ${Dauer}s for ${1:-} to have content, will wait up to $Warten seconds." rocknroll || { verbose -d "Stopped waiting for file content of ${1:-} due to terminating signal." return 1 } done verbose "Found file content in ${1:-}" return 0 } 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 local Zeit= Warten= Dauer= Count= Zeit=$(date +%s) Warten=60 while ! grep -q "${3:-}" <"${2:-}" ; do # grep -i -q -E 'error|Failed to process Wayland|failed to create display|] fatal:' <"${2:-}" && { grep -i -q -E 'Failed to process Wayland|failed to create display|] fatal:' <"${2:-}" && { warning "Found error message for ${1:-} in logfile ${2:-} $(grep -i -E 'error|failed|fatal' <"${2:-}")" return 1 } Count=$(( Count + 1 )) Dauer=$(( $(date +%s) - $Zeit )) verbose "Waiting since ${Dauer}s for ${1:-} to be ready." sleep $(awk "BEGIN { print $Count * 0.1 }") [ $Warten -lt $Dauer ] && return 1 rocknroll || { verbose -d "Stopped waiting for logentry in ${2:-} of ${1:-} due to terminating signal." return 1 } done verbose "${1:-} is ready." return 0 } mkfile() { # create file $1 owned by $Hostuser :> "${1:-}" || return 1 chown $Hostuser "${1:-}" || return 1 chgrp $Hostusergid "${1:-}" || return 1 [ -n "${2:-}" ] && { chmod ${2:-} "${1:-}" || return 1 ; } 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 } 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 # --volume echo --volume compatible syntax - result: 'unixpath':'containerpath':rw (or ":ro") # $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 --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" # not on Windows [ -z "$Winsubsystem" ] && { case $Mode in --volume) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; --unix|--subsystem) echo "$Path" ;; --windows) warning "Nonsense path conversion $Mode: $Path" ; return 1 ;; 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,}" [ "$Winsubsystem" = "WSL" ] && [ -z "$Drive" ] && case $Mode in --windows|--unix|--volume) warning "Request of Windows path to path within WSL: $Path Write access from Windows host to WSL files can damage WSL. Read-only access is ok." ;; 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) echo "'$Winsubpath$Path':'${3:-$Path}'$Readwritemode" ;; 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" ;; 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/$(mcookie)" # 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 } #### special jobs of x11docker (not running X or docker) 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= 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) Oldversion="$($0 --version)" note "Current installed version: x11docker $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 || error "Can not unpack archive. Please install 'unzip'." case ${1:-} in --update-master) note "Downloading latest x11docker master version from github" download "https://codeload.github.com/mviereck/x11docker/zip/master" "x11docker-update.zip" || error "Could not download x11docker-master from github" ;; --update) download "https://raw.githubusercontent.com/mviereck/x11docker/master/CHANGELOG.md" "CHANGELOG.md" || error "Could not 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://github.com/mviereck/x11docker/archive/$Releaseversion.zip" "x11docker-update.zip" || error "Could not download latest x11docker release from github" ;; esac note "Unpacking archive" unzip x11docker-update.zip || error "Could not unzip 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' 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." } } ;; --update-master) echo "$Oldversion" | grep -q beta && { Key1="\[Unreleased\]" Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" } || { Key1="\[Unreleased\]" Key2="\[${Oldversion}\]" } ;; 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." } 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 --format "{{.Names}}")" 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 $Line. Will summon it to terminate itself." echo timetosaygoodbye >> "$Cachebasefolder/$Line/share/timetosaygoodbye" sleep 2 } done [ -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 $(echo $Imagecommand | tr -cd '[:alpha:][:digit:][:blank:]-_.')" [ "$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="${Parsedoptions//--launcher/}" Parsedoptions="${Parsedoptions//--starter/}" mkfile "$Cachefolder/$Name.desktop" { echo "#!/usr/bin/xdg-open [Desktop Entry] # x11docker desktop file Type=Application Name=$Name Exec=x11docker $Parsedoptions Icon=x11docker Comment= Categories=System Keywords=docker x11docker $(echo $Name | tr -c '[:alpha:][:digit:][:blank:]' ' ' ) " case $(command -v x11docker) in "")echo "TryExec=$0 $Parsedoptions" ;; *) echo "TryExec=x11docker $Parsedoptions" ;; esac } >> "$Cachefolder/$Name.desktop" $Mksu "xdg-desktop-icon install --novendor '$Cachefolder/$Name.desktop'" } #### features 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 $Nvidiadriver nvidia driver file to install in container in containerrootrc # g $Nividaversion nvidia driver version on host local Gpudevice= 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)." # check device files while read -r Gpudevice ; do store_runoption device "$Gpudevice" done < <(find /dev/dri /dev/nvidia* /dev/vga_arbiter -maxdepth 0 2>/dev/null ||:) # check for closed source nvidia driver on host, provide automated installation, warn about disadvantages #Hostnvidia=yes [ "$Hostnvidia" = "yes" ] && { Nvidiaversion=$(head -n1 /dev/null | head -n1 ) Nvidiadriver="$(realpath "$Nvidiadriver")" [ -e "$Nvidiadriver" ] && { verbose -d "Found proprietary closed source NVIDIA driver installer $Nvidiadriver" [ "$Capdropall" = "yes" ] && warning "To install proprietary closed source NVIDIA driver, x11docker must give some capabilities to container that would be dropped otherwise for security reasons. Container security is reduced now. You would not have this issue with free nouveau driver on host." } || { Nvidiadriver="" warning "You are using proprietary 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 "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 may be: 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'. Automated installation fails on image systems not using glibc like Alpine and fails on openSUSE images with a self-extraction error. These issues cannot be fixed due to closed source policy of NVIDIA corporation." } } return 0 } setup_webcam() { # option --webcam: share webcam devices # Webcam devices appear as /dev/video* files. # Unprivileged users need to be in group video (x11docker default). # (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 device "$Webcamdevice" done < <(find /dev/video* -maxdepth 0 2>/dev/null) # at least cheese and gnome-ring need some device information from udev. store_runoption volume "/run/udev/data:ro" } 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." Sharecups="no" return 1 } Cupsserver="$(lpstat -H)" 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" ] && store_runoption volume "$Cupsserver" 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= warning "Option --pulseaudio allows container applications to catch your audio output and microphone input." [ "$Pulseaudiomode" = "auto" ] && { case "$Winsubsystem" in "") Pulseaudiomode="socket" ;; *) Pulseaudiomode="tcp" ;; esac [ "$Containeruser" = "$Hostuser" ] || Pulseaudiomode="tcp" } case $Pulseaudiomode in socket) # create pulseaudio socket $Mksu "pactl load-module module-native-protocol-unix socket=$Sharefolder/pulseaudio.socket 2>&1" >>$Messagelogfile case $X11dockermode in run) store_runoption env "PULSE_SERVER=unix:$Cshare/pulseaudio.socket" store_runoption env "PULSE_COOKIE=$Cshare/pulseaudio.cookie" ;; exe) store_runoption env "PULSE_SERVER=unix:$Sharefolder/pulseaudio.socket" store_runoption env "PULSE_COOKIE=$Sharefolder/pulseaudio.cookie" ;; esac echo "# Connect to host pulseaudio server using mounted UNIX socket default-server = unix:$Cshare/pulseaudio.socket # 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) case $Winsubsystem in "") 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 ;; *) note "Option --pulseaudio: Pulseaudio daemon will continue to run after x11docker terminates. You can kill pulseaudio manually with: taskkill.exe /PID pulseaudio.exe /F" [ -e "$(convertpath --subsystem "C:/cygwin64/bin/pulseaudio.exe")" ] || { warning "--pulseaudio: Did not find C:/cygwin64/bin/pulseaudio.exe. Please install Cygwin with pulseaudio. Fallback: Disabling option --pulseaudio" Pulseaudiomode="" } Line="$(netstat -n)" while :; do Pulseaudioport="$(shuf -i 34000-54000 -n1)" grep -q $Pulseaudioport <<<"$Line" || break done env XDG_RUNTIME_DIR="" HOME="" cmd.exe /C "C:/cygwin64/bin/pulseaudio.exe -D 2>&1" 2>&1 >>$Messagelogfile ;; esac [ "$Pulseaudiomode" ] && store_runoption env "PULSE_SERVER=tcp:$Hostip:$Pulseaudioport" ;; "") ;; *) warning "Unknown pulseaudio mode: $Pulseaudiomode Allowed are --pulseaudio=socket or --pulseaudio=tcp Fallback: disabling option --pulseaudio" Pulseaudiomode="" ;; esac 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 device "/dev/snd" || { warning "Option --alsa: /dev/snd not found. Sound support not possible." Sharealsa="no" return 1 } return 0 } setup_clipboard() { # option --clipboard: create shareclipboard script # xpra and nxagent have their own clipboard management. # only xpra supports image clips. # Other X servers: A script is created to synchronize Clipboard between X servers. # No clipboard support for Wayland yet. # It 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." Shareclipboard="no" return 1 ;; --nxagent|--xpra|--xpra-xwayland|--vcxsrv|--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 xclip >/dev/null && { Clipsend="xclip -selection clipboard -in" Clipreceive="xclip -selection clipboard -out" } || command -v xsel >/dev/null && { Clipsend="xsel --clipboard --input" Clipreceive="xsel --clipboard --output" } || { warning "Option --clipboard: Need either xclip or xsel for clipboard sharing with X server $Xserver. Fallback: Disabling option --clipboard." Shareclipboard="no" Clipsend="" Clipreceive="" } echo "#! /usr/bin/env bash # share clipboard between X servers $Hostdisplay and $Newdisplay while [ ! -s "$Timetosaygoodbye" ] ; do # read content of clipboard of first X server D1CLIP=\"\$(env DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority $Clipreceive)\" # check if clipboard of first X server has changed; if yes, send new content to second X server [ \"\$CLIP\" != \"\$D1CLIP\" ] && { CLIP=\"\$D1CLIP\" env DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority $Clipreceive | env DISPLAY=$Newdisplay XAUTHORITY=$Xclientcookie $Clipsend } [ -z \"\$CLIP\" ] && CLIP=' ' # avoid empty string error # read content of clipboard of second X server D2CLIP=\"\$(env DISPLAY=$Newdisplay XAUTHORITY=$Xclientcookie $Clipreceive)\" # check if clipboard of second X server has changed; if yes, send new content to first X server [ \"\$CLIP\" != \"\$D2CLIP\" ] && { CLIP=\"\$D2CLIP\" env DISPLAY=$Newdisplay XAUTHORITY=$Xclientcookie $Clipreceive | env DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority $Clipsend } [ -z \"\$CLIP\" ] && CLIP=' ' # avoid empty string error sleep 0.5 # sleep a bit to avoid high cpu usage done " >> $Shareclipboardscript ;; esac return 0 } check_windowmanager() { # option --wm: check window manager # Searches for a window manager on on to provide it for single applications in nested X servers like Xephyr. # Checks x-window-manager, wmctrl and list $Wm_all. # Default x-window-manager can be changed with: update-alternatives --config x-window-manager case $Windowmanager in ""|"none") Windowmanager="" ;; *) [ "$Windowmanager" = "auto" ] && Windowmanager="" # check if one is chosen with option --wm [ "$Windowmanager" ] && { command -v $Windowmanager > /dev/null || { warning "Window manager '$Windowmanager' not found. Fallback: Will try to autodetect a window manager on host." Windowmanager="" } } # try to find window manager from alternatives system [ -z "$Windowmanager" ] && [ -e "/etc/alternatives/x-window-manager" ] && { note "Looking for windowmanager linked with x-window-manager. You can set the default one detected in auto mode with update-alternatives --config x-window-manager" Windowmanager=$(command -v $(ls -l /etc/alternatives/x-window-manager | cut -d ">" -f2)) } # try to find one in list [ "$Windowmanager" ] || { verbose -d "Searching for window manager in list." for Windowmanager in $Wm_all "" ; do command -v $Windowmanager >/dev/null && break done } # try with wmctrl [ -z "$Windowmanager" ] && command -v wmctrl >/dev/null && { verbose -d "Searching for window manager with wmctrl" Windowmanager=$(wmctrl -m | grep 'PID' | awk '{print $2}') # wmtrl, if installed, can find already running wm. At first get pid [ -e "/proc/${Windowmanager:--1}" ] && { # check if pid is valid Windowmanager=$(ls -l "/proc/$Windowmanager/exe" | awk '{print $11}') # if yes, then get /path/executable } || { # otherwise, try insecure way over name Windowmanager=$(wmctrl -m | grep 'Name' | awk '{print $2}' | tr '[:upper:]' '[:lower:]') } } # check some special cases [ "$Windowmanager" ] && case $(basename $Windowmanager | cut -d' ' -f1) in cinnamon|cinnamon-session) Windowmanager="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" ] || { $Mksu "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) Windowmanager="enlightenment_start" ;; matchbox) Windowmanager="matchbox-window-manager" ;; mate|mate-session) Windowmanager="mate-session -f" ;; mate-wm) Windowmanager="marco --sm-disable" ;; openbox) Windowmanager="openbox --sm-disable" ;; gnome-shell|gnome-session) warning "$Windowmanager is known to cause segfaults. Please choose another one." Windowmanager="gnome-shell --sm-disable" ;; budgie-wm|mutter) warning "$Windowmanager is known to cause segfaults in some versions and to cause unusable window sizes in others. Please choose or install another one like xfwm4." ;; esac [ "$Windowmanager" ] && case $(basename $Windowmanager | cut -d' ' -f1) in enlightenment_start|lxsession|mate-session|cinnamon) note "Using extensive window manager '$Windowmanager'. It is recommended to install and use a lightweight window manager like $Wm_recommended_nodesktop_light" ;; *) note "Using host window manager $Windowmanager" ;; esac [ "$Windowmanager" ] && ! command -v $Windowmanager >/dev/null && warning "No executeable window manager '$Windowmanager' found." [ "$Windowmanager" ] || note "Could not detect a host window manager. Please specify one with option --wm=WINDOWMANAGER or install one of $Wm_good" ;; esac [ "$Windowmanager" ] && verbose -d "Chosen window manager from host: $Windowmanager" return 0 } #### X server setup 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" ; } [ "$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" [ "$Hosttty" = "yes" ] && Xserver="--weston-xwayland" [ "$Scaling" ] && [ "$Hosttty" = "yes" ] && Xserver="--xorg" [ "$Screensize" ] && [ "$Hosttty" = "yes" ] && Xserver="--xorg" [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && Xserver="--weston-xwayland" [ "$Winsubsystem" ] && Xserver="--vcxsrv" [ "$Winsubsystem" = "CYGWIN" ] && Xserver="--xwin" [ "$Sharewayland" = "yes" ] && { [ -n "$Hostwaylandsocket" ] && [ "$Desktopmode" = "no" ] && Xserver="--hostwayland" || Xserver="--weston" ; } } [ "$Sharegpu" = "yes" ] && case $Xserver in --xpra) note "Option --xpra does not support GPU access. Fallback: Will try to use option --xpra-xwayland." Xserver="--xpra-xwayland" ;; --xephyr) note "Option --xephyr does not support GPU access. Fallback: Will try to use option --weston-xwayland." Xserver="--weston-xwayland" ;; --nxagent) case "$Desktopmode" in yes) note "Option --nxagent does not support GPU access. Fallback: Will try to use option --weston-xwayland." Xserver="--weston-xwayland" ;; no) note "Option --nxagent does not support GPU access. Fallback: Will try to use option --xpra-xwayland." Xserver="--xpra-xwayland" ;; esac ;; --xdummy|--xvfb) note "Using special setup with Weston, Xwayland and xdotool instead of Xdummy or Xvfb to allow GPU access." Xserver="--xdummy-xwayland" ;; esac [ "$Hostnvidia" = "yes" ] && [ "$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 [ "$Hosttty" = "no" ] && [ "$Hostssh" = "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" } [ "$Hostssh" = "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.)" } ## check if dependencies for chosen X server are installed, fall back to best alternatives if not [ "$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 || { check_xdepends --nxagent && Xserver="--nxagent" || Xserver="--xephyr" ; } ; } [ "$Xserver" = "--xorg" ] && { check_xdepends --xorg || Xserver="--weston-xwayland" ; } [ "$Xserver" = "--xpra-xwayland" ] && { check_xdepends --xpra || 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" ; } [ "$Xserver" = "--xwin" ] && { check_xdepends --xwin || Xserver="--vcxsrv" ; } [ "$Xserver" = "--vcxsrv" ] && { check_xdepends --vcxsrv || Xserver="--hostdisplay" ; } case $Xserver in --weston|--kwin) Sharewayland="yes" ;; esac [ "$Sharewayland" = "yes" ] && { check_xdepends $Xserver || error "Failed to set up a Wayland environment. Please install 'weston' or 'kwin_wayland'." ; } # 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 --vcxsrv && Xserver="--vcxsrv" 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 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" ;; CYGWIN) error "Did not find a possibility to provide a display. Please install Xwin (xinit package) in Cygwin, or VcXsrv on Windows host." ;; MSYS2|WSL) error "Did not find a possibility to provide a display. Please install X server VcXsrv on Windows host." ;; esac } [ "$Autochooseserver" = "yes" ] && note "Using X server option $Xserver" || verbose -d "Using X server option $Xserver" return 0 } check_xdepends() { # check dependencies on host for X server option $1 # Return 1 if something is missing local Return= Message= case $Autochooseserver in yes) Message="verbose -d" ;; no) Message="note" ;; esac case ${1:-} in --xephyr|--xpra|--nxagent|--xorg|--xvfb|--xdummy|--xwayland|--weston-xwayland|--kwin-xwayland|--xwin) command -v xinit >/dev/null || { $Message "${1:-}: xinit not found." Return=1 } ;; esac [ "$Hostnvidia" = "yes" ] && case ${1:-} in --xpra-xwayland|--weston-xwayland|--xwayland|--weston|--kwin|--kwin-xwayland|--xdummy-xwayland|--hostwayland) $Message "${1:-}: Closed source NVIDIA driver does not support Wayland." # Return=1 ;; esac case ${1:-} in --xpra|--xpra-xwayland) command -v "xpra" >/dev/null || { $Message "${1:-}: xpra not found." Return=1 } ;; --xephyr) command -v "Xephyr" >/dev/null || command -v "Xnest" >/dev/null || { $Message "${1:-}: Neither Xephyr nor Xnest found." Return=1 } ;; --nxagent) command -v "nxagent" >/dev/null || { $Message "${1:-}: nxagent not found." Return=1 } ;; --xvfb) command -v "Xvfb" >/dev/null || { $Message "${1:-}: Xvfb not found." Return=1 } ;; --xorg|--xdummy) command -v "Xorg" >/dev/null || { $Message "${1:-}: Xorg not found." Return=1 } ;; --xwin) command -v "xwin" >/dev/null || { $Message "${1:-}: Xwin not found. Need 'xinit' package from Cygwin (X11 section)." Return=1 } ;; --vcxsrv) Vcxsrvpath="$(convertpath --subsystem "C:/Program Files/VcXsrv/vcxsrv.exe")" command -v "$Vcxsrvpath" >/dev/null || { $Message "${1:-}: X server VcXsrv not found." Return=1 [ "$Winsubsystem" ] && note "To run x11docker within MSYS2, Cygwin or WSL on MS Windows, please install X server VcXsrv (or Xwin on Cygwin). Searching for vcxsrv.exe in: C:/Program Files/VcXsrv" } Vcxsrvpath="$(echo "$Vcxsrvpath" | rev | cut -d/ -f2- | rev)" [ -z "$Winsubsystem" ] && Return=1 && $Message "${1:-}: X server VcXsrv is available on MS Windows only." ;; esac case $Xserver in --vcxsrv|--xwin) [ "$Hostip" ] || { warning "${1:-}: Failed to get host IP address." Return=1 } ;; esac case ${1:-} in --weston|--xpra-xwayland|--weston-xwayland|--xdummy-xwayland) command -v "weston" >/dev/null || { $Message "${1:-}: weston not found." Return=1 } ;; --kwin|--kwin-xwayland) command -v "kwin_wayland" >/dev/null || { $Message "${1:-}: kwin_wayland not found." 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." Return=1 } ;; esac case ${1:-} in --xpra-xwayland|--xdummy-xwayland) command -v "xdotool" >/dev/null || { $Message "${1:-}: xdotool not found." Return=1 } ;; esac case ${1:-} in --hostdisplay|--xpra|--xpra-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 case $Xserver in --xpra|--xpra-xwayland) [ "$Return" = "1" ] || { # check xpra version [ "$Xpraversion" ] || { Xpraversion="$(xpra --version)" Xprarelease="$(echo $Xpraversion | cut -s -d- -f2)" verbose "Xpra version: $Xpraversion" } ! 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: $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 debugnote "Dependency check for ${1:-}: ${Return:-0}" [ "$Return" = "1" ] && { $Message "${1:-} not possible: missing dependencies." Autochooseserver="yes" } return ${Return:-"0"} } check_screensize() { # check physical and virtual screen size (also option --size) local Line= Xwaylandpid= # Dummy start of Xwayland to detect screen size in pure Wayland environments [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && command -v Xwayland >/dev/null && { verbose -d "Running Xwayland to get screen size" Xwayland $Newdisplay -rootless -extension GLX +extension RANDR >>$Xinitlogfile 2>&1 & Xwaylandpid=$! && disown waitforfilecreation $Newxsocket export DISPLAY=$Newdisplay } # 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 | rmcr)" Maxxaxis=$(echo "$Line" | grep Width | rev | cut -d' ' -f1 | rev) Maxyaxis=$(echo "$Line" | grep Height | rev | cut -d' ' -f1 | rev) } [ -z "$Maxxaxis" ] && note "Could not determine your screen size. Please improve this by installing one of xrandr, xdpyinfo or xwininfo. Or use option --size=XxY." } 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" ] && [ "$Hosttty" = "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) [ "$Hosttty" = "yes" ] && { : # nothing to do on tty; 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" 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 *) [ "$Hosttty" = "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-)" # kill dummy Xwayland process checkpid $Xwaylandpid && { verbose -d "Terminating Xwayland" kill $Xwaylandpid } export DISPLAY=$Hostdisplay verbose "Virtual screen size: $Screensize" verbose "Physical screen size: $(xrandr 2>/dev/null | grep Screen ||:)" 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 | rmcr | tail -n +2 /dev/stdin | while read -r Line ; do # read all but the first line (header) verbose -d "xhost: Removing entry $Line" xhost -$Line | rmcr # disable every entry done xhost - | rmcr # enable access control [ "$(xhost 2>&1 | rmcr | wc -l)" -gt "1" ] && { warning "Remaining xhost permissions found on display ${DISPLAY:-} $(xhost 2>&1 | rmcr)" return 1 } xhost 2>&1 | rmcr | grep "access control disabled" && { warning "Failed to restrict xhost permissions. Access to display ${DISPLAY:-} is allowed for everyone." return 1 } return 0 } check_newxenv() { # find free display, create $Newxenv # find free display number [ "$Newdisplaynumber" ] || for ((Newdisplaynumber=100 ; Newdisplaynumber <=1000 ; Newdisplaynumber++)); do [ -n "$(find /tmp/.X11-unix/X$Newdisplaynumber /tmp/.X$Newdisplaynumber-lock $XDG_RUNTIME_DIR/wayland-$Newdisplaynumber 2>/dev/null)" ] || { case $Xserver in --vcxsrv) grep -q -E 'Authorization|Absolute' <<< "$(xwininfo.exe -display :$Newdisplaynumber -root 2>&1)" || break ;; *) break ;; esac } done # X over IP/TCP [ "$Xoverip" ] || case $Xserver in --vcxsrv|--xwin) Xoverip="yes" ;; *) Xoverip="no" ;; esac case $Winsubsystem in MSYS2|CYGWIN) Xoverip="yes" ;; esac # set $Newdisplay (DISPLAY of container) and $Newxsocket case $Xserver in --hostdisplay) Newdisplay=$Hostdisplay Newdisplaynumber=$(echo $Newdisplay | cut -d: -f2 | cut -d. -f1) [ -e "$Hostxsocket" ] && Newxsocket=$Hostxsocket || Newxsocket= ;; *) 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 ;; # --vcxsrv|--xwin) # Newdisplay="$Hostip:$Newdisplaynumber" # ;; # *) # 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 # 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) case $Xserver in --xpra|--xephyr|--xpra-xwayland|--weston-xwayland|--hostdisplay|--xorg|--xdummy|--xvfb|--xdummy-xwayland|--xwayland|--kwin-xwayland|--nxagent|--vcxsrv|--xwin) Newxenv="DISPLAY=$Newdisplay" [ "$Xauthentication" = "yes" ] && Newxenv="$Newxenv XAUTHORITY=$Xclientcookie" Newxenv="$Newxenv XSOCKET=$Newxsocket" Newxenv="$Newxenv $Xprashm" ;; --weston|--kwin|--hostwayland|--tty) Newxenv="DISPLAY= XAUTHORITY=" Newdisplay="" Newxsocket="" Xclientcookie="" Xservercookie="" ;; esac [ "$Sharewayland" = "yes" ] && { Newxenv="$Newxenv WAYLAND_DISPLAY=$Newwaylandsocket" Newxenv="$Newxenv XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" } [ "$Setwaylandenv" = "yes" ] && for Line in $Waylandtoolkitenv ; do Newxenv="$Newxenv $Line" ; done Newxenv="$Newxenv X11DOCKER_CACHE=$Cachefolder" verbose -d "New X environment: $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|--vcxsrv|--xwin) store_runoption env "DISPLAY=$Newdisplay" store_runoption env "XAUTHORITY=$Cshare/Xclientcookie" ;; --weston|--kwin|--hostwayland|--tty) store_runoption env "WAYLAND_DISPLAY=$Newwaylandsocket" store_runoption env "XDG_RUNTIME_DIR=/tmp/XDG_RUNTIME_DIR" ;; esac # Which X environment to use for password prompt (xtermrc) and for docker pull (pullrc) case $Xserver in --xorg) Terminalxenv="$Newxenv" ;; --weston|--kwin|--weston-xwayland|--kwin-xwayland|--tty) [ "$Hosttty" = "yes" ] && Terminalxenv="$Newxenv" || Terminalxenv="$Hostxenv" ;; *) Terminalxenv="$Hostxenv" ;; esac } check_vt() { # option --xorg: find free vt / tty local Line= Ttyinuse= # if started from console, use current tty [ "$Hosttty" = "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 verbose -d "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 } create_xdummyxorgconf() { # options --xdummy, --xpra: create xorg.conf and Xorg wrapper { 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 Modeline '$Modeline' 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 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_xcommand() { # create command to start X server and/or Wayland compositor local Xserveroptions= Xephyroptions= Nxagentoptions= Xpraoptions= Compositorpid= Westonoutput= Count= #### General X server options Xserveroptions="-dpms -s off" case $Xserver in --nxagent) { [ "$Sharehostipc" = "yes" ] || [ "$X11dockermode" = "exe" ] ; } && Xserveroptions="$Xserveroptions -shmem -shpix" || Xserveroptions="$Xserveroptions -noshmem -noshpix" ;; *) # general X server options Xserveroptions="$Xserveroptions -retro" Xserveroptions="$Xserveroptions \\ +extension RANDR +extension RENDER +extension GLX \\ +extension XVideo +extension DOUBLE-BUFFER" Xserveroptions="$Xserveroptions \\ -extension X-Resource +extension SECURITY +extension DAMAGE" Xserveroptions="$Xserveroptions \\ -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 cookie authentication case $Xauthentication in yes) case $Xserver in --vcxsrv) Xserveroptions="$Xserveroptions \\ -auth '$(convertpath --windows "$Xservercookie")'" ;; *) Xserveroptions="$Xserveroptions \\ -auth $Xservercookie" ;; esac ;; no) [ "$Xserver" = "--hostdisplay" ] || warning "Option --no-auth: SECURITY RISK! Allowing access to X server for everyone." Xserveroptions="$Xserveroptions \\ -ac" ;; esac # X over IP/TCP case $Xoverip in yes) case $Xserver in --nxagent) ;; *) Xserveroptions="$Xserveroptions \\ -listen tcp" ;; esac ;; no) Xserveroptions="$Xserveroptions \\ -nolisten tcp" ;; esac # X extension COMPOSITE [ "$Xcomposite" ] || case $Xserver in --nxagent|--vcxsrv|--xwin) Xcomposite="no" ;; *) Xcomposite="yes" ;; esac case $Xcomposite in yes) # Most X servers have extension "Composite", recent Xwayland and VcXsrv calls 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" [ "$Windowmanager" ] && warning "Did not disable X extension XTEST for X server $Xserver. If your host window manager $Windowmanager 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!" ;; no) Xserveroptions="$Xserveroptions \\ -extension XTEST -tst" ;; esac # check DPI case $Xserver in --xpra|--xpra-xwayland) { [ -n "$Dpi" ] || [ "$Scaling" ] ; } && verlt "$Xpraversion" "xpra v2.1-r16547" && ! verlt "$Xpraversion" "xpra v2.1" && { note "Option --dpi is buggy in $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." Dpi="-1" } ;; esac case $Xserver in --weston|--kwin|--tty|--hostdisplay) ;; *) [ -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." } case $Xserver in --xpra|--xpra-xwayland) [ "$Scaling" ] && { [ "$Desktopmode" = "no" ] || ! verlt "$Xpraversion" "xpra v2.2" && { # xpra desktop mode has same dpi baviour as non-desktop since v2.2. Before that, dpi in xpra desktop mode must not be adjusted 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) # disable proxy for high versions verlt "$Xpraversion" "xpra v2.1" || Xpraoptions="$Xpraoptions \\ --start-via-proxy=no" # disable --dpi for buggy versions [ -n "$Dpi" ] && verlt "$Xpraversion" "xpra v2.1-r16547" && ! verlt "$Xpraversion" "xpra v2.1" && Dpi="" verlt "$Xpraversion" "xpra v1.0" || Xpraoptions="$Xpraoptions \\ --webcam=no" # --keymap [ "$Xkblayout" ] && Xpraoptions="$Xpraoptions \\ --keyboard-layout='$Xkblayout' --keyboard-raw=yes" # specify unix socket location Xpraoptions="$Xpraoptions \\ --socket-dirs=$Cachefolder" # xpra server command [ "$Desktopmode" = "yes" ] && Xpraservercommand="xpra start-desktop" || Xpraservercommand="xpra start" Xpraservercommand="$Xpraservercommand $Newdisplay --use-display $Xpraoptions \\ --no-daemon --fake-xinerama=no --mdns=no \\ --file-transfer=off --printing=no --notifications=no \\ --start-new-commands=no --dbus-proxy=no --no-pulseaudio \\ --html=off --session-name=\"$Codename \"" verlt "$Xprarelease" "r13378" || Xpraservercommand="$Xpraservercommand --systemd-run=no" verbose -d "Xpra server command: $Xpraservercommand" # xpra client command Xpraclientcommand="xpra attach $Newdisplay $Xpraoptions \\ --title='@title@ [in container]' \\ -z0 --quality 100 \\ --no-speaker --no-pulseaudio \\ --notifications=no" [ "$Desktopmode" = "yes" ] && Xpraclientcommand="$Xpraclientcommand \\ --title='$Codename on $Newdisplay (shift+F11 toggles fullscreen)'" [ "$Fullscreen" = "yes" ] && Xpraclientcommand="$Xpraclientcommand \\ --desktop-fullscreen=yes" [ "$Scaling" ] && Xpraclientcommand="$Xpraclientcommand \\ --desktop-scaling=$Scaling" [ -n "$Dpi" ] && Xpraclientcommand="$Xpraclientcommand \\ --dpi=$Dpi" case $Shareclipboard in yes) Xpraclientcommand="$Xpraclientcommand --clipboard" ;; no) Xpraclientcommand="$Xpraclientcommand --no-clipboard" ;; esac [ "$Xpraborder" ] && Xpraclientcommand="$Xpraclientcommand \\ --border=$Xpraborder" verbose -d "Xpra client command: $Xpraclientcommand" [ -e "/run/user/$Hostuseruid" ] || { # can miss with option --hostuser, xpra needs it for socket verbose -d "Creating /run/user/$Hostuseruid" mkdir -p /run/user/$Hostuseruid chown $Hostuser /run/user/$Hostuseruid chmod 700 /run/user/$Hostuseruid } ;; esac #### Prepare weston.ini: config file for Weston case $Xserver in --weston|--weston-xwayland|--xpra-xwayland|--xdummy-xwayland) 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" [ -n "$Xkblayout" ] && echo "keymap_layout=$Xkblayout" >> "$Westonini" [ -z "$Xkblayout" ] && [ "$Hosttty" = "yes" ] && echo "$(echo -n "keymap_layout=" && grep XKBLAYOUT <"/etc/default/keyboard" | cut -d= -f2 | cut -d'"' -f2)" >> "$Westonini" [ -n "$Hostwaylandsocket" ] && [ "$Xserver" != "--xpra-xwayland" ] && [ "$Hostsystem" != "ubuntu" ] && [ "$Fullscreen" = "no" ] && { Westonoutput="WL" } || { [ -n "$Hostdisplay" ] && Westonoutput="X" } [ -z "$Westonoutput" ] && [ -n "$Hostwaylandsocket" ] && Westonoutput="WL" [ "$Hosttty" = "yes" ] && { [ -n "$Screensize" ] || [ "$Scaling" ] || [ -n "$Rotation" ] && { # short start&stop of Weston to grep name of monitor $Mksu "weston --no-config --backend=drm-backend.so >> $Compositorlogfile 2>&1 & echo \$! >>$Compositorpidfile" Compositorpid=$(cat $Compositorpidfile) waitforlogentry weston $Compositorlogfile "connector" || error "Weston startup failed. Can not run $Xserver. Last lines of weston log: $(tail "$Compositorlogfile")" Westonoutput="$(grep Output <$Compositorlogfile | grep connector | head -n1 | cut -d ' ' -f3 | rev | cut -c2- | rev)" verbose -d "Screen output for weston: $Westonoutput" kill $Compositorpid mkfile "$Compositorlogfile" mkfile "$Compositorpidfile" } } ;; esac #### create command to run X server case $Xserver in --xorg) Xserveroptions="$Xserveroptions \\ +extension XFree86-DRI +extension XFree86-DGA +extension XFree86-VidModeExtension" Xserveroptions="$Xserveroptions \\ -verbose" # make X verbose [ "$Hosttty" = "yes" ] && Xserveroptions="$Xserveroptions -keeptty" Xcommand="$(command -v Xorg) :$Newdisplaynumber vt$Newxvt \\ $Xserveroptions" ;; --xpra) case $Xpravfb in Xvfb) Xcommand="$(command -v Xvfb) :$Newdisplaynumber -screen 0 ${Maxxaxis}x${Maxyaxis}x24 \\ $Xserveroptions" ;; Xdummy) Xcommand="$Xorgwrapper :$Newdisplaynumber vt${Newxvt:-128} \\ -config $Xdummyconf \\ $Xserveroptions" ;; esac ;; --xdummy) Xcommand="$Xorgwrapper :$Newdisplaynumber \\ -config $Xdummyconf \\ $Xserveroptions" [ "$Winsubsystem" ] || Xcommand="$Xcommand vt${Newxvt:-128}" ;; --xvfb) Xcommand="$(command -v Xvfb) :$Newdisplaynumber -screen 0 ${Screensize}x24 \\ $Xserveroptions" ### FIXME: hardcoded setting of depth 24. Could be better? ;; --xephyr) case $Fullscreen in yes) Xephyroptions="$Xephyroptions -fullscreen" ;; no) for ((Count=0 ; Count<$Outputcount ; Count++)) ; do Xephyroptions="$Xephyroptions -screen $Screensize" ; done ;; esac #[ "$Xephyrglamor" = "yes" ] && Xephyroptions="$Xephyroptions -glamor" # deprecated option '--glamor' Xcommand="$(command -v Xephyr) :$Newdisplaynumber $Xephyroptions \\ $Xserveroptions" # Fallback: Xnest command -v Xephyr >/dev/null || { Xcommand="$(command -v Xnest) :$Newdisplaynumber -geometry $Screensize -scrns $Outputcount \\ -name '$Imagename on $Newdisplay' \\ $Xserveroptions " note "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'." } ;; --xwayland) Xcommand="$(command -v Xwayland) :$Newdisplaynumber \\ $Xserveroptions" ;; --xpra-xwayland|--xdummy-xwayland) Xcommand="$(command -v Xwayland) :$Newdisplaynumber \\ $Xserveroptions" [ "$Desktopmode" = "no" ] && [ "$Xserver" = "--xpra-xwayland" ] && Xcommand="$Xcommand \\ -rootless" [ "$Hostwaylandsocket" ] && Westonoutput="WL" [ "$Hostdisplay" ] && Westonoutput="X" echo "[output]" >> $Westonini case $Westonoutput in X|WL) echo "name=${Westonoutput}1" >> $Westonini ;; *) echo "name=${Westonoutput}" >> $Westonini ;; esac echo "mode=$Screensize" >> $Westonini [ -n "$Customwestonini" ] && Westonini="$Customwestonini" Compositorcommand="weston --socket=$Newwaylandsocket" case $Westonoutput in WL) Compositorcommand="$Compositorcommand --backend=wayland-backend.so" ;; X) Compositorcommand="$Compositorcommand --backend=x11-backend.so" ;; *) case "$Hosttty" in yes) Compositorcommand="$Compositorcommand --backend=drm-backend.so" ;; no) Compositorcommand="$Compositorcommand --backend=x11-backend.so" ;; esac ;; esac [ "$Xserver" = "--xpra-xwayland" ] && Compositorcommand="$Compositorcommand --fullscreen" [ -n "$Customwestonini" ] && Westonini="$Customwestonini" Compositorcommand="$Compositorcommand \\ --config='$Westonini'" ;; --weston|--weston-xwayland) [ "$Xserver" = "--weston-xwayland" ] && Xcommand="$(command -v Xwayland) :$Newdisplaynumber \\ $Xserveroptions" Compositorcommand="weston --socket=$Newwaylandsocket" [ -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 [ "$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 "$Hosttty" 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-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 $Hosttty in yes) Compositorcommand="$Compositorcommand --drm" ;; no) Compositorcommand="$Compositorcommand --windowed" ;; esac ;; --kwin) 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 $Hosttty in yes) Compositorcommand="$Compositorcommand --drm" ;; no) Compositorcommand="$Compositorcommand --windowed" ;; esac ;; --nxagent) Nxagentoptions="$Nxagentoptions \\ -options $Cachefolder/nxagentoptions \\ -norootlessexit -verbose -ac" case $Desktopmode in "yes") Nxagentoptions="$Nxagentoptions \\ -D -name '$Imagename on $Newdisplay (shift+F11 toggles fullscreen)'" ;; # desktop mode, similar to xephyr "no") Nxagentoptions="$Nxagentoptions -R" ;; # rootless mode, similar to xpra esac export NXAGENT_KEYSTROKEFILE="$Cachefolder/nx_keys" Xcommand="$(command -v nxagent) :$Newdisplaynumber $Xserveroptions $Nxagentoptions \\ -keystrokefile $NXAGENT_KEYSTROKEFILE" # Xcommand="$(echo $Xcommand | sed 's/+extension Composite.*//')" # create file for additional nxagent options Nxagentoptions="nx/nx" [ "$Shareclipboard" = "yes" ] && Nxagentoptions="$Nxagentoptions,clipboard=both" || Nxagentoptions="$Nxagentoptions,clipboard=none" [ "$Fullscreen" = "yes" ] && Nxagentoptions="$Nxagentoptions,fullscreen=1" || { [ -n "$Screensize" ] && Nxagentoptions="$Nxagentoptions,geometry=$Screensize" } # set keyboard layout case $Xkblayout in "") # --keymap not set? set layout from host. command -v setxkbmap >/dev/null && { Nxagentoptions=$Nxagentoptions,keyboard=$(setxkbmap -query | grep rules | rev | cut -d" " -f1 | rev)/$(setxkbmap -query | grep layout | rev | cut -d" " -f1 | rev) } || note "Could not check your keyboard layout due to missing setxkbmap If you get mismatching keys, please install setxkbmap." ;; *) # --keymap Nxagentoptions=$Nxagentoptions,keyboard="evdev/$Xkblayout" ;; esac Nxagentoptions="$Nxagentoptions$Newdisplay" mkfile "$Cachefolder/nxagentoptions" echo $Nxagentoptions >> "$Cachefolder/nxagentoptions" # workaround as nxagent ignores XAUTHORITY and fails to start if option -auth is given. Option -ac above complies "xhost +" and is disabled in xinitrc [ "$Xauthentication" = "yes" ] && { $Mksu "cp '$Hostxauthority' '$Xservercookie'" $Mksu "$Xauthexe -f '$Xclientcookie' add $Newdisplay . $(mcookie)" $Mksu "$Xauthexe -f '$Xclientcookie' nlist $Newdisplay | $Xauthexe -f '$Xservercookie' nmerge -" } # fake NXclient export NX_CLIENT="$Cachefolder/nx_client" mkfile "$NX_CLIENT" 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 >> '$Timetosaygoodbye' ;; esac ' >> "$NX_CLIENT" $Mksu "chmod +x '$NX_CLIENT'" mkfile "$NXAGENT_KEYSTROKEFILE" echo ' ' >> "$NXAGENT_KEYSTROKEFILE" ;; --vcxsrv|--xwin) [ "$Xserver" = "--vcxsrv" ] && Xcommand="$(escapestring "$Vcxsrvpath")/vcxsrv.exe :$Newdisplaynumber" [ "$Xserver" = "--xwin" ] && Xcommand="$(command -v Xwin) :$Newdisplaynumber" Xcommand="$Xcommand \\ $Xserveroptions \\ " case $Sharegpu in no) Xcommand="$Xcommand -nowgl -iglx" ;; yes) Xcommand="$Xcommand -wgl +iglx" store_runoption env "LIBGL_ALWAYS_INDIRECT=1" ;; esac case $Fullscreen in yes) Xcommand="$Xcommand -fullscreen" ;; no) Xcommand="$Xcommand -lesspointer" case $Desktopmode in yes) for ((Count=0 ; Count<$Outputcount ; Count++)); do Xcommand="$Xcommand -screen $Count $Screensize" done ;; no) Xcommand="$Xcommand -multiwindow" ;; esac ;; esac case $Shareclipboard in yes) Xcommand="$Xcommand -clipboard" ;; no) Xcommand="$Xcommand -noclipboard" ;; esac ;; --hostwayland|--hostdisplay|--tty) ;; esac return 0 } create_xinitrc() { # create xinitrc: set up X environment, create cookies echo "#! /bin/sh" #[ "$Debugmode" = "yes" ] && echo "set -x" echo "Timetosaygoodbye=$Timetosaygoodbye" echo "Timetosaygoodbyefifo=$Timetosaygoodbyefifo" echo "Sharefolder=$Sharefolder" echo "Bgpidfile=$Bgpidfile" echo "Winsubsystem=$Winsubsystem" # declaring functions echoes them into xinitrc declare -f storepid declare -f rocknroll waitfortheend saygoodbye declare -f rmcr disable_xhost echo "$Messagefifofuncs" echo "Messagefile=$Messagefifo" echo "" echo "export PATH='$PATH'" echo "verbose -d 'Running xinitrc'" [ "$Noxhost" = "yes" ] && [ -n "$Hostdisplay" ] && { echo "export $Hostxenv" echo 'verbose -d "Option --no-xhost: disabling any access to host display $DISPLAY granted by xhost"' echo 'disable_xhost || warning "Option --no-xhost failed. Output of xhost:' echo '$(xhost 2>&1 | rmcr)"' } echo "export $Newxenv" case $Xserver in --weston|--kwin|--hostwayland) echo "unset DISPLAY XAUTHORITY" ;; --tty) echo "unset DISPLAY XAUTHORITY WAYLAND_DISPLAY" ;; *) # here something for real X servers echo "# background color" case $Xserver in --hostdisplay) ;; --nxagent) echo "sleep 2 && xsetroot -solid '#7F7F7F' &" ;; *) echo "xsetroot -solid '#7F7F7F'" ;; esac [ "$Xauthentication" = "yes" ] && { echo "# create new XAUTHORITY cookies" echo "cd $Sharefolder" [ "$Xserver" = "--nxagent" ] && { echo "cp $Xclientcookie $Xclientcookie.bak # nxagent workaround cookie was created before starting xinit" echo "export XAUTHORITY=$Xclientcookie.bak" } [ "$Trusted" = "yes" ] && Trusted="trusted" || Trusted="untrusted" echo ":> $Xclientcookie" [ "$Xserver" = "--hostdisplay" ] && { [ -s "$Hostxauthority" ] && echo "export XAUTHORITY=$Hostxauthority" echo "xhost | rmcr | grep -q 'SI:localuser:$Hostuser' || { xhost +SI:localuser:$Hostuser | rmcr ; Xhostentry='yes' ; }" } case "$Hostssh" in no) case $Xserver in --vcxsrv) ;; *) echo "verbose 'Requesting $Trusted cookie from X server'" echo "echo 'Requesting $Trusted cookie from X server'" echo "$Xauthexe -v -i -f Xclientcookie generate $Newdisplay . $Trusted timeout 3600 | rmcr" ;; esac ;; yes) echo "verbose 'Can not use cookies created over SSH. Will bake one myself.'" ;; esac echo "export XAUTHORITY=$Xclientcookie" echo "[ -s '$Xclientcookie' ] || { " echo " [ '$Trusted' = 'untrusted' ] && note 'Could not create untrusted cookie. " echo " Maybe your X server misses extension SECURITY.'" [ "$Xserver" = "--hostdisplay" ] && { [ "$Sharehostipc" = "no" ] && [ "$Hostssh" = "no" ] && { echo " warning 'Memory access failures and rendering glitches" echo " may occure due to unrestricted cookie." echo " Avoid them with isolation breaking option --hostipc," echo " or use another X server option like --xpra or --nxagent.'" } echo " warning 'SECURITY RISK! Keylogging and remote host control " echo " may be possible! Better avoid using option --hostdisplay," echo " rather use --xpra or --nxagent.'" echo " cp $Hostxauthority $Xclientcookie" } echo "}" echo "ls -l $Xclientcookie" echo "[ -s '$Xclientcookie' ] || { " echo " # still no cookie? try to create one without extension security" echo " verbose -d '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 " $Xauthexe -v -i -f Xclientcookie add :$Newdisplaynumber . $(mcookie) | rmcr" echo " ls -l $Xclientcookie" echo "}" echo "# create prepared cookie with localhost identification disabled by ffff, needed if X socket is shared. ffff means 'familiy wild'" echo "Cookie=\$($Xauthexe -i -f Xclientcookie nlist | rmcr | sed -e 's/^..../ffff/')" echo "echo \"\$Cookie\" | $Xauthexe -v -i -f Xclientcookie nmerge - | rmcr" echo "cp $Xclientcookie $Xservercookie" echo "[ -s '$Xclientcookie' ] || warning 'Cookie creation failed!'" echo "verbose -d \"Created cookie: \$($Xauthexe -f Xclientcookie list 2>&1 | rmcr)\"" echo "chmod 644 $Xclientcookie" [ "$Xserver" = "--hostdisplay" ] && echo "[ '\$Xhostentry' = 'yes' ] && env XAUTHORITY=$Hostxauthority xhost -SI:localuser:$Hostuser | rmcr" [ "$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.'" case "$Xserver" in --hostdisplay) ;; # do not change host settings --vcxsrv|--xwin) ;; # xhost does not work over tcp *) case $Xauthentication in yes) echo "verbose 'Disabling any possible access to new X server possibly granted by xhost'" echo "disable_xhost" ;; esac [ -n "$Xhost" ] && { echo "verbose 'Custom xhost setting on $Newdisplay (option --xhost): $Xhost'" echo "xhost $Xhost | rmcr" } [ "$Initsystem" = "systemd" ] || [ "$Dbussystem" = "yes" ] && { echo "note 'Allowing xhost access for $Containeruser on display $Newdisplay" echo " to avoid service issues with X access. (option --systemd or --dbus-system):" echo " xhost +SI:localuser:$Containeruser'" echo "xhost +SI:localuser:$Containeruser | rmcr" } ;; esac case $Xserver in --hostdisplay|--vcxsrv|--xwin|--nxagent) ;; *) # 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." } [ -s "$Xkbkeymapfile" ] && { echo "#" echo "# set keyboard layout on $Newdisplay to same as on host" echo "echo 'x11docker: keyboard layout:'" echo "cat $Xkbkeymapfile" echo "xkbcomp $Xkbkeymapfile $Newdisplay | rmcr" } ;; esac [ "$Shareclipboard" = "yes" ] && [ -n "$Hostdisplay" ] && { case $Xserver in --xpra|--xpra-xwayland|--nxagent|--vcxsrv|--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 $Shareclipboardscript & storepid \$! shareclipboard" ;; esac } ;; esac [ "$Setwaylandenv" = "yes" ] && for Line in $Waylandtoolkitenv ; do echo "export $Line" ; done [ "$Sharewayland" = "yes" ] && [ -n "$Newwaylandsocket" ] && echo "export WAYLAND_DISPLAY=$Newwaylandsocket" case $Xserver in --weston|--kwin|--hostwayland|--tty) ;; *) 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 "}" [ "$Xserver" != "--vcxsrv" ] && echo "Output=\$(getprimary)" ;; esac case $Xserver in --xpra|--xpra-xwayland) ! verlt "$Xpraversion" "xpra v2.3" && verlt "$Xprarelease" "r19606" && { warning "Your xpra version has a cookie authentication issue. Installed version is: $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'" echo "xhost +SI:localuser:$Containeruser | rmcr" } ;; esac case $Xserver in --xpra|--xdummy|--xvfb) # create set of different screen resolutions echo "xrandr --newmode $Modeline" echo "xrandr --addmode \$Output $(echo $Modeline | cut -d " " -f1)" [ "$Xserver" = "--xpra" ] && [ "$Desktopmode" = "yes" ] && echo "xrandr --output \$Output --mode $(echo $Modeline | cut -d " " -f1)" { [ "$Desktopmode" = "yes" ] || [ "$Xserver" = "--xdummy" ] || [ "$Xserver" = "--xvfb" ] ; } && { echo "for Ycount in 25 30 40 45 50 55 60 65 70 75 80 85 90 95 100; do" echo " for Xcount in 25 30 40 45 50 55 60 65 70 75 80 85 90 95 100; do" echo " Modeline=\$(cvt \$(awk -v a=$Maxxaxis -v b=\$Xcount 'BEGIN {print (a * b / 100)}') \$(awk -v a=$Maxyaxis -v b=\$Ycount 'BEGIN {print (a * b / 100)}') | tail -n1 | cut -d' ' -f2- | tr -d '\"')" echo " xrandr --newmode \$Modeline" echo " xrandr --addmode \$Output \$(echo \$Modeline | cut -d' ' -f1)" echo " done" echo "done 2>&1 | uniq" } ;; --xorg) # --xorg: --scale, --size, --rotate echo "xrandr | grep connected | grep -v disconnected && {" [ -z "$Screensize" ] && { echo " # determine screen size" 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}" [ "$Screensize" ] && [ -z "$Scaling" ] && { 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.\"" } [ "$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 ' }' } [ "$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 " }" } [ -n "$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 ' [ -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 "}" ;; esac [ -n "$Newdisplay" ] && [ "$Xserver" != "--vcxsrv" ] && echo "verbose \"Output of xrandr on $Newdisplay \$(xrandr)\"" [ "$Xfishtank" = "yes" ] && echo "xfishtank & storepid \$! xfishtank" [ -n "$Windowmanager" ] && { echo "verbose 'Starting host window manager $Windowmanager on new display $Newdisplay'" echo "$Windowmanager & storepid \$! windowmanager" } [ "$Runfromhost" ] && { echo "# custom host command added with option --runfromhost" echo "$Runfromhost" } echo "touch $Cachefolder/Xready" echo "waitfortheend" return 0 } >> $Xinitrc #### docker command setup store_runoption() { # store env, cap, volume or device for docker command # $1 env store environment variable $2 # volume store volume path $2 # device store device $2 # cap store apability $2 # dump dump all entries of $2 local Count Line case ${1:-} in env) Containerenvironmentcount=$((Containerenvironmentcount + 1)) Containerenvironment[$Containerenvironmentcount]="${2:-}" ;; volume) [ -e "$(convertpath --subsystem "${2:-}")" ] && { Sharevolumescount=$((Sharevolumescount + 1)) Sharevolumes[$Sharevolumescount]="${2:-}" } || warning "File or folder not found. Will not share volume: ${2:-}" ;; device) [ -e "$(convertpath --subsystem "${2:-}")" ] && { Sharedevicescount=$((Sharedevicescount + 1)) Sharedevices[$Sharedevicescount]="${2:-}" } || warning "Device not found: ${2:-}" ;; 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 ;; device) for ((Count=1 ; Count<=$Sharedevicescount ; Count ++)) ; do echo "${Sharedevices[$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 } check_containeruser() { # check container user and shared home folder (also option --user) ## check container user [ -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) [ "$Sharehosthome" = "no" ] && Containeruserhome="/fakehome/$Containeruser" || Containeruserhome=$(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 Containeruserhome="/fakehome/$Containeruser" [ -z "$Containeruserhosthome" ] && [ "$Sharehosthome" = "yes" ] && { warning "Option --home: Will not create a persistent home folder on host for a non-existing user. Instead, you can specify --homedir to a location where user uid '$Containeruseruid' has write access. Fallback: Disabling option --home, not creating a persistent home folder." Sharehosthome="no" } } Containerusergroup="$(getent group $Containerusergid | cut -d: -f1 || echo group_$Containeruser)" [ "$Containeruseruid" = "0" ] && { Containeruser="root" Containerusergid="0" Containerusergroup="root" Containeruserhome="/root" Sudouser="yes" && note "Option --user=root: Enabling option --sudouser." } return 0 } 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 '--homedir': share custom host folder as home ## option '--homebasedir': Specify base folder here to store container home folders for --home # base home folder [ "$Homebasefolder" ] && { Homebasefolder="$(convertpath --subsystem "$Homebasefolder")" [ -e "$Homebasefolder" ] || { warning "Option --homebasedir: Specified path does not exist: $Homebasefolder Fallback: Using default home base directory." Homebasefolder="" } } [ "$Homebasefolder" ] || case $Winsubsystem in "") Homebasefolder="$Containeruserhome/.local/share/x11docker" ;; *) Homebasefolder="$(convertpath --subsystem "$(cmd.exe /C "echo %userprofile%" | rmcr)")/x11docker/home" ;; esac [ "$Sharehosthome" = "yes" ] && { # if no home folder on host is specified (--homedir), create a standard one in ~/.local/share/x11docker [ -z "$Containeruserhosthome" ] && Containeruserhosthome="$Homebasefolder/$Codename" [ -d "$Containeruserhosthome" ] || { [ "$Startuser" = "root" ] && su $Containeruser -c "mkdir -p '$Containeruserhosthome'" [ "$Containeruser" = "$Hostuser" ] && $Mksu "mkdir -p '$Containeruserhosthome'" && { # create symbolic link to ~/x11docker echo "$Containeruserhosthome" | grep -q .local/share/x11docker && [ ! -e "$Containeruserhome/x11docker" ] && $Mksu "ln -s '$Homebasefolder' '$Containeruserhome/x11docker'" ||: } || { # errors can occur if specifying a user with --user different from current user error "Can not create persistent home folder for user '$Containeruser' on host (option --home or --homedir). Four possibilities to solve issue: 1.) Create folder with write access for user '$Containeruser' $Containeruserhosthome 2.) Run x11docker one time as user '$Containeruser'. 3.) Run x11docker one time as user 'root'. 4.) Use option --homedir=DIR with DIR pointing to a writeable folder." } } writeaccess $Containeruseruid "$Containeruserhosthome" || warning "User '$Containeruser' seems to have no write access to $Containeruserhosthome." verbose "Sharing directory $Containeruserhosthome with container as its home directory $Containeruserhome" } return 0 } setup_initsystem() { # options --tini (default), --systemd,--openrc, --sysvinit: set up capabilities, check or create files # some init system setup also in containerrootrc local Message= store_runoption env "container=docker" # At least OpenRC and systemd regard this hint case $Initsystem in none) ;; tini) Tinibinary="$(command -v docker-init ||:)" [ -z "$Tinibinary" ] && Tinibinary="/snap/docker/current/bin/docker-init" [ -e "$Tinibinary" ] || Tinibinary="/snap/docker/current/usr/bin/docker-init" [ -e "/usr/local/share/x11docker/tini-static" ] && Tinibinary="/usr/local/share/x11docker/tini-static" [ -e "$Hostuserhome/.local/share/x11docker/tini-static" ] && Tinibinary="$Hostuserhome/.local/share/x11docker/tini-static" Tinibinary="$(realpath "$Tinibinary" 2>/dev/null ||:)" [ -e "$Tinibinary" ] || Tinibinary="" [ "$Tinibinary" ] && { [ -x "$Tinibinary" ] || { chmod +x "$Tinibinary" || { warning "Your tini binary is not executeable. Please run chmod +x $Tinibinary" Initsystem="none" } } } || { Initsystem="none" 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 "Found tini binary: ${Tinibinary:-(none)}" ;; systemd) warning "Option --systemd degrades container isolation. It allows some docker container capabilities (nearly like --cap-default). It shares access to host cgroups in /sys/fs/cgroup. systemd services started as root might try to abuse their privileges." Stopsignal="SIGRTMIN+3" Containerusergroups="$Containerusergroups systemd-journal" echo "[Unit] Description=x11docker target Wants=multi-user.target After=multi-user.target [Install] Also=x11docker-startcmd.service Also=x11docker-watch.service Also=x11docker-journal.service " >> $Systemdtarget echo "[Unit] Description=x11docker start CMD service Wants=multi-user.target Wants=x11docker-watch.service Wants=x11docker-journal.service Wants=dbus.service After=systemd-user-sessions.service plymouth-quit-wait.service After=rc-local.service getty-pre.target Before=getty.target [Service] #CONTAINERUSER will be replaced in dockerrc #User=CONTAINERUSER # disabled because it does not invoke logind #ExecStart=/bin/sh $Cshare/container.CMD.sh #ExecStart=/bin/su - -s /bin/sh CONTAINERUSER $Cshare/container.CMD.sh #ExecStart=/usr/local/bin/x11docker-login ExecStart=/sbin/agetty -a CONTAINERUSER -l /usr/local/bin/x11docker-login console StandardInput=tty StandardOutput=tty Type=idle #Restart=always UtmpIdentifier=cons TTYPath=/dev/console TTYReset=yes TTYVHangup=yes KillMode=process IgnoreSIGPIPE=no SendSIGHUP=yes [Install] WantedBy=x11docker.target WantedBy=getty.target " >> $Systemdstartcmd echo "[Unit] Description=x11docker watch service [Service] Type=simple ExecStart=/bin/sh -c 'while sleep 1; do systemctl is-active console-getty >/dev/null || { echo timetosaygoodbye >>$Cshare/timetosaygoodbye ; systemctl halt ; } ; [ -s $Cshare/timetosaygoodbye ] && systemctl halt ; done' [Install] WantedBy=x11docker.target " >> $Systemdwatch echo "[Unit] Description=x11docker journal log service [Service] Type=simple ExecStart=/bin/sh -c '/bin/journalctl --follow --no-tail --merge >> $Cshare/journalctl.log 2>&1' [Install] WantedBy=x11docker.target " >> $Systemdjournal echo "[Manager] DefaultEnvironment=DISPLAY=$Newdisplay XAUTHORITY=$Cshare/Xclientcookie $(while read -r Line; do echo -n "$Line" ; done < <(store_runoption dump env)) " >> $Systemdenvironment ;; runit) warning "Option --runit slightly degrades container isolation. Beside some user switching capabilities x11docker would disable otherwise it adds capability SYS_BOOT. runit services started as root might try to abuse their privileges." Stopsignal="HUP" store_runoption env "VIRTUALIZATION=docker" ;; openrc) warning "Option --openrc slightly degrades container isolation. Beside some user switching capabilities x11docker would disable otherwise it adds capability SYS_BOOT. OpenRC services started as root might try to abuse their privileges." ;; sysvinit) warning "Option --sysvinit slightly degrades container isolation. Beside some user switching capabilities x11docker would disable otherwise it adds capability SYS_BOOT. SysVinit services started as root might try to abuse their privileges." Stopsignal="INT" ;; esac return 0 } setup_capabilities() { # check linux capabilities needed by container # compare: man capabilities [ "$Sudouser" = "yes" ] && Adminusercaps="yes" [ "$Capdropall" = "no" ] && Nonewprivileges="no" # --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" case $Sharewayland in yes) Switchcontaineruser="yes" ;; no) Switchcontainerusercaps="yes" ;; esac } # option --sys-admin [ "$Capsysadmin" = "yes" ] && store_runoption cap "SYS_ADMIN" case $Initsystem in none|tini) ;; systemd) Switchcontaineruser="yes" Sharecgroup="yes" store_runoption cap "FSETID FOWNER SETPCAP SYS_BOOT" ;; runit|openrc|sysvinit) Switchcontaineruser="yes" store_runoption cap "SYS_BOOT KILL" ;; esac [ "$Sharecgroup" = "yes" ] && Switchcontaineruser="yes" # needed for elogind [ "$Switchcontaineruser" = "yes" ] && Switchcontainerusercaps="yes" [ "$Adminusercaps" = "yes" ] && { Switchcontainerusercaps="yes" Nonewprivileges="no" store_runoption cap "CHOWN KILL FSETID FOWNER SETPCAP" } [ "$Switchcontainerusercaps" = "yes" ] && store_runoption cap "SETUID SETGID DAC_OVERRIDE AUDIT_WRITE" [ "$Sharegpu" = "yes" ] && [ "$Nvidiadriver" ] && store_runoption cap "SETUID SETGID DAC_OVERRIDE CHOWN FOWNER" # 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 --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_hostdbus() { # option --hostdbus: connect to host DBus session daemon. local Busadress= warning "--hostdbus: Connecting container to host DBus degrades container isolation. Container applications might send malicious requests." Busadress="${DBUS_SESSION_BUS_ADDRESS:-}" store_runoption env "NO_AT_BRIDGE=1" Dbusrunsession=no [ "$Busadress" ] || { # 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." } grep -q "unix:path" <<< "$Busadress" && { # DBus socket file store_runoption env "DBUS_SESSION_BUS_ADDRESS=$Busadress" store_runoption volume "/$(cut -d/ -f2- <<<"$Busadress"):ro" } grep -q "unix:abstract" <<< "$Busadress" && { # DBus abstract socket (dbus-launch) store_runoption env "DBUS_SESSION_BUS_ADDRESS=$Busadress" [ "${DBUS_SESSION_BUS_PID:-}" ] && store_runoption env "${DBUS_SESSION_BUS_PID:-}" [ "${DBUS_SESSION_BUS_WINDOWID:-}" ] && store_runoption env "${DBUS_SESSION_BUS_WINDOWID:-}" Sharehostnet="yes" warning "Option --hostdbus: Did not find a DBus session socket file but an abstract unix socket. To allow access for container, x11docker sets docker run option '--network=host' (option --hostnet). This degrades container isolation. Container shares host network stack." } return 0 } create_dockercommand() { # create command to run docker local Line= Memory Dockercommand="$Dockerexe run --tty" [ "$Preservecachefiles" = "no" ] && Dockercommand="$Dockercommand --rm" case $Interactive in yes) Dockercommand="$Dockercommand --interactive" ;; no) Dockercommand="$Dockercommand --detach" ;; esac [ -z "$Containername" ] && Containername="x11docker_X${Newdisplaynumber}_${Mycookie}_$Codename" Dockercommand="$Dockercommand \\ --name $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) case $Switchcontaineruser in no) [ "$Createcontaineruser" = "yes" ] && Dockercommand="$Dockercommand \\ --user $Containeruseruid:$Containerusergid \\ --env USER=$Containeruser" ;; yes) Dockercommand="$Dockercommand \\ --user root \\ --env USER=root" ;; esac ;; systemd|runit|openrc|sysvinit) Dockercommand="$Dockercommand \\ --user root" ;; esac [ "$Createcontaineruser" = "yes" ] && { # Disable user namespacing to avoid file permission issues with --home or --sharedir. Files need same UID/GID. Dockercommand="$Dockercommand \\ --userns host" } # add container user groups, mainly video and audio and --group-add [ "$Switchcontaineruser" = "no" ] && { 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 } # capabilities [ "$Capdropall" = "yes" ] && Dockercommand="$Dockercommand \\ --cap-drop ALL" while read Line ; do Dockercommand="$Dockercommand \\ --cap-add $Line" done < <(store_runoption dump cap) # default yes, do not gain privileges [ "$Nonewprivileges" = "yes" ] && 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" # stop signal for some init systems [ "$Stopsignal" ] && Dockercommand="$Dockercommand \\ --stop-signal $Stopsignal" # shared files for some init systems case $Initsystem in tini) Dockercommand="$Dockercommand \\ --volume $(convertpath --volume "$Tinibinary:ro" "$Cshare/tini")" ;; systemd) Dockercommand="$Dockercommand \\ -v $Systemdtarget:/etc/systemd/system/x11docker.target:ro \\ -v $Systemdstartcmd:/lib/systemd/system/console-getty.service:ro \\ -v $Systemdwatch:/etc/systemd/system/x11docker-watch.service:ro \\ -v $Systemdjournal:/etc/systemd/system/x11docker-journal.service \\ -v $Systemdenvironment:/etc/systemd/system.conf.d/x11docker.conf" ;; esac # option --sharecgroup [ "$Sharecgroup" = "yes" ] && Dockercommand="$Dockercommand \\ --volume /sys/fs/cgroup:/sys/fs/cgroup:ro" # Needed especially for --systemd and --dbus-daemon Dockercommand="$Dockercommand \\ --tmpfs /run --tmpfs /run/lock" # shared cache folder Dockercommand="$Dockercommand \\ --volume $(convertpath --volume "$Sharefolder:rw" $Cshare)" # --home, --homedir [ "$Sharehosthome" = "yes" ] && [ -n "$Containeruserhosthome" ] && Dockercommand="$Dockercommand \\ --volume $(convertpath --volume "$Containeruserhosthome:rw" "$Containeruserhome")" # --sharedir while read -r Line; do case "$Line" in "$Containeruserhome") Dockercommand="$Dockercommand \\ --volume $(convertpath --volume "$Containeruserhome" "$Containeruserhome/$(basename $Containeruserhome)")" ;; *) Dockercommand="$Dockercommand \\ --volume $(convertpath --volume "$Line")" ;; esac done < <(store_runoption dump volume) # devices: --gpu, --webcam, --alsa while read -r Line; do Dockercommand="$Dockercommand \\ --device $(convertpath --volume "$Line") \\ --volume $(convertpath --volume "$Line")" done < <(store_runoption dump device) # --gpu: share NVIDIDA driver installer [ -e "$Nvidiadriver" ] && Dockercommand="$Dockercommand \\ --volume $(convertpath --volume "$Nvidiadriver:ro" "$Cshare/NVIDIA-$Nvidiaversion.run")" # X socket will be softlinked to /tmp/.X11-unix in container.CMD.sh [ "$Newxsocket" ] && Dockercommand="$Dockercommand \\ --volume $(convertpath --volume "$Newxsocket" "/X$Newdisplaynumber")" # Wayland socket will be softlinked to XDG_RUNTIME_DIR in container.CMD.sh [ "$Sharewayland" = "yes" ] && Dockercommand="$Dockercommand \\ --volume $(convertpath --volume "$XDG_RUNTIME_DIR/$Newwaylandsocket" "/$Newwaylandsocket")" ## options --hostipc and --hostnet [ "$Sharehostipc" = "yes" ] && Dockercommand="$Dockercommand \\ --ipc host" [ "$Sharehostnet" = "yes" ] && Dockercommand="$Dockercommand \\ --network host" ## option --no-internet [ "$Internetaccess" = "no" ] && Dockercommand="$Dockercommand \\ --net none" ## option --pulseaudio [ "$Pulseaudiomode" = "socket" ] && Dockercommand="$Dockercommand \\ --volume $Cachefolder/pulseclient.conf:/etc/pulse/client.conf:ro" ## option --workdir or /tmp Dockercommand="$Dockercommand \\ --workdir '$Workdir'" # real entrypoint is checked in dockerrc Dockercommand="$Dockercommand \\ --entrypoint env" # add environment variables. Only needed here for possible 'docker exec'. Otherwise set in container.CMD.sh while read Line; do Dockercommand="$Dockercommand \\ --env '$Line'" done < <(store_runoption dump env) # add custom docker arguments, imagename and imagecommand [ "$Customdockeroptions" ] && Dockercommand="$Dockercommand \\ $Customdockeroptions" Dockercommand="$Dockercommand \\ --" case $Switchcontaineruser in no) Dockercommand="$Dockercommand $Imagename /bin/sh - $Cshare/container.CMD.sh" ;; # dockerrc runs containerrootrc with 'docker exec' yes) case $Initsystem in tini) Dockercommand="$Dockercommand $Imagename $Cshare/tini -- /bin/sh - $Cshare/containerrootrc" ;; # containerrootrc runs container.CMD.sh *) Dockercommand="$Dockercommand $Imagename /bin/sh - $Cshare/containerrootrc" ;; # containerrootrc runs container.CMD.sh esac ;; esac return 0 } #### docker helper scripts create_dockerrc() { # create dockerrc: This script runs as root (or member of group docker) on host # create container.CMD.sh -> runs as unprivileged user in container # check and set up cgroup on host for systemd or elogind # run docker local Line= Wantcgroup= echo "#! /usr/bin/env bash" declare -f mkfile declare -f rocknroll declare -f waitforfilecreation declare -f escapestring rmcr echo "Hostuser=$Hostuser" echo "Hostusergid=$Hostusergid" echo "Timetosaygoodbye=$Timetosaygoodbye" echo "$Messagefifofuncs" echo "Messagefile=$Messagefifo" echo "" echo "verbose -d 'Running dockerrc'" [ "$Debugmode" = "yes" ] && { echo "PS4='+ dockerrc: \$(date +%S+%3N) '" #echo "set -x" declare -f traperror | sed 's/Command/dockerrc: Command/' echo "set -Eu" echo "trap 'traperror \$? \$LINENO \$BASH_LINENO \"\$BASH_COMMAND\" \$(printf \"::%s\" \${FUNCNAME[@]})' ERR" } echo "Imagename=\"$Imagename\"" echo "Imagecommand=\"$Imagecommand\"" 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 echo "export '$Line'" verbose -d "Found docker environment variable: $Line" done < <(env | grep -e '^DOCKER_') # possible Windows pathes to docker.exe [ "$Winsubsystem" ] && echo "export PATH=\"$(convertpath --subsystem "C:/Program Files/docker"):$(convertpath --subsystem "C:/Program Files/Docker/Docker/resources/bin"):$PATH\"" echo "# check whether docker daemon is running and refresh images.list for x11docker-gui" echo "mkfile $Cachebasefolder/x11docker-gui/images.list" echo "$Dockerexe images --format '{{.Repository}}:{{.Tag}}' >>$Cachefolder/images.list 2>>$Containerlogfile || { rmcr '$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: \$(tail $Containerlogfile)\" }" echo "rmcr $Cachefolder/images.list" echo "while read -r Line ; do" echo ' grep -q "" <<<$Line || echo $Line >> '$Cachebasefolder/x11docker-gui/images.list echo "done < <(sort < $Cachefolder/images.list)" echo "" echo "Dockerpull=no" case $Pullimage in no) ;; always) echo "Dockerpull=yes" ;; yes) echo "$Dockerexe inspect --type=image $Imagename >>$Containerlogfile 2>&1 || Dockerpull=yes" ;; ask) [ "$Runsinterminal" = "yes" ] && { echo "$Dockerexe inspect --type=image $Imagename >>$Containerlogfile 2>&1 || {" echo " read -t60 -n1 -e -p \"Image '$Imagename' not found locally. " echo "Do you want to pull it from docker hub? [Y|n]" echo "(Will wait up to 60s for a response, otherwise assuming no)\" Choice" echo " [ \"\$?\" = '0' ] && {" echo " [[ \"\$Choice\" == [YyJj]* ]] || [ -z \"\$Choice\" ] && Dockerpull=yes" echo " }" echo "}" } ;; esac echo "[ \"\$Dockerpull\" = 'yes' ] && {" echo " note 'Pulling image $Imagename from docker hub'" echo " notify-send 'x11docker: Pulling image $Imagename from docker hub' 2>/dev/null" echo " $Sudo $Dockerexe pull $Imagename 1>&2" echo "}" echo "$Dockerexe inspect --type=image $Imagename >>$Containerlogfile 2>&1 || {" echo " error 'Did not find image $Imagename. Please build image first or pull it from docker hub with: docker pull $Imagename or run x11docker with option --pull=yes'" echo "}" echo "# check CMD" echo "[ -z \"\$Imagecommand\" ] && {" echo " # extract image command from image if not given on cli" echo " Imagecommand=\"\$($Dockerexe inspect --format='{{.Config.Cmd}}' $Imagename | rmcr)\"" echo " Imagecommand=\"\${Imagecommand#[}\"" echo " Imagecommand=\"\${Imagecommand%]}\"" echo " Imagecommand=\"\${Imagecommand#/bin/sh -c }\"" echo "}" echo "# check USER" echo "Imageuser=\$(docker inspect --format '{{.Config.User}}' $Imagename | rmcr)" 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 "echo $Containeruser >> $Containeruserfile" echo "sed -i \"s/CONTAINERUSER/$Containeruser/\" $Systemdstartcmd" ;; no) echo "echo \${Imageuser:-root} >> $Containeruserfile" echo "sed -i \"s/CONTAINERUSER/\${Imageuser:-root}/\" $Systemdstartcmd" ;; esac [ "$Tinibinary" ] && echo "Tini=\"$Cshare/tini --\"" || echo "Tini=" echo "Exec=exec" case $Noentrypoint in yes) echo "Entrypoint=" ;; no) echo "# check ENTRYPOINT" echo "Entrypoint=\$($Dockerexe inspect --format='{{.Config.Entrypoint}}' $Imagename | rmcr)" echo "Entrypoint=\${Entrypoint#[}" echo "Entrypoint=\${Entrypoint#/bin/sh -c }" echo "Entrypoint=\${Entrypoint%]}" case $Initsystem in systemd|sysvinit|runit|openrc) echo "echo \"\$Entrypoint\" | grep -qE '/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 --no-init.\"" echo " Entrypoint=" echo "}" ;; tini) echo "echo \"\$Entrypoint\" | grep -qE '/tini|/init|/systemd' && {" echo " note \"There seems to be an init system in ENTRYPOINT of image: \$Entrypoint x11docker will run this instead of tini from host. You can disable this ENTRYPOINT with option --no-entrypoint.\"" echo " Tini=" echo "}" ;; none) echo "echo \"\$Entrypoint\" | grep -qE '/tini|/init|/systemd' && {" echo " note \"There seems to be an init system in ENTRYPOINT of image: \$Entrypoint \"" echo "} || {" [ "$Desktopmode" = "yes" ] && { echo " Exec= # disable exec to have sh as a fake init for desktop environments. Allows logout." echo " note 'Using sh as a fake init system / PID 1 in container due to missing /usr/bin/docker-init on host. Without at least a fake init desktop logouts would fail. Please consider to provide tini for x11docker containers.'" } || echo " :" echo "}" ;; esac ;; esac echo "" echo "[ -z \"\$Imagecommand\$Entrypoint\" ] && error 'No image command specified and no CMD or ENTRYPOINT found in image.' && exit 1" echo "" #### create container.CMD.sh #### echo "# create container.CMD.sh (shared with container and given as image command to docker run)" echo "{ echo '#! /bin/sh'" #[ "$Debugmode" = "yes" ] && echo "echo 'set -x'" echo " echo '# created startscript for docker run: container.CMD.sh'" echo " echo '# runs as unprivileged user in container'" echo " echo '$Messagefifofuncs'" echo " echo 'Messagefile=$Cshare/message.fifo'" echo " echo ''" echo " echo '# wait for container setup script do be ready'" echo " echo 'Zeit=\$(date +%s)'" echo " echo 'for Count in $(seq -s' ' 50); do'" echo " echo ' verbose \"Waiting since \$((\$(date +%s) - \$Zeit))s for container setup to be ready\"'" echo " echo ' [ -e /x11docker.setupready ] && break'" echo " echo ' sleep \$(awk \"BEGIN { print \$Count*0.2 }\")'" echo " echo 'done'" echo " echo ''" echo " echo 'verbose -d \"Running unprivileged user commands in container\"'" echo " echo ''" echo ' echo Imagecommand="\"$Imagecommand\""' echo ' echo Entrypoint="\"$Entrypoint\""' echo " echo ''" echo " echo 'verbose \"Container system:'" echo " echo '\$(cat /etc/os-release)\"'" echo " echo ''" echo "} >> $Imagecommandscript" [ "$Switchcontaineruser" = "yes" ] && { echo "echo '# Environment variables found in image:' >> $Imagecommandscript" echo "IFS=$'\n'" echo "while read -r Line; do" echo " echo export \$(escapestring \"\$Line\") >> $Imagecommandscript" echo "done < <($Dockerexe run --rm --entrypoint env $Imagename env 2>>$Containerlogfile| rmcr)" echo "rmcr '$Containerlogfile'" echo "IFS=$' \t\n'" echo "" } echo "{" echo " echo ''" echo " echo '# x11docker variable settings'" echo " echo 'Containeruser=\"\$(cat $Cshare/container.user)\"'" case $Createcontaineruser in yes) echo " echo 'Containeruserhome=\"$Containeruserhome\"'" ;; no) echo " echo 'Containeruserhome=\$(getent passwd \$Containeruser | cut -d: -f6)'" echo " echo 'Containeruserhome=\${Containeruserhome:-/tmp/\$Containeruser}'" ;; esac echo " echo 'export USER=\"\$Containeruser\"'" echo " echo 'export HOME=\"\$Containeruserhome\"'" [ "$Sharehosthome" = "no" ] && echo " echo 'mkdir -p \$Containeruserhome'" echo " echo '# Copy files from /etc/skel into empty HOME'" echo " echo '[ -d /etc/skel ] && {'" echo " echo ' [ \"\$(find \$Containeruserhome -mindepth 1 -print)\" ] || cp -R /etc/skel/. \$Containeruserhome'" echo " echo '}'" [ "$Sharehosthome" = "no" ] && { while read -r Line; do echo " echo '# create soft links of shared folders to fake home'" echo " echo 'ln -s \"$Line\" \"\$Containeruserhome\"'" done < <(store_runoption dump volume) } [ -n "$Newdisplay" ] && echo " echo '[ -e /tmp/.X11-unix/X$Newdisplaynumber ] || ln -s /X$Newdisplaynumber /tmp/.X11-unix'" [ "$Xserver" = "--tty" ] && echo " echo 'unset DISPLAY WAYLAND_DISPLAY XAUTHORITY'" [ "$Dbusrunsession" = "yes" ] && echo " echo 'command -v dbus-run-session >/dev/null && Dbus=dbus-run-session || note \"Option --dbus: dbus seems to be not installed in image\"'" echo " echo 'export NO_AT_BRIDGE=1'" echo " echo ''" echo ' echo "[ -e \"\$XDG_RUNTIME_DIR\" ] || {"' echo " echo ' export XDG_RUNTIME_DIR=/tmp/XDG_RUNTIME_DIR'" echo " echo ' mkdir -m 700 -p \$XDG_RUNTIME_DIR'" echo " echo ' export XDG_RUNTIME_DIR'" echo " echo '}'" echo " echo ''" [ "$Sharewayland" = "yes" ] && { echo " echo '# Wayland environment'" echo " echo 'export WAYLAND_DISPLAY=$Newwaylandsocket'" echo " echo 'ln -s /$Newwaylandsocket \$XDG_RUNTIME_DIR/$Newwaylandsocket'" echo " echo ''" } [ "$Setwaylandenv" = "yes" ] && { for Line in $Waylandtoolkitenv ; do echo " echo 'export $Line'" ; done } || case $Xserver in --weston|--kwin|--hostwayland) echo " echo 'export XDG_SESSION_TYPE=wayland'" ;; --tty) ;; *) echo " echo 'export XDG_SESSION_TYPE=x11'" ;; esac echo " echo ''" [ "$Switchcontaineruser" = "yes" ] && echo " echo 'export TINI_SUBREAPER=1'" echo " echo ''" echo " echo '# xpra environment settings'" echo " echo 'export UBUNTU_MENUPROXY= QT_X11_NO_NATIVE_MENUBAR=1 MWNOCAPTURE=true MWNO_RIT=true MWWM=allwm'" [ -n "$Xprashm" ] && echo " echo 'export $Xprashm'" echo " echo ''" echo " echo 'bash --version >/dev/null 2>&1 && export SHELL=/bin/bash || export SHELL=/bin/sh'" echo " echo 'export TERM=xterm'" echo " echo '[ -e /x11docker.LANG ] && export LANG=\"\$(cat /x11docker.LANG)\"'" echo " echo '[ -e \"$Hostlocaltime\" ] || export TZ=$Hostutctime'" echo " echo '[ \"\$(date -Ihours)\" != \"$(date -Ihours)\" ] && export TZ=$Hostutctime'" echo " echo '[ \"\$DEBIAN_FRONTEND\" = noninteractive ] && unset DEBIAN_FRONTEND'" echo " echo '[ \"\$DEBIAN_FRONTEND\" = newt ] && unset DEBIAN_FRONTEND'" echo " echo '# container environment (--env)'" while read -r Line ; do echo " echo 'export \"$Line\"'" done < <(store_runoption dump env) echo " echo ''" [ "$Xauthentication" = "yes" ] || echo " echo 'unset XAUTHORITY'" echo " echo 'env >> $Cshare/environment'" echo " echo 'sed -i \"/\(PWD=\|_=\)/d\" $Cshare/environment'" echo " echo 'verbose \"Container environment:'" echo " echo '\$(env | sort)\"'" echo " echo 'verbose \"Copy of environment stored in $Cshare/environment\"'" echo " echo ''" echo " echo 'cd \"\$HOME\"'" # HOME as default working directory echo " Workdir=\$($Dockerexe inspect --format='{{.Config.WorkingDir}}' $Imagename 2>>'$Containerlogfile'| rmcr)" # WORKDIR in image echo ' [ "$Workdir" ] && echo "[ -d \"$Workdir\" ] && cd \"$Workdir\" # WORKDIR in image"' [ "$Workdir" = "/tmp" ] || echo " echo 'cd $Workdir'" # x11docker option --workdir echo " echo ''" echo " echo verbose -d \"Running image command: \$Tini \\\$Dbus \$Entrypoint \$Imagecommand\"" case $Interactive in no) echo " echo \"\$Exec \$Tini \\\$Dbus \$Entrypoint \$Imagecommand $( [ "$Forwardstdin" = "yes" ] && echo "<$Cshare/stdin") >>$Cshare/stdout 2>>$Cshare/stderr\"" ;; yes) echo " echo \"\$Exec \$Tini \\\$Dbus \$Entrypoint \$Imagecommand\"" ;; esac echo " echo '# Ready for docker run'" echo "} >> $Imagecommandscript" #### End of container.CMD.sh #### echo "" echo "nl -ba >> $Logfile < $Imagecommandscript" 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 "[ '$Wantcgroup' = 'systemd' ] || $Dockerexe run --rm --entrypoint env $Imagename which elogind >/dev/null && {" 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 "" } } #### run docker image #### [ "$Winsubsystem" = "MSYS2" ] && echo "export MSYS2_ARG_CONV_EXCL='*'" case $Interactive in no) echo "read Containerid < <($Dockercommand 2>>$Containerlogfile | rmcr)" ;; yes) [ "$Winpty" ] && echo "$Winpty bash $Dockercommandfile <&0 &" || echo "$Dockercommand <&0 &" echo "Containerid=$Containername" ;; esac ########################## echo "" echo "verbose -d \"Container ID: \$Containerid\"" echo "[ \"\$Containerid\" ] || { rmcr '$Containerlogfile' error \"Startup of docker failed. Did not receive a container ID. Last lines of container log: \$(tail $Containerlogfile)\" }" echo "echo \$Containerid >> $Containeridfile" [ "$Showcontainerid" = "yes" ] && echo "stdout \"\$(cat $Containeridfile)\"" echo "" echo "# wait for container to be ready" echo "for ((Count=1 ; Count<=20 ; Count++)); do" echo " $Dockerexe exec $Containername sh -c : 2>&1 | rmcr >>$Containerlogfile && { verbose -d 'Container is up and running.' ; break ; } || verbose -d \"Container not ready on \$Count. attempt, trying again.\"" echo " sleep 0.2" echo "done" echo "$Dockerexe logs -f \$Containerid 2>&1 | rmcr >> $Containerlogfile &" echo "Failure=\"\$(cat $Containerlogfile | rmcr | 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\"" echo "}" echo "" echo "$Dockerexe inspect --format '{{ .NetworkSettings.IPAddress }}' $Containername 2>>$Containerlogfile | rmcr >> $Containeripfile" echo "verbose -d \"Container IP: \$(cat $Containeripfile)\"" echo "" echo "Pid1pid=\$($Dockerexe inspect --format '{{.State.Pid}}' $Containername 2>>$Containerlogfile | rmcr)" echo "echo \$Pid1pid >> $Containerpid1pidfile" echo "verbose -d \"Host PID of container PID 1: \$Pid1pid\"" [ "$Showcontainerpid1pid" = "yes" ] && echo "stdout \$Pid1pid" echo "" echo "# get PID of container" echo "Containerpid=\$(ps -o ppid \$Pid1pid 2>/dev/null || echo unknown | tail -n1)" echo "verbose -d \"Container PID: \$Containerpid\"" echo "echo \$Containerpid >> $Containerpidfile" echo "" # container.CMD.sh will wait until setup script is ready [ "$Switchcontaineruser" = "no" ] && echo "$Dockerexe exec --tty -u root $Containername /bin/sh $Cshare/containerrootrc 2>&1 | rmcr >>$Containerlogfile" echo "exit 0" return 0 } >> $Dockerrc create_containerrootrc() { # create container root setup script: This script runs as root in container local Line= echo "#! /bin/sh" echo "# set up docker container as root before ongoing in unprivileged container.CMD.sh" echo "# commands in this script are executed as root in container" #[ "$Debugmode" = "yes" ] && echo "set -x" echo "$Messagefifofuncs" echo "Messagefile=$Cshare/message.fifo" echo "" echo "verbose -d 'Running setup as root in container'" 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 'verbose -d "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=$Cshare/Xclientcookie" [ "$Xoverip" = "no" ] && { echo "[ -e /X$Newdisplaynumber ] && ln -s /X$Newdisplaynumber $Newxsocket" # done again in container.CMD.sh. At least x11docker/deepin needs it here already. echo "ls -l /X$Newdisplaynumber" echo "ls -l $Newxsocket" } echo "" echo "# time zone" [ "$Hostlocaltime" ] && { echo '[ ! -d /usr/share/zoneinfo ] && [ "$Containerlibc" = "'$Hostlibc'" ] && {' echo " mkdir -p $(dirname $Hostlocaltime)" echo " cp '$Cshare/timezone' '$Hostlocaltime'" echo "}" echo "[ -e '$Hostlocaltime' ] && ln -f -s '$Hostlocaltime' /etc/localtime" echo "" } echo "Containersystem=\$(. /etc/os-release; echo \$ID)" 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=\$(cat $Cshare/container.user)" echo "" case $Createcontaineruser in yes) # create container user echo "# create user entry in /etc/passwd (and delete possibly existing same uid)" echo "getent passwd | grep -v \"\$(getent passwd $Containeruseruid || echo USERNOTFOUND)\" > /tmp/passwd" echo "# disable possible /etc/shadow passwords for other users" echo "sed -i s%:x:%:-:% /tmp/passwd" echo "echo '$Containeruser:x:$Containeruseruid:$Containerusergid:$Containeruser,,,:$Containeruserhome:/bin/sh' >> /tmp/passwd" echo "rm /etc/passwd" echo "mv /tmp/passwd /etc/passwd || warning 'Unable to change /etc/passwd. That may be a seurity 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 "" echo "# create user group entry (and delete possibly existing same gid)" echo "getent group | grep -v \$(getent group $Containerusergid || echo USERNOTFOUND) > /tmp/group" echo "echo $Containerusergroup:x:$Containerusergid: >> /tmp/group" echo "mv /tmp/group /etc/group" echo "" [ "$Sharehosthome" = "no" ] && echo "mkdir -v -m 777 /fakehome" 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 'root ALL=(ALL) ALL' >> /etc/sudoers" [ "$Sudouser" = "yes" ] && echo "echo '$Containeruser ALL=(ALL) ALL' >> /etc/sudoers" echo "" # disable possible custom PAM setups that could allow root in container [ "$Sudouser" = "no" ] && { echo "# restrict PAM configuration of su and sudo" 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 "[ -e /etc/pam.d/sudo ] && rm -v /etc/pam.d/sudo" echo "" } ;; no) # check container user home. Can miss with --user=RETAIN echo "Containeruserhome=\$(getent passwd \$Containeruser | cut -d: -f6)" echo "Containeruserhome=\${Containeruserhome:-/tmp/\$Containeruser}" echo "" echo "debugnote \"User in container: \$(id \$Containeruser) \$(getent passwd \$Containeruser)\"" ;; esac echo "[ -e '$Containeruserhome' ] || {" echo " # only create HOME here if capability CHOWN is allowed." echo " :>/tmp/chowntestfile" echo " chown \$Containeruser /tmp/chowntestfile && {" echo " mkdir -v -p '$Containeruserhome'" echo " chown \$Containeruser '$Containeruserhome'" echo " }" echo " rm /tmp/chowntestfile" echo "}" echo "" echo "# Set up container user groups" for Line in $Containerusergroups ; do echo "Groupname=$(getent group $Line | cut -d: -f1)" echo "Groupid=$(getent group $Line | cut -d: -f3)" echo "[ \"\$Groupname\" ] || Groupname=\$(getent group $Line | cut -d: -f1)" echo "[ \"\$Groupid\" ] || Groupid=\$(getent group $Line | cut -d: -f3)" echo "[ \"\$Groupname\" ] && {" echo " getent group | sed \"s/^\$Groupname.*/\$Groupname:x:\$Groupid:\$(getent group \$Groupname | cut -d: -f4 ),\$Containeruser/\" | sed 's/:,/:/' > /tmp/group" echo " getent group \$Groupname >/dev/null || echo \$Groupname:x:\$Groupid:\$Containeruser >> /tmp/group" echo " cp /tmp/group /etc/group" echo "}" echo "" done # --gpu with closed source nvidia driver [ "$Nvidiadriver" ] && { 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" echo " based on musl libc like Alpine is not possible due to" echo " 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 " 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 " env TMPDIR=/tmp2 PATH=/tmp2:\$PATH sh $Cshare/NVIDIA-$Nvidiaversion.run -A | grep -q 'install-libglvnd' && Libglvnd='--install-libglvnd'" echo " env TMPDIR=/tmp2 PATH=/tmp2:\$PATH sh $Cshare/NVIDIA-$Nvidiaversion.run -A | grep -q 'no-nvidia-modprobe' && Nvidiamodprobe='--no-nvidia-modprobe'" echo " env TMPDIR=/tmp2 PATH=/tmp2:\$PATH sh $Cshare/NVIDIA-$Nvidiaversion.run --tmpdir /tmp2 \$Libglvnd \ --accept-license --no-runlevel-check --no-questions --no-backup --ui=none \ --no-kernel-module --no-kernel-module-source --no-nouveau-check \$Nvidiamodprobe" echo " rm -R /tmp2 && unset TMPDIR" echo " } || note 'Skipping installation of $Cshare/NVIDIA-$Nvidiaversion.run'" echo " ;;" echo "esac" echo "" } [ "$Switchcontaineruser" = "yes" ] && { echo "echo \"#! /bin/sh # Need user switch from root to unprivileged user. # Additionally, su triggers logind and elogind. # Issue: shell has no job control in --interactive mode. exec su - \$Containeruser $Cshare/container.CMD.sh \" >/usr/local/bin/x11docker-login" echo "chmod +x /usr/local/bin/x11docker-login" echo "echo \"#! /bin/sh exec setsid agetty -a \$Containeruser -l /usr/local/bin/x11docker-login console #agetty console ### no job control in shell even with /bin/login? WTF? \" >/usr/local/bin/x11docker-agetty" echo "chmod +x /usr/local/bin/x11docker-agetty" } case $Initsystem in tini|none) ;; systemd) echo "# enable x11docker CMD service" echo "systemctl enable x11docker-startcmd.service" echo "systemctl enable x11docker-journal.service" echo "# to be sure, allow/unmask most important services" echo "systemctl unmask systemd-logind dbus" echo "# disable cgproxy.service, can cause failing startup." echo "systemctl mask cgproxy" echo "# disable useless plymouth" echo "systemctl mask plymouth-start plymouth-read-write plymouth-quit plymouth-quit-wait" echo "# disable useless NetworkManager" echo "systemctl mask NetworkManager" echo "# disable deepin update service" echo "systemctl mask lastore-daemon lastore-update-metadata-info" ;; runit) echo "# create and enable x11docker service containing image command" #echo "echo 'FAKE_SHELL /bin/bash' >> /etc/login.defs" echo "mkdir -p /etc/sv/x11docker" echo "echo \"#! /bin/sh 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' ] && sleep 0.2 || break done } } # make stderr visible exec 2>&1 # wait for all other services for Service in \\\$(ls /etc/runit/runsvdir/default/) ; do waitforservice \\\$Service ;done echo 'Current status of runit services:' for Service in \\\$(ls /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 \"#! /bin/sh sv down x11docker runit-init 0 \" > /etc/sv/x11docker/finish" echo "chmod +x /etc/sv/x11docker/finish" echo "ln -s /etc/sv/x11docker /etc/runit/runsvdir/default" echo "verbose 'DBus: enabling dbus service'" echo "ln -s /etc/sv/dbus /etc/runit/runsvdir/default" ;; openrc) echo "# create and enable x11docker service containing image command" echo "printf \"#!/sbin/openrc-run name=x11docker depend() { after * } start() { ebegin 'Starting container.CMD.sh' /usr/local/bin/x11docker-login openrc-shutdown --poweroff shutdown halt eend \$? } \" > /etc/init.d/x11docker.service" echo "chmod +x /etc/init.d/x11docker.service" echo "rc-update add x11docker.service default" echo "verbose 'DBus: enabling dbus service'" echo "rc-update add dbus default" echo "# tell openrc that it runs in docker" echo "sed -e 's/#rc_sys=\"\"/rc_sys=\"docker\"/g' -i /etc/rc.conf" ;; sysvinit) echo "# adding x11docker start command to rc.local" echo "sed -i '/exit 0/d' /etc/rc.local" echo "echo \"/usr/local/bin/x11docker-login || echo \\\"x11docker: Exit code of x11docker-login: \\\$?\\\" echo 'x11docker: rc.local sends shutdown -h now' shutdown -h now exit 0\" >> /etc/rc.local" echo "chmod +x /etc/rc.local" ;; esac echo "# disable getty in inittab" echo "[ -e /etc/inittab ] && sed -i 's/.*getty/##getty disabled by x11docker## \0/' /etc/inittab" # start dbus daemon and dbus services case $Dbussystem in yes) echo "verbose 'DBus: removing failing or useless services.'" echo "for Line in \$(find /usr/share/dbus-1/system-services /usr/share/dbus-1/services -type f); do" echo ' Name="$(cat $Line | grep Name= | cut -d= -f2)"' echo ' Command="$(cat $Line | grep Exec= | cut -d= -f2)"' echo ' debugnote "Checking $Name: $Command"' echo ' echo "Checking $Name: $Command"' echo " case \$Name in" echo " org.bluez)" echo " rm -v \$Line" echo " ;;" echo " org.freedesktop.UPower|org.freedesktop.ConsoleKit|org.freedesktop.resolve1|org.freedesktop.network1|org.freedesktop.timedate1)" echo " rm -v \$Line" echo " ;;" echo " org.freedesktop.systemd1) " [ "$Initsystem" = "systemd" ] || echo " rm -v \$Line" #echo ' debugnote "$(cat $Line)"' echo " ;;" echo " org.freedesktop.login1)" echo ' # check for loginservice, store it to run it later' echo ' verbose -d "Found login service $Name: $Command"' echo ' Loginservice=$Name' echo ' Loginservicecommand=$Command' [ "$Sharecgroup" = "no" ] && { echo " rm -v \$Line" echo ' echo $Command | grep -q elogind && {' echo ' note "Found login service elogind.' echo ' If you want to use it, enable option --sharecgroup."' echo ' Loginservice=' echo ' Loginservicecommand=' echo ' }' echo ' echo $Command | grep -q "systemd-logind" && {' echo ' Loginservice=' echo ' Loginservicecommand=' echo ' }' } [ "$Initsystem" = "systemd" ] || { echo ' echo $Command | grep -q "systemd-logind" && {' echo ' Loginservice=' echo ' Loginservicecommand=' echo ' }' } echo " ;;" echo " org.freedesktop.hostname1|org.freedesktop.locale1)" [ "$Initsystem" = "systemd" ] || { echo " # Service somehow kills container after some time if container runs without systemd. wtf. Just deleting this:" echo " rm -v \$Line" } echo " ;;" echo " org.opensuse.CupsPkHelper.Mechanism)" echo " rm -v \$Line" echo " ;;" echo " com.deepin.lastore|com.deepin.dde.Welcome|com.deepin.daemon.Bluetooth|com.deepin.daemon.Grub2|com.deepin.daemon.Network|com.deepin.daemon.Timedate|com.deepin.daemon.Power|com.deepin.daemon.Audio|com.deepin.daemon.InputDevices)" echo " # deepin services" echo " rm -v \$Line" echo " ;;" echo " esac" echo "done" ;; esac [ "$Dbussystem" = "yes" ] && case $Initsystem in tini|none) warning "Will run DBus system services as root in container. If the image contains malicious applications started by DBus service files, they might abuse their root privileges and may try to break out of container." note "Option --dbus-system can have a long 90s delay in some setups. In that case install systemd in image and run with --systemd. (Also --runit, --openrc or --sysvinit can be set up and used)." ### FIXME echo "echo 'x11docker: DBus: starting dbus system daemon'" echo "dbus-daemon --system --fork" echo "for Count in 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0; do" echo " [ -e /run/dbus/system_bus_socket ] && break" echo " sleep 0.2" echo "done" echo "[ -e /run/dbus/system_bus_socket ] || warning 'DBus startup seems to have failed. /run/dbus/system_bus_socket not found.'" [ "$Nonewprivileges" = "yes" ] && { echo "# support for dbus daemon with no-new-privileges" echo "waitforservice() {" echo " # wait for dbus service \$1 to appear in dbus service list" echo " for Count in 1 2 3 4 5 6 7 8 9 10 ; do" echo " dbus-send --system --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep -q \$1 && break" echo " verbose -d \"DBus: waiting for \$1 to be ready\"" echo " sleep 0.1" echo " done" echo "}" echo '[ "$Loginservice" ] && {' echo ' verbose -d "Starting login service $Loginservicecommand"' echo ' command -v $Loginservicecommand && {' echo ' eval $Loginservicecommand &' echo ' } || {' echo ' echo $Loginservicecommand | grep -q elogind && elogind &' echo ' }' echo ' waitforservice $Loginservice' echo '}' echo "# start polkitd as first service" echo "[ -x /usr/lib/policykit-1/polkitd ] && {" echo " verbose -d 'DBus: starting PolicyKit'" echo " /usr/lib/policykit-1/polkitd &" echo " waitforservice org.freedesktop.PolicyKit1" echo "}" echo "[ -x /usr/lib/accountsservice/accounts-daemon ] && {" echo " verbose -d 'DBus: starting Accounts daemon'" echo " /usr/lib/accountsservice/accounts-daemon &" echo " waitforservice org.freedesktop.Accounts" echo "}" echo "# start all other dbus system services" echo "for Line in \$(find /usr/share/dbus-1/system-services -type f); do" echo " Name=\"\$(cat \$Line | grep Name= | cut -d= -f2)\"" echo " Command=\"\$(cat \$Line | grep Exec= | cut -d= -f2)\"" echo " case \$Name in" echo " org.freedesktop.PolicyKit1|org.freedesktop.Accounts) ;;" echo " org.freedesktop.login1) ;;" echo ' *)' echo ' verbose -d "DBus: starting service $Name: $Command"' echo ' $Command &' echo ' [ "$Command" != "/bin/false" ] && waitforservice $Name' echo ' ;;' echo ' esac' echo 'done' } echo "verbose -d \"DBus: Running system services: \$(dbus-send --system \ --dest=org.freedesktop.DBus \ --type=method_call \ --print-reply \ /org/freedesktop/DBus \ org.freedesktop.DBus.ListNames)\"" ;; esac # --lang: language locale [ "$Langwunsch" ] && { echo "verbose \"Searching for language locale matching $Langwunsch\"" 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 $Langwunsch | cut -d. -f1)" 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 $Langwunsch && {" echo " Langland=\$(echo \"\$Langall\" | grep -i -m1 $Langwunsch)" echo " }" echo " }" echo "}" echo "Langland=\$(echo \$Langland | cut -d_ -f1 | tr '[:upper:]' '[:lower:]')_\$(echo \$Langland | cut -d_ -f2 | tr '[:lower:]' '[:upper:]')" echo "echo \"\$Locales\" | grep -q \$Langland.UTF-8 && Langcontainer=\$Langland.UTF-8" echo "echo \"\$Locales\" | grep -q \$Langland.utf8 && Langcontainer=\$Langland.utf8" echo "echo $Langwunsch | grep -E 'C|C.UTF-8|C.utf8|POSIX' >/dev/null && Langcontainer=$Langwunsch" echo "[ -z \"\$Langcontainer\" ] && {" echo " [ -e /usr/share/i18n/SUPPORTED ] || warning \"/usr/share/i18n/SUPPORTED not found. Please install locale package in image (belongs to glibc)\"" echo " Langcontainer=\$Langland.utf8" echo " verbose \"Creating language locale \$Langcontainer\"" echo " command -v localedef >/dev/null || note 'Command localedef not found in image. Need it for language locale creation (--lang).'" echo " localedef --verbose --force -i \$Langland -f UTF-8 \$Langcontainer || verbose \"localedef exit code: \$?\"" echo " locale -a | grep -q \"\$Langcontainer\" || {" echo " warning \"Locale creation of \$Langcontainer failed.\"" echo " Langcontainer=''" echo " }" echo "} || {" echo " verbose \"Found locale in image: \$Langcontainer\"" echo "}" echo "[ \"\$Langcontainer\" ] && {" echo " echo \"\$Langcontainer\" > /x11docker.LANG" echo " echo \"LANG=\$Langcontainer\" > /etc/default/locale" echo "} || warning 'Desired locale for '--lang=$Langwunsch' not found and not created.'" echo "verbose \"Output of locale -a:" echo "\$(locale -a)\"" echo "" } # --runasroot command added here [ "$Runasroot" ] && { echo "# custom setup root command added with option --runasroot" echo "$Runasroot" } # message to container.CMD.sh echo "echo 'x11docker: Container root setup is ready'" echo ":> /x11docker.setupready" [ "$Switchcontaineruser" = "yes" ] && { # if "no", container.CMD.sh is executed in command line $Dockercommand case $Initsystem in none) echo "/bin/su - -s /bin/sh \$Containeruser $Cshare/container.CMD.sh" ;; tini) echo "Exec=exec" [ "$Sharecgroup" = "yes" ] && echo '[ "$Loginservice" ] && Exec=' echo "\$Exec /bin/su - -s /bin/sh \$Containeruser $Cshare/container.CMD.sh" ;; runit|openrc|sysvinit) echo "{ read Dummy <$Cshare/timetosaygoodbye.fifo echo timetosaygoodbye >$Cshare/timetosaygoodbye.fifo echo 'x11docker: $Initsystem shutdown now' shutdown now halt } &" echo "exec /sbin/init" ;; 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 --systemd)."' echo '}' echo 'exec $Systemd' ;; esac } return 0 } >> $Containerrootrc create_xtermrc() { # create xtermrc: script to prompt for password and to run dockerrc echo "#! /usr/bin/env bash" #[ "$Debugmode" = "yes" ] && echo "set -x" echo "touchxtermready() {" echo " $Mksu 'touch $Cachefolder/xtermready'" echo " trap - EXIT" echo " exit" echo "}" echo "trap touchxtermready EXIT" echo "export TERM=xterm SHELL=/bin/bash" [ "$Passwordneeded" = "yes" ] && case $Passwordfrontend in su|sudo) echo "echo 'x11docker $Imagename $Imagecommand:'" echo "echo 'Please type in your password to run docker on display $Newdisplay'" echo "echo -n 'Password ($Passwordfrontend): '" ;; esac case $Passwordfrontend in gksudo|lxsudo) echo "$Passwordcommand bash $Dockerrc" ;; pkexec) echo "pkexec env DISPLAY=\$DISPLAY XAUTHORITY=\$XAUTHORITY bash $Dockerrc" ;; gksu) echo "$Passwordcommand \"bash $Dockerrc \" 2>/dev/null" ;; *) echo "$Passwordcommand \"${Sudo}bash $Dockerrc \"" ;; esac echo "exit" return 0 } >> $Xtermrc #### final startup routines waitfor_xserver() { # wait for X server to be ready local Zeit=$(date +%s) Count=0 Dauer=0 local Compositorpid= Xinitpid= Xserverpid= # wait for X server to be ready (sign is creation of $Cachefolder/Xready in xinitrc) debugnote "Waiting for X server $Xserver to be ready." while [ ! -e "$Cachefolder/Xready" ] ; do Count=$(( Count + 1 )) Dauer=$(( $(date +%s) - $Zeit )) sleep $(awk "BEGIN { print $Count * 0.1 }") [ $Dauer -gt 60 ] && error "X server $Xserver not ready after 60s." verbose "Waiting since ${Dauer}s for $Xserver to be ready." rocknroll || { verbose -d "Stopped waiting for $Xserver due to terminating signal." ; break ; } grep -q -E 'xinit: giving up|unable to connect to X server|Connection refused|server error|Only console users are allowed' <$Xinitlogfile && error "Error during startup of X server $Xserver. Last lines of xinit log: $(tail $Xinitlogfile) $( [ -s "$Compositorlogfile" ] && echo "Last lines of compositor log: $(tail $Compositorlogfile)")" done verbose -d "$Xserver is ready" # check and watch compositor [ "$Compositorcommand" ] && { Compositorpid=$(cat $Compositorpidfile) checkpid $Compositorpid && setonwatchpidlist $Compositorpid compositor || error "Startup of compositor failed. Last lines of compositor log: $(tail $Compositorlogfile)" storepid $Compositorpid compositor } # check and watch X server case $Xserver in --tty|--hostdisplay|--hostwayland|--weston|--kwin|--vcxsrv) ;; *) Xinitpid="$(pgrep -a xinit 2>/dev/null | grep "xinit $Xinitrc" | awk '{print $1}')" checkpid "$Xinitpid" && setonwatchpidlist $Xinitpid xinit && storepid $Xinitpid xinit echo $Xcommand | grep -q Xorgwrapper && Line="Xorg $Newdisplay" || Line="$(echo "$Xcommand" | head -n1)" Xserverpid=$(ps aux | grep "$(echo "${Line:-nothingtolookfor}" | cut -d' ' -f1-2)" | grep -v grep | grep -v xinit | awk '{print $2}') checkpid "$Xserverpid" && setonwatchpidlist $Xserverpid Xserver && storepid $Xserverpid Xserver ;; esac return 0 } start_xpra() { # options --xpra / --xpra-xwayland: start and watch xpra server and xpra client local Xpracrashcount= Xpraserverpid= Xpraclientpid= case $Xserver in --xpra|--xpra-xwayland) notify-send 'x11docker: stay tuned, xpra will start soon.' 2>/dev/null & note 'Stay tuned, xpra will start soon.' # loop to keep xpra running. # xpra server can crash with some applications like atom editor, --mmap=no fixes that. # xpra client can be disconnected after a tty switch, will be restarted in that case. Xpracrashcount="0" while rocknroll ; do # xpra server checkpid $Xpraserverpid || { [ -n "$Xpraserverpid" ] && { Xpracrashcount=$((Xpracrashcount + 1)) [ "$Xpracrashcount" -eq 1 ] && { warning "xpra server has crashed. x11docker will add xpra option --mmap=no to hopefully fix that issue. xpra will need more CPU power now, but will run more stable. Last lines of xpra server log: $(tail $Xpraserverlogfile)" Xpraservercommand="$Xpraservercommand --mmap=no" Xpraclientcommand="$Xpraclientcommand --mmap=no" } [ "$Xpracrashcount" -gt 1 ] && error "Xpra server has crashed again. x11docker stops now. Try out other X server options like --nxagent, --xephyr or --hostdisplay." notify-send "x11docker: xpra server crashed. Restarting xpra now." mkfile $Xpraserverlogfile # clear logfile to remove "xpra is ready" message } verbose -d "Starting Xpra server" $Mksu "env $Newxenv XPRA_OPENGL_DOUBLE_BUFFERED=1 $Xpraservercommand" >> $Xpraserverlogfile 2>&1 & Xpraserverpid=$! && storepid $Xpraserverpid xpraserver waitforlogentry "xpra server" $Xpraserverlogfile 'xpra is ready' && verbose "Xpra server is ready" || error "Xpra server startup failed. Last lines of Xpra server log: $(tail $Xpraserverlogfile)" } # xpra client checkpid $Xpraclientpid && kill $Xpraclientpid verbose -d "Starting Xpra client" [ -n "$Xpraclientpid" ] && note "Restarting Xpra client." $Mksu "env $Hostxenv XPRA_PADDING_COLORS='0,0.2,1' $Xpraclientcommand" >> $Xpraclientlogfile 2>&1 & Xpraclientpid=$! && storepid $Xpraclientpid xpraclient checkpid $Xpraclientpid && mywatch "ps -p $Xpraserverpid -o pid ; ps -p $Xpraclientpid -o pid #xpra" tail $Xpraserverlogfile | grep -q "client ping timeout" || saygoodbye xpraclient done & storepid $! xpraloop ;; esac return 0 } start_docker() { # start docker container local Containerpid= Containerid= Containerpid1= # start docker in xtermrc export $Terminalxenv case $Passwordfrontend in su|sudo) [ "$Passwordterminal" = "bash -c" ] && Passwordterminal="" #[ -z "$Passwordterminal" ] && exec &1 ||:)" return 0 } start_hostexe() { # options --exe, --xonly: Run host executeable instead of docker container local Hostexepid= # create start script { echo "#! /usr/bin/env bash" #[ "$Debugmode" = "yes" ] && echo "set -Eux" echo "storepid() {" echo " echo \${1:-} \${2:-} >> $Bgpidfile" echo "}" [ "$Dbusrunsession" = "yes" ] && command -v dbus-run-session >/dev/null && echo "Dbus=dbus-run-session" || Dbus= echo "export $Newxenv" [ "$Sharehosthome" = "yes" ] && { echo "export HOME='$Containeruserhosthome'" echo "cd '$Containeruserhosthome'" } [ "$Workdir" = "/tmp" ] || echo "cd '$Workdir'" 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 "\$Dbus $Hostexe $( [ "$Forwardstdin" = "yes" ] && echo "<$Cmdstdinfile") >>$Cmdstdoutlogfile 2>>$Cmdstderrlogfile &" echo "storepid \$! hostexe" } >> $Imagecommandscript nl -ba <$Imagecommandscript >> $Containerlogfile # run start script $Mksu "bash $Imagecommandscript" # check whether startup was successfull sleep 1 # wait for possible startup failure Hostexepid=$(grep hostexe <$Bgpidfile | cut -d' ' -f1) checkpid $Hostexepid || warning "Application '$Hostexe' terminated fast or failed at all, or the process forked itself and cannot be supervised. Last lines of application output: $(tail $Cmdstdoutlogfile $Cmdstderrlogfile)" [ "$Showcontainerid" = "yes" ] && echo "" # --showid: empty here, no container ID [ "$Showcontainerpid1pid" = "yes" ] && echo $Hostexepid # --showpid1 echo $Hostexepid >>$Containerpid1pidfile storepid $Hostexepid hostexe setonwatchpidlist $Hostexepid hostexe sleep 1 && debugnote "Process tree of $Hostexe: $(pstree -p $Hostexepid 2>&1 ||:)" return 0 } start_pulseaudiotcp() { # option --pulseaudio=tcp: load Pulseaudio TCP module (authenticated with container IP) case $Winsubsystem in "") $Mksu "pactl load-module module-native-protocol-tcp port=$Pulseaudioport auth-ip-acl=${Containerip:-"127.0.0.1"} 2>&1" >>$Messagelogfile ;; *) env XDG_RUNTIME_DIR="" HOME="" cmd.exe /C "C:/cygwin64/bin/pactl.exe load-module module-native-protocol-tcp port=$Pulseaudioport auth-ip-acl=${Containerip:-$Hostip} 2>&1" 2>&1 >>$Messagelogfile ;; esac return 0 } start_compositor() { # start Wayland compositor Weston or KWin local Compositorkeyword= Dbuslaunch= command -v dbus-launch >/dev/null && Dbuslaunch=dbus-launch case $Xserver in --weston|--weston-xwayland|--xpra-xwayland|--xdummy-xwayland) Compositorkeyword="weston-desktop-shell" ;; --kwin|--kwin-xwayland) Compositorkeyword="X-Server" ;; esac $Mksu "exec $Dbuslaunch $Compositorcommand >> $Compositorlogfile 2>&1 & echo \$! >>$Compositorpidfile" waitforlogentry compositor $Compositorlogfile "$Compositorkeyword" || error "Startup of Wayland compositor failed. Can not run $Xserver. Last lines of compositor log: $(tail $Compositorlogfile)" case $Xserver in --xpra-xwayland|--xdummy-xwayland) # hide weston window $Mksu "xdotool windowunmap 0x$(printf '%x\n' $(grep 'window id' $Compositorlogfile | rev | cut -d' ' -f1 | rev))" ;; esac return 0 } start_xserver() { # start X server local Xserverpid= Tasklistnew= Tasklistold= case $Xserver in --xpra|--xephyr|--xdummy|--xvfb|--xwayland|--nxagent|--weston-xwayland|--kwin-xwayland|--xpra-xwayland|--xdummy-xwayland) $Mksu "exec env WAYLAND_DISPLAY=$Newwaylandsocket xinit $Xinitrc -- $Xcommand >> $Xinitlogfile 2>&1 " ;; --xwin) Xcommand="$(echo $Xcommand | sed 's/\\//g')" ; xinit $Xinitrc -- $Xcommand >> $Xinitlogfile 2>&1 ;; --xorg) case $Xlegacywrapper in yes) $Mksu "exec xinit $Xinitrc -- $Xcommand >> $Xinitlogfile 2>&1 " ;; no) bash -c "exec xinit $Xinitrc -- $Xcommand >> $Xinitlogfile 2>&1 " ;; esac ;; --hostdisplay|--hostwayland|--weston|--kwin|--tty) $Mksu "exec bash $Xinitrc >> $Xinitlogfile 2>&1 " ;; --vcxsrv) [ "$Winsubsystem" = "WSL" ] && Tasklistold="$(tasklist.exe | rmcr | grep vcxsrv.exe | awk '{print $2}')" $Mksu "exec env MSYS2_ARG_CONV_EXCL='*' $Xcommand >> $Xinitlogfile 2>&1 & echo \$! >$Cachefolder/Xserverpid" Xserverpid=$(cat "$Cachefolder/Xserverpid") [ "$Xserverpid" ] && { storepid $Xserverpid Xserver setonwatchpidlist $Xserverpid Xserver } || error "Failed to receive PID of X server." # Wait for VcXsrv to accept connections for Count in $(seq -s' ' 25); do sleep 0.2 verbose "$Count. access check for $Xserver" grep -q -E 'Authorization|Absolute' <<< "$(xwininfo.exe -display $Newdisplay -root 2>&1)" && break done # bug in WSL: WSL does not terminate Windows process on TERM for processes started in bash. Workaround to get Windows pid. [ "$Winsubsystem" = "WSL" ] && { Tasklistnew="$(tasklist.exe | rmcr | grep vcxsrv.exe | awk '{print $2}')" Winpidlist="$Winpidlist $(echo "$Tasklistold $Tasklistnew" | sort | uniq -u)" verbose -d "Stored Windows pids: $Winpidlist" } $Mksu "exec bash $Xinitrc >> $Xinitlogfile 2>&1 " ;; esac [ $? != 0 ] && rocknroll && warning "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_host() { # check host environment Hostsystem="$(source /etc/os-release 2>/dev/null; echo ${ID:-})" # Check libc from host. If same as in container, it is possible to share timezone file Hostlibc="unknown" # check libc:glibc, musl or others. Needed to eventually provide time zone file. 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 Hostlocaltime="$(realpath /etc/localtime)" # find time zone file in /usr/share/zoneinfo Hostutctime=$(date +%:::z) # offset of UTC 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 uname -r | grep -q Microsoft && Winsubsystem="WSL" command -v cygcheck.exe >/dev/null && { cygcheck.exe -V | rmcr | grep -q "(cygwin)" && Winsubsystem="CYGWIN" cygcheck.exe -V | rmcr | grep -q "(msys)" && Winsubsystem="MSYS2" } case $Winsubsystem in MSYS2|CYGWIN) Winsubmount="$(cygpath.exe -u "c:/" | rmcr | sed s%/c/%%)" Winsubpath="$(convertpath --unix "$(cygpath.exe -w "/" | rmcr)" )" ;; WSL) Winsubmount="/mnt" Winsubpath="$(convertpath --unix "$(getwslpath)")" ;; esac [ "$Winsubsystem" ] && { export PATH="$PATH:$(convertpath --subsystem "C:/Program Files/docker"):$(convertpath --subsystem "C:/Program Files/Docker/Docker/resources/bin")" Dockerexe="docker.exe" Hostsystem="Windows-$Winsubsystem" } # Check host IP. Needed for --pulseaudio=tcp, --vcxsrv and --xwin case $Winsubsystem in "") Hostip="$(ip -4 -o a | grep 'docker0' | awk '{print $4}' | cut -d/ -f1)" [ "$Hostip" ] || Hostip="$(ip -4 -o a | awk '{print $4}' | cut -d/ -f1 | grep -v 127.0.0.1 | head -n1)" ;; *) Hostip="$(ipconfig.exe | rmcr | grep -A6 'DockerNAT' | grep 'IPv4' | rev | cut -d' ' -f1 | rev)" [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*' | grep "^10\.0\.*" )" [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | head -n1 | | rev | cut -d' ' -f1 | rev)" ;; esac # Provide dos->unix newline converter to $Mksu commands export -f rmcr # Check whether x11docker runs over SSH pstree -ps $$ >/dev/null 2>&1 && { pstree -ps $$ | grep -q sshd && Hostssh="yes" || Hostssh="no" } || { check_parent_sshd "$$" && Hostssh="yes" || Hostssh="no" } # Check whether x11docker runs on X or on tty tty | grep -q tty && Hosttty="yes" || Hosttty="no" [ "$Winsubsystem" ] && Hosttty="no" XDG_VTNR=${XDG_VTNR:-} # Check whether x11docker runs in an interactive terminal tty >/dev/null 2>&1 && Runsinterminal="yes" || Runsinterminal="no" # Check whether ps can watch processes of other users mount | grep "^proc" | grep -q "hidepid=2" && { Hosthidepid="yes" verbose -d "/proc is mounted with hidepid=2." } || { Hosthidepid="no" } ps aux | cut -d' ' -f1 | grep -q root && { Hostcanwatchroot="yes" } || { Hostcanwatchroot="no" [ "$Winsubsystem" ] && Hostcanwatchroot="yes" || verbose -d "ps can not watch root processes." } # Check if host uses proprietary NVIDIA driver [ -e "/proc/driver/nvidia" ] && Hostnvidia="yes" || Hostnvidia="no" return 0 } check_hostuser() { # check for unprivileged host user # check host user, want an unprivileged one to run X server # default behaviour: # x11docker started as unprivileged user: starting X server as this user and create same user in container # x11docker started as root: determine real user with $(logname), instead of root use real user like above # x11docker started as root with --hostuser=root: root runs X server and root is container user (discouraged) # if you want root in container, just use --user=root # x11docker with --user=someuser container user is someuser, host user is unprivileged user $(logname) # # root permissions are only needed to run docker. If started unprivileged, a password prompt appears. # user who started x11docker Startuser="$(id -un)" # not root? Use current user. [ -z "$Hostuser" ] && [ "$Startuser" != "root" ] && Hostuser="$Startuser" # root? find unprivileged user Lognameuser="$(logname 2>/dev/null)" [ -z "$Lognameuser" ] && [ -z "$Hostuser" ] && note "Your terminal seems to be not POSIX compliant. Command 'logname' does not return a value. Consider to use another terminal emulator. Fallback: Will try to check \$SUDO_USER and \$PKEXEC_UID." [ -z "$Lognameuser" ] && [ -n "${SUDO_USER:-}" ] && Lognameuser="${SUDO_USER:-}" && [ -z "$Hostuser" ] && note "Will use \$SUDO_USER = ${SUDO_USER:-} as host user." [ -z "$Lognameuser" ] && [ -n "${PKEXEC_UID:-}" ] && Lognameuser="${PKEXEC_UID:-}" && [ -z "$Hostuser" ] && note "Will use user with uid \$PKEXEC_UID = ${PKEXEC_UID:-} as host user." [ -z "$Lognameuser" ] && Lognameuser="$Startuser" && [ -z "$Hostuser" ] && note "Will use \$(id -un) = $Lognameuser as host user." # option --hostuser [ -z "$Hostuser" ] && Hostuser=$Lognameuser [ "$Hostuser" != "$Lognameuser" ] && { [ "$Startuser" = "root" ] || error "x11docker must run as root to choose a host user different from user '$Lognameuser'. (option --hostuser)" } getent passwd $Hostuser >/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) Hostuserhome=$(getent passwd $Hostuser 2>/dev/null | cut -d: -f6) [ -z "$Hostuserhome" ] && { warning "Could not read your home directory from /etc/passwd. Will try HOME=${HOME:-} instead." Hostuserhome=${HOME:-} } [ -z "$Hostuserhome" ] && { warning "Please set \$HOME with a valid path. Fallback: setting HOME=/tmp" Hostuserhome="/tmp" } # Mksu: prefix to run command as unprivileged host user [ "$Hostuser" = "$Startuser" ] && Mksu="bash -c" || Mksu="su $Hostuser -c" # differenciated as only root can use su on itself and others without password [ "$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." [ -z "$Hostuserhome" ] && error "No home directory found for user '$Hostuser'. You need to specify option --cachedir. (Also specify --homedir if you want to use option --home)." id | grep -q "(docker)" && warning "User $Hostuser is member of group docker. That allows unprivileged processes on host to gain root privileges." 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" } # 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" [ "$Hostssh" = "yes" ] && [ -e "$Hostuserhome/.Xauthority" ] && XAUTHORITY="$Hostuserhome/.Xauthority" [ "${XAUTHORITY:-}" ] && { $Mksu "$Xauthexe -i -f ${XAUTHORITY:-} nlist $Hostdisplay 2>/dev/null | rmcr | $Xauthexe -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." return 0 } create_cachefiles() { # create empty cache files owned by unprivileged user # create base cache folder [ "$Cachebasefolder" ] || { case $Winsubsystem in "") Cachebasefolder="$Hostuserhome/.cache/x11docker" ;; *) Cachebasefolder="$(convertpath --subsystem "$(cmd.exe /C "echo %userprofile%")" | rmcr)/x11docker/cache" ;; esac } Cachebasefolder="$(convertpath --subsystem "$Cachebasefolder")" $Mksu "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" # can happen with --cachebasedir Logbackup="$Cachebasefolder/x11docker.log" # afterward copy of $Logfile in $Cachebasefolder [ "$Cachebasefolder" != "$(echo $Cachebasefolder | sed -e 's/ *//g')" ] && error "Cache root folder must not contain whitespaces. $Cachebasefolder" # create cache subfolder Cachefolder="$Cachebasefolder/$Codename-$Mycookie" [ -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" $Mksu "mkdir -p $Sharefolder" export Cachefolder Sharefolder Logfile="$Sharefolder/x11docker.log" && mkfile $Logfile Messagelogfile="$Cachefolder/$Messagelogfile" && mkfile $Messagelogfile Timetosaygoodbye="$Sharefolder/$Timetosaygoodbye" && mkfile $Timetosaygoodbye Xinitrc="$Cachefolder/$Xinitrc" && mkfile $Xinitrc Xinitlogfile="$Cachefolder/$Xinitlogfile" && mkfile $Xinitlogfile Xtermrc="$Cachefolder/$Xtermrc" && mkfile $Xtermrc Pullrc="$Cachefolder/$Pullrc" && mkfile $Pullrc Containerlogfile="$Cachefolder/$Containerlogfile" && mkfile $Containerlogfile Containerpidfile="$Cachefolder/$Containerpidfile" && mkfile $Containerpidfile Containeridfile="$Cachefolder/$Containeridfile" && mkfile $Containeridfile Containerpid1pidfile="$Cachefolder/$Containerpid1pidfile" && mkfile $Containerpid1pidfile Containeruserfile="$Sharefolder/$Containeruserfile" && mkfile $Containeruserfile Dockerrc="$Cachefolder/$Dockerrc" && mkfile $Dockerrc Dockercommandfile="$Cachefolder/$Dockercommandfile" && mkfile $Dockercommandfile Containerrootrc="$Sharefolder/$Containerrootrc" && mkfile $Containerrootrc Containeripfile="$Cachefolder/$Containeripfile" && mkfile $Containeripfile Xservercookie="$Cachefolder/$Xservercookie" && mkfile $Xservercookie Xclientcookie="$Sharefolder/$Xclientcookie" && mkfile $Xclientcookie Hostxauthority="$Cachefolder/$Hostxauthority" && mkfile $Hostxauthority Xpraserverlogfile="$Cachefolder/$Xpraserverlogfile" && mkfile $Xpraserverlogfile Xpraclientlogfile="$Cachefolder/$Xpraclientlogfile" && mkfile $Xpraclientlogfile Compositorlogfile="$Cachefolder/$Compositorlogfile" && mkfile $Compositorlogfile Compositorpidfile="$Cachefolder/$Compositorpidfile" && mkfile $Compositorpidfile Pulseaudioconf="$Cachefolder/$Pulseaudioconf" && mkfile $Pulseaudioconf Bgpidfile="$Cachefolder/$Bgpidfile" && mkfile $Bgpidfile Imagecommandscript="$Sharefolder/$Imagecommandscript" && mkfile $Imagecommandscript Shareclipboardscript="$Cachefolder/$Shareclipboardscript" && mkfile $Shareclipboardscript Westonini="$Cachefolder/$Westonini" && mkfile $Westonini Xdummyconf="$Cachefolder/$Xdummyconf" && mkfile $Xdummyconf Xorgwrapper="$Cachefolder/$Xorgwrapper" && mkfile $Xorgwrapper Xkbkeymapfile="$Cachefolder/$Xkbkeymapfile" && mkfile $Xkbkeymapfile Systemdtarget=$Cachefolder/x11docker.target && mkfile $Systemdtarget Systemdstartcmd=$Cachefolder/x11docker-startcmd.service && mkfile $Systemdstartcmd Systemdwatch=$Cachefolder/x11docker-watch.service && mkfile $Systemdwatch Systemdjournal=$Cachefolder/x11docker-journal.service && mkfile $Systemdjournal Journallogfile=$Sharefolder/journalctl.log && mkfile $Journallogfile Systemdenvironment=$Cachefolder/systemd.env.conf && mkfile $Systemdenvironment # Cache folder for x11docker-gui. Created here to store image list in dockerrc for x11docker-gui mkdir -p $Cachebasefolder/x11docker-gui && chown $Hostuser $Cachebasefolder/x11docker-gui # standard streams for container Cmdstdoutlogfile="$Sharefolder/$Cmdstdoutlogfile" && mkfile $Cmdstdoutlogfile 666 Cmdstderrlogfile="$Sharefolder/$Cmdstderrlogfile" && mkfile $Cmdstderrlogfile 666 mkfile "$Sharefolder/environment" 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) Codename="$(echo $Imagename | tr / - | cut -d: -f1)" command -v $Dockerexe >/dev/null || error "docker is not installed. To run docker images, you need to install docker." verbose "Image name: $Imagename Image command: $Imagecommand" ;; exe) Hostexe="$Imagename $Imagecommand" Imagename="" Imagecommand="" Codename="$(basename $(echo $Hostexe | cut -d' ' -f1) | tr -cd '[:alpha:][:digit:][:blank:]-_.')" command -v $Hostexe >/dev/null || error "Command '$Hostexe' not found." verbose "Host application to execute: $Hostexe" ;; xonly) X11dockermode="exe" Hostexe="sleep infinity" Imagename="" Imagecommand="" Codename="xonly" Showdisplayenvironment="yes" ;; esac return 0 } check_option_interferences() { # check multiple option interferences, change settings if needed 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" ] && [ "$Hosttty" = "no" ] && warning "Your configuration seems not to allow to start a second core Xorg server from within X. Option --xorg will probably 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 Ubuntu 16.04 and debian 9 you need package xserver-xorg-legacy." } ;; --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'." && Xpravfb="Xdummy" [ "$Xpravfb" ] || { command -v Xvfb >/dev/null && Xpravfb="Xvfb" || Xpravfb="Xdummy" ; } ;; esac case $Xserver in -xpra|--xpra-xwayland) # check for version with cookie bug ! verlt "$Xpraversion" "xpra 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." Xauthentication="no" } } ;; esac # add VcXsrv folder to PATH to get vcxsrv.exe, xhost.exe and xauth.exe available [ "$Xserver" = "--vcxsrv" ] && export PATH="$PATH:$Vcxsrvpath" # check if a host window manager is needed [ "$Desktopmode" = "no" ] && [ -z "$Windowmanager" ] && case $Xserver in --xephyr|--weston-xwayland|--kwin-xwayland|--xorg|--xwayland) Windowmanager="auto" [ "$Autochooseserver" = "yes" ] && [ "$Hosttty" = "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." ;; 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." ;; esac } ;; esac # check xauth [ "$Xserver" != "--tty" ] && [ "$Xauthentication" = "yes" ] && { command -v xauth.exe >/dev/null && Xauthexe="xauth.exe" command -v xauth >/dev/null && Xauthexe="xauth" command -v $Xauthexe >/dev/null || { warning "Command 'xauth' not found. Please install 'xauth' to allow X cookie authentication. Fallback: Disabling X authentication protocol. (option --no-auth)" Xauthentication="no" } command -v $Xauthexe | grep -q VcXsrv && { Vcxsrvversion="$(vcxsrv.exe -version 2>&1 | rmcr | grep Release | cut -d' ' -f2)" verlt "$Vcxsrvversion" "1.20.0.1" && { warning "Please update X server VcXsrv to at least version 1.20.0.1. Your currently installed version $Vcxsrvversion provides a buggy xauth.exe. Can not create a cookie for secure X connections. Fallback: Disabling X authentication protocol. (option --no-auth)" Xauthentication="no" } } } # --fullscreen is nonsense on tty at all. Avoids weston error on tty. [ "$Hosttty" = "yes" ] && Fullscreen="no" # --gpu [ "$Sharegpu" = "yes" ] && { case $Xserver in --xpra|--xdummy|--nxagent|--xephyr) note "Option $Xserver does not support hardware acceleration. Fallback: using software rendering, disabling option --gpu" Sharegpu="no" ;; esac } # --hostdisplay --gpu [ "$Xserver" = "--hostdisplay" ] && [ "$Sharegpu" = "yes" ] && { note "Option --gpu: To allow GPU acceleration with --hostdisplay, x11docker will allow trusted cookies. That enables option --clipboard, too." 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'." } # --clipboard [ "$Shareclipboard" = "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) 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|--vcxsrv|--xwin) ;; *) note "Sharing picture clips with option --clipboard is only possible with options --xpra, --xpra-xwayland and --hostdisplay." ;; esac } # --hostdisplay with SSH [ "$Xserver" = "--hostdisplay" ] && [ "$Hostssh" = "yes" ] && { [ "$Trusted" = "no" ] || [ "$Sharehostnet" = "no" ] && { note "For SSH connection with option --hostdisplay x11docker must enable option --hostnet and allow trusted cookies. It is recommended to use other X server options like --xpra, --xephyr or --nxagent." Sharehostnet="yes" Trusted="yes" } } [ "$Xserver" = "--hostdisplay" ] && [ "$Trusted" = "yes" ] && [ "$Hostmitshm" = "yes" ] && [ "$Sharehostipc" = "no" ] && [ "$Hostssh" = "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" } # --hostnet --no-internet [ "$Sharehostnet" = "yes" ] && [ "$Internetaccess" = "no" ] && { note "You have chosen --hostnet and --no-internet. That does not work. You can not share host network stack (--hostnet) and forbid internet access at the same time (except you disconnect your host from internet). Fallback: disabling option --hostnet, keeping --no-internet." Sharehostnet="no" } # --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" Scaling="" } ;; --xpra|--xpra-xwayland|--xorg) isnum $Scaling || { note "Option --scale needs a number. '$Scaling' is not allowed. Fallback: disabling option --scale" 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" Scaling="" ;; esac case $Xserver in --xpra|--xpra-xwayland) verlt "$Xpraversion" "xpra 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" 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" 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" Rotation="" } ;; *) note "Option $Xserver does not support option --rotate. Rotation is possible for --xorg, --weston and --weston-xwayland. Fallback: disabling option --rotate" 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'." ;; 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|--vcxsrv|--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" } [ "$Hosttty" = "yes" ] && { note "Option --outputcount only works in nested/windowed mode, but not on tty. Fallback: disabling --outputcount" 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." Xfishtank="no" } case $Xserver in --xpra|--xpra-xwayland|--nxagent) [ "$Desktopmode" = "no" ] && [ -z "$Windowmanager" ] && Windowmanager="auto" && Desktopmode="yes" ;; --weston|--kwin|--hostwayland|--hostdisplay|--tty) note "Option --xfishtank is not supported for $Xserver." Xfishtank="no" ;; esac } # MSYS2, Cygwin, WSL [ "$Winsubsystem" ] && { grep -q "/c/" <<< "$Cachebasefolder" && [ -z "$Homebasefolder" ] && note "Per default x11docker stores its cache files on drive C:. docker setup may 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." [ "$Initsystem" = "systemd" ] && { note "Option --systemd is not possible on MSYS2/Cygwin/WSL. systemd needs a running linux kernel with cgroup support. Fallback: Disabling option --systemd, enabling option --dbus-system" Initsystem="tini" Dbussystem="yes" } [ "$Sharecgroup" = "yes" ] && { note "Option --sharecgroup is not possible on MSYS2/Cygwin/WSL. Fallback: Disabling option --sharecgroup." Sharecgroup="no" } case $Xserver in tty) ;; *) 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 } # check XDG_RUNTIME_DIR case $Xserver in --weston|--weston-xwayland|--kwin|--kwin-xwayland|--xpra-xwayland|--xwayland) [ -z "${XDG_RUNTIME_DIR:-}" ] && [ -e "/run/user/$Hostuseruid" ] && export XDG_RUNTIME_DIR="/run/user/$Hostuseruid" [ -z "${XDG_RUNTIME_DIR:-}" ] && { export XDG_RUNTIME_DIR=/tmp/XDG_RUNTIME_DIR.x11docker.$Hostuseruid $Mksu "mkdir -p $XDG_RUNTIME_DIR" $Mksu "chmod 700 $XDG_RUNTIME_DIR" } ;; esac # check --westonini [ -n "$Customwestonini" ] && [ ! -e "$Customwestonini" ] && { warning "Custom weston.ini (option --westonini) not found. $Customwestonini" Customwestonini="" } # --user=RETAIN / keep container defined in image case $Createcontaineruser in no) [ "$Sharehosthome" = "yes" ] && { warning "Option --home is not supported with option --user=RETAIN. You can use option --sharedir to share host directories. To use option --home, you can check uid and gid of container user and run x11docker with --user=uid:gid instead of --user=RETAIN. Fallback: Disabling option --home." Sharehosthome="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 # --interactive case $Interactive in yes) [ "$Forwardstdin" = "yes" ] && { note "You cannot use --stdin along with --interactive. Fallback: Disabling option --stdin." Forwardstdin="no" } Showstdout="no" Showstderr="no" case $Initsystem in systemd|openrc|runit|sysvinit) note "Interactive mode (--interactive) with option --$Initsystem is not well integrated yet. Shells do not have job control, and CTRL-C can behave different than expected." ;; esac [ "$Winsubsystem" ] && { Winpty="$(command -v winpty)" Winpty="$(escapestring "$Winpty")" [ "$Winpty" ] || note "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. WSL isn't supported yet." } ;; esac # --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... and start x11docker there. 3.) Run x11docker as root." [ "$Autochooseserver" = "yes" ] && [ -n "$Hostdisplay" ] && note "Could not find Xephyr, Xnest, nxagent, xpra, weston+Xwayland or kwin_wayland+Xwayland to run a nested X server. Consider to install one of them." 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 [ "$Hostssh" = "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" "xpra v1.0" && { note "Your xpra version '$Xpraversion' is out of date. It is recommended to install at least xpra v1.0." [ "$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" "xpra v2.2-r17117" && note "Xpra desktop mode works best since xpra v2.2-r17117. You have installed lower version $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" ] && note "Xpra startup is rather slow. For faster startup with seamless applications, try --nxagent. If security is not a concern, try --hostdisplay." [ "$Sharegpu" = "yes" ] && note "If performance of GPU acceleration with $Xserver is not satisfying, you can try insecure '--hostdisplay --gpu'." ;; --xephyr) note "Xephyr is a quite stable nested X server. Less stable, but resizeable is nxagent with option --nxagent." ;; --nxagent) [ "$Hostsystem" = "mageia" ] && { [ "$Desktopmode" = "no" ] && [ "$Autochooseserver" = "yes" ] && Desktopmode="yes" && Windowmanager="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 or do not appear at all (e.g. atom editor, kodi, cinnamon). 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 [ "$Hostnvidia" = "yes" ] && [ "$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 --xpra or --xephyr." ;; esac # --fullscreen [ "$Fullscreen" = "yes" ] && { case $Xserver in --xephyr|--weston|--weston-xwayland|--nxagent|--xpra|--xpra-xwayland|--vcxsrv|--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 } # --keymap: XKB keyboard layount [ -n "$Xkblayout" ] && case $Xserver in --kwin|--kwin-xwayland) [ "$Hosttty" = "yes" ] && note "Option --keymap does not work with option $Xserver if running from console." ;; esac # --hostipc [ "$Sharehostipc" = "yes" ] && warning "Option --hostipc degrades container isolation. IPC namespace remapping is disabled." # --hostnet [ "$Sharehostnet" = "yes" ] && warning "Option --hostnet 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." # --cap-default [ "$Capdropall" = "no" ] && warning "Option --cap-default disables security hardening for containers. Granting docker's default capabilities is considered insecure." # --sys-admin [ "$Capsysadmin" = "yes" ] && warning "Option --sys-admin may be dangerous. It adds insecure capability SYS_ADMIN to container. It is needed to run debian 9 images with option --systemd. Debian 10 images run well without --sys-admin." # --webcam [ "$Sharewebcam" = "yes" ] && warning "Option --webcam: Container applications might look at you and also might take screenshots of your Desktop." [ "$Customdockeroptions" ] && warning "Found custom DOCKER_RUN_OPTIONS. x11docker will add them to 'docker run' command without a check for validity or security. Found options: $Customdockeroptions" 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" [ -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" ] && 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" ] && 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)." Passwordcommand="bash -c" ; Passwordfrontend="none" ; Passwordneeded="no" ; Passwordterminal="bash -c" } } 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' 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 NOLUCK" [ -z "$Hostdisplay" ] && { case $Xserver in --weston|--hostwayland) Terminallist="konsole qterminal gnome-terminal NOLUCK" ;; --kwin) Terminallist="konsole NOLUCK" ;; --tty|--xdummy|--xvfb) Terminallist="bash NOLUCK" ;; *) ;; esac } [ "$Runsinterminal" = "yes" ] && Terminallist="bash NOLUCK" [ "$Xserver" = "--tty" ] && Terminallist="bash NOLUCK" for Passwordterminal in $Terminallist ; do command -v $Passwordterminal >/dev/null && break ; done [ "$Passwordterminal" = "NOLUCK" ] && warning "Can not find a terminal emulator that works for option $Xserver. Please install one of: ${Terminallist%"NOLUCK"}" [ -z "$Hostdisplay" ] && [ "$Sharewayland" = "yes" ] && { 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 } 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="bash -c" ;; *) Passwordterminal="$Passwordterminal -e" ;; esac [ -z "$Hostdisplay$Newdisplay" ] && { case $Xserver in --weston|--kwin|--hostwayland) ;; *) Passwordterminal="bash -c";; esac } 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: # &0 stdin --stdin with catstdin # &1 stdout # &2 stderr # &3 stderr for warnings and notes, with --silent redirected to /dev/null # &4 stderr for --stderr # &5 (not in use) # &6 $Messagefifo for messages from other threads to watchmessagefifo() # &7 stdin>>$Cmdstinfile --stdin with catstdin # &8 $Timetosaygoodbyefifo for saygoodbye() and waitfortheend() # &9 $Watchpidfifo for watchpidlist() # redirect stdin to named pipe. Named pipe is shared with container and used as stdin of image command in container.CMD.sh [ "$Forwardstdin" = "yes" ] && { Cmdstdinfile="$Sharefolder/$Cmdstdinfile" case $Winsubsystem in "") $Mksu "mkfifo $Cmdstdinfile" ;; *) mkfile $Cmdstdinfile ;; esac exec 7<>$Cmdstdinfile cat <&0 >&7 & storepid $! catstdin } case $Winsubsystem in MSYS2|CYGWIN|WSL) Watchpidfifo="$Cachefolder/$Watchpidfifo" && mkfile $Watchpidfifo Messagefifo="$Sharefolder/$Messagefifo" && mkfile $Messagefifo && chmod 666 $Messagefifo Timetosaygoodbyefifo="$Sharefolder/$Timetosaygoodbyefifo" && mkfile $Timetosaygoodbyefifo && chmod 666 $Timetosaygoodbyefifo ;; "") Watchpidfifo="$Cachefolder/$Watchpidfifo" && $Mksu "mkfifo $Watchpidfifo" Messagefifo="$Sharefolder/$Messagefifo" && $Mksu "mkfifo $Messagefifo && chmod 666 $Messagefifo" Timetosaygoodbyefifo="$Sharefolder/$Timetosaygoodbyefifo" && $Mksu "mkfifo $Timetosaygoodbyefifo" ;; esac # used by waitfortheend() exec 8<>$Timetosaygoodbyefifo # start watching important pids, e.g. xinit, container. exec 9<>$Watchpidfifo watchpidlist & storepid $! watchpidlist # start watching for messages out of container or dockerrc exec 6<>$Messagefifo watchmessagefifo & storepid $! watchmessagefifo return 0 } #### main declare_variables() { # declare global variables export IFS=$' \n\t' # set IFS to default export PATH="$PATH:/usr/games:/usr/local/bin" # can miss for root, but might be needed for --exe and --xfishtank export PATH="$PATH:/usr/sbin:/sbin" # can miss for unprivileged users, but might be needed for `ip` Dockerexe="docker" # can be docker.exe on Windows Bgpidfile=backgroundpids # file to store pids and names of background processes that shut be killed on exit Timetosaygoodbye=timetosaygoodbye # file giving term signal to all parties Timetosaygoodbyefifo=timetosaygoodbye.fifo # message channel for --openrc, --runit, --sysvinit to shut down on x11docker signal Watchpidfifo=watchpid.fifo # message channel to transfer pids to watchpidlist() Messagefifo=message.fifo # message channel for warning/verbose/debugnote/note/error within container, dockerrc, containerrootrc Messagelogfile=message.log # logfile for warning/verbose/debugnote/note/error Logmessages="" # stores messages until logfile is available, needed by logentry() Logfile="" # x11docker.log Mycookie=$(mcookie 2>/dev/null || echo $RANDOM | cut -b1-6) # random number used for container name and cache folder name # terminal colors used for messages and --verbose=c 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" # Users Startuser="" # user who started x11docker Lognameuser="" # $(logname) or $SUDO_USER or $PKEXEC_USER Hostuser="" # $Lognameuser or --hostuser. Unprivileged user for non-root commands Hostuseruid="" Hostusergid="" Hostuserhome="" Homebasefolder="" # option --homebasedir: base directory for container home with --home Createcontaineruser="yes" Containeruser="" # option --user: container user. Default: same as $Hostuser. Imageuser="" # option --user=RETAIN: possible USER in image Containeruseruid="" Containerusergid="" Containerusergroup="" Containerusergroups="video audio" # option --group-add: additional groups for container user Containeruserhome="" # HOME path within container Containeruserhosthome="" # options --home, --homedir: path to shared host folder used as HOME in container. Containeruserpassword="sac19FwGGTx/A" # encrypted password "x11docker", suits /etc/shadow. Generated with: perl -e 'print crypt("x11docker", "salt"),"\n"' Mksu="" # prefix to run unprivileged commands on host (auto or --hostuser) # Hostsystem Hostsystem="" # $ID from /etc/os-release Hostip="" # An IP address to access host Hostdisplay="" # environment variable DISPLAY Hostdisplaynumber="" # DISPLAY without : Hostxauthority="Xauthority-$Hostdisplaynumber" # file to store copy of $XAUTHORITY Hostxsocket="" # socket of DISPLAY in /tmp/.X11-unix Hostxenv="" # collection of host X environment variables Hostmitshm="yes" # X on host has extension MIT-SHM enabled? Assume yes, check later Hostlibc="" # glibc or musl Hostlocaltime="" # time zone from host, realpath /etc/localtime Hostutctime="" # time zone from host as offset to UTC Hostssh="" # x11docker runsover ssh yes/no Hosttty="" # x11docker runs on tty yes/no Hosthidepid="" # /proc is mounted with hidepid=2 yes/no Hostnvidia="" # proprietary nvidia driver on host yes/no Hostcanwatchroot="" # x11docker can watch root processes yes/no Runsinterminal="" # x11docker runs in a terminal yes/no Winsubsystem="" # Windows subsystem WSL, MSYS2 or CYGWIN Winsubmount="" # path within subsystem to mounted Windows drives Winsubpath="" # path within Windows to subsystem files Winpidlist="" # List of stored Windows pids (currently vcxsrv.exe on WSL only) Winpty="" # Path to winpty for --interactive on Windows # Gaining root privileges to run docker Passwordfrontend="" # --pw: method to prompt for password. one of pkexec, su, sudo, gksu, gksudo, kdesu, kdesudo, lxsu, lxsudo, beesu, auto, none Passwordcommand="" # generated command for password prompt Sudo="" # "sudo", "sudo -n", or empty Passwordneeded="yes" # password needed to run docker? assume yes, check later # Cache folders Cachebasefolder="" # base cache folder to store temporary files Cachefolder="" # subfolder of $Cachebasefolder for current container Sharefolder=share # subfolder of $Cachefolder for files shared with container Cshare=/x11docker # mountpoint of $Sharefolder in container Codename="" # Generated name for cache, containerhome and other purposes from image name or hostexe # Parsed arguments X11dockermode="run" # can be either "xonly", "run" or "exe", depends on options. while parsing, xonly changes to run or exe Imagename="" # name of image to run Imagecommand="" # image command [+args] Codename="" # image name with : and / replaced by - for use with container name and --home folder Hostexe="" # option --exe: contains host executable Customdockeroptions="" # custom options for "docker run". Provide after -- and before image name, encapsulated with "" (or without whitespaces) # docker variables Containername="" # generated container name Nonewprivileges="yes" # docker run option --security-opt=no-new-privileges. Default: yes. Disabled by options --cap-default, --sudouser and --user=root. Capabilities="" # capabilities to add Stopsignal="" # signal to send on 'docker stop' Pullimage="ask" # option --pull: allow 'docker pull' yes|no|always|ask # docker related files Dockerrc=dockerrc # generated script to check image, set up host and create $Imagecommandscript Dockercommandfile=dockercommand # generated script only containing docker command, needed for --interactive Containerrootrc=containerrootrc # generated script to set up container. runs as root in container. Imagecommandscript=container.CMD.sh # generated script starting image comand in container Containerlogfile=container.log # file to log output of container other than image command output Containerpidfile=container.pid # file to store container PID Containeridfile=container.id # file to store container ID Containeripfile=container.ip # file to store container IP Containerpid1pidfile=container.pid1pid # file to store host PID of container PID 1 Containeruserfile=container.user # X server config files, log files and such stuff Xinitrc=xinitrc # generated xinitrc script Xtermrc=xtermrc # generated script for password prompt Pullrc=pullrc # generated script for pull dialog Xinitlogfile=xinit.log # logfile for xinit/X server Xpraserverlogfile=xpraserver.log # logfile for xpra server Xpraclientlogfile=xpraclient.log # logfile for xpra client Westonini=weston.ini # generated config file for weston Customwestonini="" # --westonini: custom config file for weston Xdummyconf=xdummy.xorg.conf # generated xorg.conf for dummy video driver Xorgwrapper=Xorgwrapper # fork from xpra to wrap Xorg for Xdummy Xservercookie=Xservercookie # generated X server cookie Xclientcookie=Xclientcookie # generated X client cookie. Normally same as $Xservercookie, except for --hostdisplay and --nxagent Xkbkeymapfile=xkb_keymap # file to store output of host keymap in xinitrc: 'setxkbmap -display $Hostdisplay -print' Vcxsrvpath="" # path to vcxsrv.exe and xauth.exe (--vcxsrv) # stdin stdout stderr Forwardstdin="no" # option --stdin: forward stdin to image command Cmdstdinfile=stdin # stdin for image command. fifo/named pipe to forward stdin of x11docker to image command Cmdstdoutlogfile=stdout # stdout for image command Cmdstderrlogfile=stderr # stderr for image command # X server settings Xserver="" # X server option to use Xcommand="" # created command to start X server Newdisplay="" # new display for new X server Newdisplaynumber="" # new display number for new X server. Without ':' Newxsocket="" # New X socket Newxenv="" # environment variables for new X server DISPLAY XAUTHORITY XSOCKET WAYLAND_DISPLAY Newxvt="" # option --vt or automatically: number of virtual console to use for --xorg (>7) Newxlock="" # .Xn-lock - exists for running X server with socket n Xlegacywrapper="" # option --xorg: /etc/X11/Xwrapper.config is configured to run within X yes/no Xpraservercommand="" # created xpra server command Xpraclientcommand="" # created xpra client command Xpraborder="" # --border: Colored border for xpra clients Xpravfb="" # vfb for --xpra: Xdummy or Xvfb Xprashm="" # content XPRA_XSHM=0 disables usage of MIT-SHM in xpra Xpraversion="" # $(xpra --version) to decide some xpra options and messages Xprarelease="" # release number from $Xpraversion Xephyrglamor="yes" # former option '--glamor': enable Xephyr glamor 2D acceleration (deprecated, now always yes) Xfishtank="no" # option --xfishtank: fish tank on new X server Xkblayout="" # option '--keymap': Layout for keymap, compare /usr/share/X11/xkb/symbols Xtest="" # enable extension Xtest yes/no. If empty, yes for --xpra/--xdummy/--xvfb, otherwise no Xcomposite="" # +extension COMPOSITE yes/no Xauthexe="xauth" # either 'xauth' or 'xauth.exe' Xoverip="" # Connect to X over TCP yes/no # Main options influencing --auto Autochooseserver="yes" # option '--auto': automatically choose X server (default) Desktopmode="no" # option --desktop: image contains a desktop enironment. Windowmanager="" # option '-w, --wm': window manager to use. If not given but needed, autodetection is used Sharegpu="no" # option '--gpu': Use hardware accelerated OpenGL, share files in /dev/dri # --gpu: nvidia Nvidiadriver="" # option '--gpu': nvidia driver installer for container in [...]local/share/x11docker Nvidiaversion="" # " " nvidia driver version on host # Screensize and related X server adjustments Screensize="" # option --size XxY Xaxis="" # virtual screen width Yaxis="" # virtual screen height Modeline="" # screen modeline describing display size, see "man cvt" Maxxaxis="" # maximal screen size of display to support fullscreen beside windowed desktop Maxyaxis="" Fullscreen="no" # option '-f, --fullscreen': use fullscreen mode (Xephyr only) Scaling="" # option --scale: Scaling factor for xpra and weston Rotation="" # option --rotate: Rotation for --weston and --weston-xwayland 0/90/180/270/flipped/flipped-90/.. Dpi="" # option --dpi: dots per inch to tell the clients. Outputcount="1" # option --output-count, quantum of virtual screens for Weston and Xephyr # Wayland and Weston Hostwaylandsocket="${WAYLAND_DISPLAY:-}" # store host wayland socket name Newwaylandsocket="" # Wayland socket of $Compositorcommand Compositorcommand="" # command to start Weston or KWin 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" Sharewayland="no" # share wayland socket and WAYLAND_DISPLAY Setwaylandenv="no" # set environment variables $Waylandtoolkitenv Compositorpidfile=compositor.pid # file to store PID of compositor Compositorlogfile=compositor.log # logfile for weston or kwin_wayland # Available terminal emulators Passwordterminal="" # terminal emulator to use for password prompt (if no terminal emulator is needed, it will be 'bash -c') Terminallist="" # list of possible terminal emulators. Only a few work on Wayland yet. # regular options Sudouser="no" # option --sudouser: Create user and root with sudo permissions with password 'x11docker' Sharehosthome="no" # option '-m, --home' (or '--homedir'): share a folder ~/.local/share/x11docker/Imagename with created container Sharevolumes="" # option --volume: host folders to share, array Sharevolumescount="0" # counts shared folders in array Shareclipboard="no" # option '-c, --clipboard' enable clipboard sharing Shareclipboardscript=clipboard.bash # " " created script for text clipboard sharing Sharealsa="no" # option --alsa: enable ALSA sound, share /dev/snd Alsacard="${ALSA_CARD:-}" # option --alsa: specified ALSA card Noentrypoint="no" # option --no-entrypoint: disable entrypoint in image Internetaccess="yes" # option --no-internet: disable internet access Langwunsch="" # option --lang: search or create UTF-8 locale and set LANG Sharecups="no" # option --printer: share access to cups printer server Sharewebcam="no" # option --webcam: Share webcam /dev/video* Sharedevices="" Sharedevicescount="0" # --pulseaudio Pulseaudiomode="" # option --pulseaudio: 'tcp', 'socket' or 'auto' Pulseaudioconf=pulseclient.conf # client config in container Pulseaudioport="" # TCP port for --pulseaudio=tcp # verbosity options Verbose="no" # option '-v, --verbose': if "yes", be verbose Showstdout="yes" # option --stdout: show image command stdout Showstderr="yes" # option --stderr: show image command stderr Silent="no" # option --silent: do not show messages Showdisplayenvironment="no" # option -E, --env: output of environment variables of new display on stdout Showcontainerid="no" # option --showid: output of container id on stdout Showcontainerpid1pid="no" # option --showpid1: output of host PID of container PID 1 Verbosecolors="no" # colored output for --verbose and delete some systemd error messages. Debugmode="no" # option --debug: Throw it all out # advanced options Containerenvironment="" # option '--env': set custom environment variables Containerenvironmentcount="0" Interactive="no" # option --interactive: Run docker with interactive tty yes/no Capdropall="yes" # option --cap-default: (don't) drop all container capabilities Adminusercaps="no" # options --sudouser, --systemd: add capabilities for general sys administration Switchcontaineruser="no" # options for init systems: user switching to toggle login daemons Switchcontainerusercaps="no" # options --dbus, --sudouser and initsystems: add capabilities for su/sudo user switching Sharehostipc="no" # option --hostipc, set --ipc=host. Sharehostnet="no" # option --hostnet, set --ipc=net Preservecachefiles="no" # if yes, dont delete cache files Capsysadmin="no" # option --sys-admin: add capability SYS_ADMIN (deprecated) Runfromhost="" # option --runfromhost: add host command to xinitrc Runasroot="" # option --runasroot: add command running as root to container setup script Workdir="/tmp" # option --workdir: set working directory Limitresources="" # option --limit: Limit access to CPU and RAM, 0.1 ... 1.0 # init and dbus Initsystem="tini" # options --systemd --openrc --runit --sysvinit --tini --no-init:Init system in container Sharecgroup="no" # options --systemd, --cgroup: share /sys/fs/cgroup Dbusrunsession="no" # option '--dbus': run image command with dbus-run-session / user session Dbussystem="no" # option '--dbus-system': set up dbus system daemon in container Sharehostdbus="no" # option '--hostdbus' Tinibinary="" # option --tini/default: binary for tini; either /usr/bin/docker-exec or provided by user in ...share/x11docker # X authentication Xauthentication="yes" # option '--no-auth' use cookie authentication yes/no Trusted="yes" # Create trusted or untrusted cookies, --hostdisplay uses untrusted cookies Noxhost="no" # option '--no-xhost': if yes, disable all X server access granted by xhost Xhost="" # option '--xhost': custom xhost setting on new X server # special options not starting X or docker Cleanup="no" # option '--cleanup': check for non-removed containers and maybe root-owned files in cache Createlauncher="no" # option '--launcher': create application launcher on desktop and exit yes/no Installermode="" # options --install/--update/--remove # 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 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 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 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" Messagefifofuncs=' warning() { echo "$*:WARNING" >>$Messagefile } note() { echo "$*:NOTE" >>$Messagefile } verbose() { echo "$*:VERBOSE" >>$Messagefile } debugnote() { echo "$*:DEBUGNOTE" >>$Messagefile } error() { echo "$*:ERROR" >>$Messagefile exit 1 } stdout() { echo "$*:STDOUT" >>$Messagefile }' } parse_options() { # parse cli options local Shortoptions Longoptions Parsererror Shortoptions="aAbcdDefgGhHiKl:mMnNopPqQrstTuv::w:WxXyY" Longoptions="auto,x,X,xpra,xephyr,x11,xorg,hostdisplay,xwayland,weston-xwayland,xpra-xwayland,nxagent,wayland" # X servers Longoptions="$Longoptions,weston,hostwayland,kwin,kwin-xwayland,xdummy,xvfb,no-x,vcxsrv,xwin,tty" # more X/Wayland servers Longoptions="$Longoptions,wm:,desktop,exe,xonly" Longoptions="$Longoptions,fullscreen,size:,scale:,rotate:,dpi:,output-count:,gpu,xfishtank,border::" # X appearance options Longoptions="$Longoptions,xhost:,no-auth,vt:,display:,env:,showenv,showid,showpid1" # X and environment options Longoptions="$Longoptions,home,clipboard,pulseaudio::,alsa::,lang:,printer,webcam" # comfort options Longoptions="$Longoptions,no-xhost,dbus,dbus-system,hostdbus,pw:,no-internet,workdir:,sharessh,limit::,pull::" # advanced options Longoptions="$Longoptions,homedir:,sharedir:,cachebasedir:,homebasedir:" # host folders Longoptions="$Longoptions,tini,no-init,systemd,runit,openrc,sysvinit,sys-admin,sharecgroup" # init system options Longoptions="$Longoptions,launcher,cleanup,license,licence,help,version,install,update,update-master,remove,wmlist" # special options without starting X or container Longoptions="$Longoptions,verbose::,quiet,debug,stdin" # verbose options Longoptions="$Longoptions,hostipc,hostnet,ps,cap-default,sudouser,hostuser:,user:,group-add:,name:" # capabilities, users Longoptions="$Longoptions,keymap:,name:,no-entrypoint,runfromhost:,runasroot:,westonini:,interactive" # developer options Longoptions="$Longoptions,cachedir:,starter,trusted,untrusted,xtest,no-xtest,stdout,stderr,silent,nothing" # deprecated Longoptions="$Longoptions,xtest,xcomposite" # experimental Parsedoptions="$(getopt --options $Shortoptions --longoptions $Longoptions --name "$0" -- "$@" 2>/tmp/x11docker_parsererror)" [ -e /tmp/x11docker_parsererror ] && Parsererror=$(cat /tmp/x11docker_parsererror) && rm /tmp/x11docker_parsererror [ "$Parsererror" ] && error "$Parsererror" eval set -- "$Parsedoptions" [ "$*" = "-h --" ] && usage && exit 0 # catch single -h for usage info, otherwise it means --hostdisplay while { [ $# -gt 0 ] && [ -z "$Imagename" ] ;}; do 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 -e|--exe) X11dockermode="exe" ;; # execute application from host instead of running docker image --xonly) X11dockermode="xonly" ;; # only create X erver #### X servers --auto) Autochooseserver="yes" ;; # use xpra or --xephyr, --xorg or hostdisplay, --xpra-xwayland or --weston-xwayland -a|--xpra) [ "$Xserver" = "--xdummy" ] && Xpravfb="Xdummy" [ "$Xserver" = "--xvfb" ] && Xpravfb="Xvfb" Xserver="--xpra" ; Autochooseserver="no" ;; # use xpra on host -y|--xephyr) Xserver="--xephyr" ; Autochooseserver="no" ;; # use Xephyr -x|--xorg|--x11) Xserver="--xorg" ; Autochooseserver="no" ;; # use core Xorg -h|--hostdisplay) Xserver="--hostdisplay" ; Autochooseserver="no" # use host display :0 with shared X socket Trusted="no" ;; -X|--xwayland) Xserver="--xwayland" ; Autochooseserver="no" ;; # Xwayland needs already running Wayland -A|--xpra-xwayland) Xserver="--xpra-xwayland" ; Autochooseserver="no" ;; # Xpra with Xwayland -Y|--weston-xwayland) Xserver="--weston-xwayland" ; Autochooseserver="no" ;; # Weston-Xwayland as Wayland compositor with Xwayland, runs in X or standalone from console --xdummy) [ "$Xserver" = "--xpra" ] && Xpravfb="Xdummy" || { # use Xdummy. Invisible on host. For custom network setups with VNC or xpra Xserver="--xdummy" ; Autochooseserver="no" Showdisplayenvironment="yes" ; } ;; --xvfb) [ "$Xserver" = "--xpra" ] && Xpravfb="Xvfb" || { # use Xvfb. Invisible on host. For custom network setups with VNC or xpra Xserver="--xvfb" ; Autochooseserver="no" Showdisplayenvironment="yes" ; } ;; -T|--weston) Xserver="--weston" ; Autochooseserver="no" ;; # Wayland in Weston only, no X -H|--hostwayland) [ "$Xserver" = "--hostdisplay" ] || Xserver="--hostwayland" # share host wayland. Allow coexistence with option --hostdisplay Sharewayland="yes" ; Autochooseserver="no" ;; -K|--kwin) Xserver="--kwin" ; Autochooseserver="no" ;; --kwin-xwayland) Xserver="--kwin-xwayland" ; Autochooseserver="no" ;; -n|--nxagent) Xserver="--nxagent" ; Autochooseserver="no" ;; --vcxsrv) Xserver="--vcxsrv" ; Autochooseserver="no" ;; --xwin) Xserver="--xwin" ; Autochooseserver="no" ;; -t|--tty) Xserver="--tty" ; Autochooseserver="no" ;; # Do not provide any X nor Wayland #### Influencing X server -d|--desktop) Desktopmode="yes" ;; # image contains a desktop environment. -g|--gpu) Sharegpu="yes" ;; # share files in /dev/dri, allow GPU usage -w|--wm) case ${2:-} in # choose host window manager ""|"n"|"none") Windowmanager="none" ;; "m"|"auto") Windowmanager="auto" ;; *) Windowmanager=${2:-} ;; esac shift Desktopmode="yes" ;; -W|--wayland) Sharewayland="yes" # set up wayland environment, regards --desktop Setwaylandenv="yes" Dbusrunsession="yes" ;; #### Appearance --border) Xpraborder="${2:-"blue,1"}" ; shift ;; # colored border for xpra clients -f|--fullscreen) Fullscreen="yes" ;; # fullscreen mode for Xephyr and Weston --size) Screensize="${2:-}" ; shift ;; # set virtual screen size -l|--scale) Scaling=${2:-} ; shift ;; # zoom --rotate) Rotation=${2:-} ; shift ;; # rotation and mirroring --dpi) Dpi=${2:-} ; shift ;; # dots per inch / influences font size --output-count) Outputcount="${2:-}" ; shift ;; # number of virtual outputs --xfishtank) Xfishtank="yes" ;; # Run xfishtank on new X server #### Options -c|--clipboard) Shareclipboard="yes" ;; # share host clipboard with dockered applications (xpra only) --alsa) Sharealsa="yes" # enable ALSA sound (shares /dev/snd) Alsacard="${2:-$Alsacard}" ; shift ;; -p) Pulseaudiomode="auto" ;; # enable pulseaudio sound --pulseaudio) Pulseaudiomode="${2:-}" ; shift # enable pulseaudio sound [ "$Pulseaudiomode" ] || Pulseaudiomode="auto" ;; --lang) Langwunsch=${2:-} ; shift ;; # locale/language setting --printer) Sharecups="yes" ;; # share printer (cups) --webcam) Sharewebcam="yes" ;; # share webcam #### Advanced options -i|--interactive) Interactive="yes" ;; # run with interactive tty --limit) Limitresources="${2:-0.5}" ; shift ;; # run with limited CPU and RAM access --no-entrypoint) Noentrypoint="yes" ;; # don't use ENTRYPOINT of image --no-internet)Internetaccess="no" ;; # disallow internet access --workdir) Workdir="${2:-}" ; shift ;; # set working directory --pw) Passwordfrontend="${2:-}" ; shift ;; # frontend for password prompt --runfromhost) Runfromhost="$Runfromhost ${2:-}" ; shift ;; # add custom host command in xinitrc --runasroot) Runasroot="$Runasroot ${2:-}" ; shift ;; # add custom root command in container setup script --stdin) Forwardstdin="yes" ;; # forward stdin to image command --pull) Pullimage="${2:-yes}" ; shift ;; # allow 'docker pull' #### Verbose options -v|--verbose) Verbose="yes" # be verbose [ "${2:-}" = "c" ] && Verbosecolors="yes" && shift ;; -D|--debug) Debugmode="yes" ;; # debugging mode -q|--quiet) Silent="yes" ;; # do not show warnings or errors #### Developer options --sharessh) [ -e "${SSH_AUTH_SOCK:-}" ] && { # share host folder at same location in container with rw access store_runoption volume "$(dirname $SSH_AUTH_SOCK)" store_runoption env "SSH_AUTH_SOCK=$(escapestring "${SSH_AUTH_SOCK:-}")" } || warning "Option --sharessh: environment variable \$SSH_AUTH_SOCK not set:" ; ;; ## User settings --user) Containeruser="${2:-}" ; shift # set container user instead of host user [ "$Containeruser" = "RETAIN" ] && Createcontaineruser="no" ;; --hostuser) Hostuser="${2:-}" ; shift ;; # set host user different from logged in user --sudouser) Sudouser="yes" ;; # give container user sudo without password --group-add) Containerusergroups="$Containerusergroups ${2:-}" ; shift ;; # additional groups for user ## init system --no-init) Initsystem="none" ;; # no init, image command is PID 1 --tini) Initsystem="tini" ;; # default init system tini (run option --init) --runit) Initsystem="runit" ;; # run runit as init system and image command as a service --openrc) Initsystem="openrc" ;; --systemd) Initsystem="systemd" ;; # run systemd as init system and image command as a service --sysvinit) Initsystem="sysvinit" ;; --sharecgroup)Sharecgroup="yes" ;; # share /sys/fs/cgroup. default for --systemd, possible use with --openrc --sys-admin) Capsysadmin="yes" ;; # add capability SYS_ADMIN for older systemd versions in image (i.e. debian 9 needs it, debian 10 runs well) --dbus-system) Dbussystem="yes" ;; # set up dbus system daemon -b|--dbus) Dbusrunsession="yes" ;; # run image command with dbus-launch or dbus-run-session --hostdbus) Sharehostdbus="yes" ;; # connect to host DBus ## Environment --showid) Showcontainerid="yes" ;; # output of container id on stdout --showenv) Showdisplayenvironment="yes" ;; # output of display number and cookie file on stdout. Catch with ~$ read xdenv < <(x11docker --showenv) --showpid1) Showcontainerpid1pid="yes" ;; # output of host PID of container PID 1 --env) store_runoption env "${2:-}" # set environment variables shift ;; --vt) Newxvt="${2:-}" ; shift ;; # set virtual console to use --display) Newdisplaynumber=${2:-} # display number to use [ "$(echo $Newdisplaynumber | cut -c1)" = ":" ] && Newdisplaynumber="$(echo $Newdisplaynumber | cut -c2-)" shift ;; --keymap) Xkblayout="${2:-}" ; shift ;; # keymap layout for xkbcomp. Compare /usr/share/X11/xkb/symbols --xtest) Xtest="yes" ;; --xcomposite) Xcomposite="yes" ;; ## X Authentication --xhost) Xhost="$2" ; shift ;; # custom xhost setting on new X server --no-auth) Xauthentication="no" ;; # disable cookie authentication -o|--no-xhost) Noxhost="yes" ;; # disable any access granted by xhost ## host folders -m|--home) Sharehosthome="yes" ;; # share folder ~/x11docker/Imagename with container --homedir) Sharehosthome="yes" # set host folder to share as home folder instead of ~/x11docker/NAME Containeruserhosthome="${2:-}" ; shift ;; --homebasedir) Homebasefolder="${2:-}" ; shift ;; --cachebasedir) Cachebasefolder="${2:-}" ; shift ;; # set cache folder instead of default $Cachebasefolder --cachedir) Cachebasefolder="${2:-}" ; shift note "Option --cachedir is deprecated. Please use instead: --cachebasedir" ;; --sharedir) store_runoption volume "${2:-}" # share host directory shift ;; ## docker options --hostipc) Sharehostipc="yes" ;; # docker run option --ipc=host --hostnet) Sharehostnet="yes" ;; # docker run option --net=host --cap-default) Capdropall="no" ;; # don't use --cap-drop=ALL --name) Containername="${2:-}" ; shift ;; # set container name ## miscellaneous --ps) Preservecachefiles="yes" ;; # preserve container instead of removing it with 'docker run --rm' --westonini) Customwestonini="${2:-}" ; shift ;; # custom weston.ini #### special options not starting X or docker --launcher|--starter) Createlauncher="yes" ;; # create application launcher on desktop and exit --cleanup) Cleanup="yes" ;; # check for orphaned containers and files owned by root, created by docker) --install|--update|--update-master|--remove) Installermode="${1:-}" ;; # installer --wmlist) echo $Wm_all ; exit 0 ;; # special option for x11docker-gui to retrieve list of window managers #### deprecated --trusted) Shareclipboard="yes" warning "Option --trusted is deprecated. To allow trusted cookies for --hostdisplay use option --clipboard. Fallback: enabling option --clipboard." ;; --untrusted) warning "Option --untrusted is deprecated." Trusted="no" ;; # create untrusted cookies # --xtest) warning "Option --xtest is deprecated." # Xtest="yes" ;; # allow extension XTEST. default for xvfb, xdummy and xpra --no-xtest) warning "Option --no-xtest is deprecated." Xtest="no" ;; # disable extension XTEST. default for most X server options --stdout) note "Option --stdout is deprecated. stdout of container application will always be displayed." ;; --stderr) note "Option --stderr is deprecated. stderr of container application will always be displayed." ;; -Q) note "Option -Q is deprecated. stdout and stderr of container application will always be displayed." ;; --silent) Silent="yes" note "Option --silent is deprecated. Use --quiet instead." ;; -N|--nothing) Xserver="--tty" ; Autochooseserver="no" warning "Option -N, --nothing is deprecated. Please use -t, --tty instead." ;; # Do not provide any X nor Wayland ##### custom docker options / image name + image command --) 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" ] && Imagecommand="$Imagecommand '${1:-}'" shift done ;; '') ;; *) error "Unknown option ${1:-} Parsed options: $Parsedoptions" ;; esac shift done return 0 } main() { trap finish EXIT trap finish_sigint SIGINT exec 3>&2 # stderr channel for warning(), error(), note(), debugnote() and --verbose exec 4>&2 # stderr channel for --stderr declare_variables parse_options "$@" check_runmode # modes: run image, or host command, or X only # --exe, --xonly [ "$Silent" = "yes" ] && exec 3>/dev/null # --silent [ "$Debugmode" = "yes" ] && { # --debug set -Eu trap 'traperror $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]})' ERR } verbose -d " x11docker version: $Version docker version: $($Dockerexe --version 2>&1) Host system: $(source /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-$Hostsystem}") Command: $0 $(for Line in "$@"; do echo -n "'$Line' " ; done) Parsed options: $Parsedoptions" # check host, create cache check_host # get some infos about host system check_hostuser # find unprivileged host user # --hostuser create_cachefiles # create cache files owned by unprivileged user # --cachebasedir check_hostxenv # check X environment from host # 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" ] || 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 multiple options, change settings if needed option_messages # some messages depending on options, but not changing anything # container user [ "$Createcontaineruser" = "yes" ] && { check_containeruser # unprivileged user in container # --user check_containerhome # create persistant container home on host # --home, --homedir, --homebasedir } # some checks and setup setup_verbosity # create summary logfile, start verbose output # --verbose, --stdout, --stderr setup_fifo # open message channels for container, dockerrc, xinitrc and watchpidlist() check_screensize # size of host X and of new X server # --size check_windowmanager # WM for single apps in e.g. Xephyr # --wm [ "$Sharegpu" = "yes" ] && setup_gpu # --gpu [ "$Sharewebcam" = "yes" ] && setup_webcam # --webcam [ "$Sharecups" = "yes" ] && setup_printer # --printer [ "$Pulseaudiomode" ] && setup_sound_pulseaudio # --pulseaudio [ "$Sharealsa" = "yes" ] && setup_sound_alsa # --alsa [ "$Shareclipboard" = "yes" ] && setup_clipboard # --clipboard #### Create command to run X server [and/or Wayland compositor] [ "$Xserver" = "--xorg" ] && [ -z "$Newxvt" ] && check_vt # --vt: find free tty/virtual terminal for Xorg { [ "$Xserver" = "--xdummy" ] || [ "$Xpravfb" = "Xdummy" ] ; } && create_xdummyxorgconf check_newxenv # find free display, create $Newxenv create_xcommand # set up start command for X server # all X server and Wayland options [ "$Xcommand" ] && verbose -d "X server command: $Xcommand" [ "$Compositorcommand" ] && verbose -d "Compositor command: $Compositorcommand" check_terminalemulator # find terminal emulator like xterm for error messages and 'docker pull' check_passwordfrontend # check for su/sudo/gksu/pkexec etc. # --pw [ "$Runsinterminal" = "no" ] && [ "$Passwordneeded" = "yes" ] && warning "You might need to run x11docker in terminal for password prompt if prompting for password with a GUI fails." verbose -d "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 Running X and other user commands with: $Mksu Terminal for password frontend: $Passwordterminal Running on console: $Hosttty Running over SSH: $Hostssh" [ "$Winsubsystem" ] && verbose -d "Running on Windows subsystem: $Winsubsystem Path to subsystem: $(convertpath --windows $Winsubpath) Mount path in subsystem: $Winsubmount/" #### Create docker command [ "$X11dockermode" = "run" ] && { # core setup of docker command setup_initsystem # init in container. Default: tini # --tini, --systemd, --openrc, --sysvinit, --no-init 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 echo "$Dockercommand" >> $Dockercommandfile verbose -d "Generated 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 x11docker.CMD.sh to run image command in container 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. 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 waitfor_xserver [ "$Showdisplayenvironment" = "yes" ] && echo $Newxenv # --showenv [ "$Xpraservercommand" ] && start_xpra # --xpra, --xpra-xwayland case $X11dockermode in run) start_docker ;; # (default) exe) start_hostexe ;; # --exe, --xonly esac [ "$Pulseaudiomode" = "tcp" ] && start_pulseaudiotcp # --pulseaudio=tcp debugnote "Process tree of x11docker: $(pstree -p $$ 2>&1 ||:)" } <&0 & storepid $! containershell #### Start X server [and/or Wayland compositor] [ "$Compositorcommand" ] && start_compositor start_xserver saygoodbye main } main "$@" #### ToDo notes for development todo() { # BUG --xpra-xwayland/py3 fails with tor-browser, but not with wine pcmanfm # BUG notify-send in dockerrc does not show up # BUG mywatch "ps -p $Xpraserverpid will fail on busybox systems # BUG mcookie not available on play-with-docker # BUG --dbus-system: slow startup (90s timeout) e.g. in arch and debian buster containers for unknown reasons. # BUG --interactive with --systemd, --runit, --openrc, --sysvinit: no job control in shell # BUG x11docker/deepin: new builds have strange issues # BUG x11docker/fluxbox on arch host: background missing, sometimes no context menu. where is the difference? # BUG error message window in wayland fails: xterm: no display. should use konsole # BUG: x11docker/xwayland in a nested setup: where is the X socket ?? # Enhancement: # --update: regard possible location in /opt? # x11docker-gui: menu entry management? persistant command storage? # --group-add: further checks? mismatch messagebus-101-systemd-journal, works nonetheless. # further checks of pam.d # check multimonitor behaviour # MSYS2/Cygwin/WSL: # needs test: # --pulseaudio # --wayland: Nested setup in WSL with weston might work if socket sharing succeeds: # x11docker --stderr --exe -- x11docker --wayland x11docker/xfce xfce4-terminal # not implemented, but maybe possible: # --sharessh: socket sharing impossible, but maybe similar setup? # --printer: CUPS not available, but lpr in Cygwin. # impossible: # --alsa # --webcam # --systemd, --sharecgroup, elogind # --hostuser # useless: # --showpid1 (shows pid in docker VM) # check xhost +localhost # --weston/x: allow tty switch/option --vt if running as root? # --systemd: try to avoid xhost +SI:localuser:$Containeruser, needed by deepin, it does not recognize XAUTHORITY # --no-init no-new-privileges switchuser: exec fails, but why? # check out capsh. replace su with capsh? (missing in alpine) But how to trigger login? # check echo especially in sh scripts, replace with printf if needed (#25) # avoid possible confusions --home --sharedir. --sharedir mounts can overlap --home folder. What about softlinks with existing target in --home? Maybe avoid /home/$USER at all # --lang: find out locale package names for several distris for documentation # further check of xpra server crashes with jess/atom and chromium. --mmap=no avoids the bug: xpra bug report? # --keymap does not work on tty with --kwin and --kwin-xwayland. No idea how to set it. # bug report to docker about --volume waylandsocket in --volume # --xpra-xwayland, xdummy-xwayland: use kwin-wayland as fallback for missing weston? # --xorg: getty and autologin to avoid Xwrapper.config changes? # --wayland --user/--hostuser: wayland socket access denied due to XDG_RUNTIME_DIR file access permissions # --nxagent 3.5.0: Mageia 6: seamless mode fails # fedora: SElinux issue: '--security-opt label=type:container_runtime_t': need more restrictive setting # https://unix.stackexchange.com/questions/386767/selinux-and-docker-allow-access-to-x-unix-socket-in-tmp-x11-unix # --xdummy --gpu on tty allows real resolutions only # --xorg: check custom systemd start of X #7 # check X in container #7 # some tests with Xephyrglamor=no ### BUG collection: 3rd party bugs # BUG segmentation faults in gnome3 based desktops: pantheon budgie gnome3 # BUG Xwayland does not always sit at 0.0 on multiple outputs. # bugreport: https://bugzilla.redhat.com/show_bug.cgi?id=1498665 # BUG --kwin*: wrong fullscreen and crashes in gnome-wayland, strange in weston, WAYLAND_DISPLAY="" does not help, probably bug in kwin # BUG scale>1 Xwayland in Weston is too large (Xwayland bug), rendering issues on tty (switching scaled/unscaled Xwayland on keyboard/mouse events) # bugreport: https://bugzilla.redhat.com/show_bug.cgi?id=1498669 # BUG x11docker-gui in weston freezes weston in combo boxes. Weston bug ? QT3/4 bug? # BUG debian bug report lightdm/sddm contra gdm, dm can crash on tty switch if multiple graphical sessions are running : }