#! /usr/bin/env bash # x11docker # Run GUI applications and desktop environments in Docker containers. # # - Runs additional X servers to circumvent common X security leaks. # - Restricts container capabilities to enhance container security. # - Container user is same as host user to avoid root in container. # - Features e.g. sound, hardware acceleration and data storage. # # Run 'x11docker --help' or scroll down to read usage information. # More documentation at: https://github.com/mviereck/x11docker Version="7.6.3-beta" Packagedversion="no" # Set to "yes" if you want to package x11docker. This disables installation options. # --enforce-i: Enforce running in interactive mode to allow commands tty and weston-launch in special setups. (deprecated) grep -q -- "--enforce-i" <<< "$*" && case $- in *i*) set +H ;; *) exec bash --noprofile --norc --noediting -i -- "$0" "$@" ;; esac usage() { # --help: show usage information echo " x11docker: Run GUI applications and desktop environments in containers. Supports docker, podman, and (experimental) nerdctl. Can run X servers from host or in containers of x11docker/xserver. Can also provide X servers to host applications. Usage: To run a container on a new X server: x11docker IMAGE x11docker [OPTIONS] IMAGE [COMMAND] x11docker [OPTIONS] -- IMAGE [COMMAND [ARG1 ARG2 ...]] x11docker [OPTIONS] -- CUSTOM_RUN_OPTIONS -- IMAGE [COMMAND [ARG1 ARG2 ...]] To run a host application on a new X server: x11docker [OPTIONS] --backend=host COMMAND x11docker [OPTIONS] --backend=host -- COMMAND [ARG1 ARG2 ...] x11docker [OPTIONS] --backend=host -- -- COMMAND [ARG1 ARG2 ...] -- [ARG3] To run only an empty new X server: x11docker [OPTIONS] --xonly x11docker always runs a fresh container from image and discards it afterwards. Runs on Linux and (with some restrictions) on MS Windows. Not adapted for macOS. Optional features: * GPU hardware acceleration * Sound with pulseaudio or ALSA * Clipboard sharing * Printer access * Webcam access * Persistent home folder * Wayland support * Language locale creation * Several init systems and DBus in container * Support of several container runtimes and backends 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. To switch between docker, podman and nerdctl use option --backend. x11docker sets up an unprivileged container user with password 'x11docker' and restricts container capabilities. Some applications might behave different than with a regular 'docker run' command due to these security restrictions. Achieve a less restricted setup with --cap-default or --sudouser. Dependencies on host: For core functionality x11docker only needs bash, an X server and one of docker, podman or nerdctl. Depending on chosen options x11docker might need some additional tools. It checks for them on startup and shows messages if some are missing. * Most recommended: Provide image x11docker/xserver to run X or Wayland in container. The image contains all X related dependencies. Otherwise provide on host: * Recommended to allow security and convenience: X servers: xpra Xephyr nxagent Xorg X tools: xauth xclip xrandr xhost xinit * Additional for advanced GPU support: weston Xwayland xpra xdotool See also: https://github.com/mviereck/x11docker/wiki/Dependencies Dependencies in image: No dependencies in image except for a few feature options. Most important: --gpu: OpenGL/MESA packages, collected often in 'mesa-utils' package. --pulseaudio: Needs pulseaudio on host and pulseaudio client libs in image. --printer: Needs cups on host and cups client libs in image. See also: https://github.com/mviereck/x11docker/wiki/Dependencies Options: (short options do not accept arguments) --help Display this message and exit. --license Show license of x11docker (MIT) and exit. --version Show x11docker version and exit. Basic settings: -d, --desktop Indicate a desktop environment in image. -i, --interactive Run with an interactive tty to allow shell commands. Useful with commands like bash. --backend=BACKEND Container backend to use. BACKEND can be: docker (recommended for rootful) (default) podman (recommended for rootless and rootful) nerdctl (experimental) host Run a host application, no container. --rootless [=yes|no] Use (or disallow) rootless backend. Default behaviour without option --rootless: --backend=docker: rootful unless DOCKER_HOST is set. --backend=podman: rootless except started as root. --backend=nerdctl: rootless except started as root --xc [=yes|no|BACKEND] Run X server in container of x11docker/xserver. BACKEND can specify one of docker|podman|nerdctl. --xonly Only start empty X server. Host integration: --alsa [=ALSA_CARD] Sound with ALSA. You can define a desired sound card with ALSA_CARD. List of available sound cards: aplay -l -c, --clipboard [=yes|no|oneway|superv|altv] Share clipboard with host. Possible arguments: yes: Share clipboard in both directions. Includes middle-mouse-click selection. oneway: Copy clipboard from container to host only. Includes middle-mouse-click selection. superv: Keys [SUPER][v] copy clipboard from host to container. Does not copy middle-mouse-click to container. Otherwise same as 'oneway'. altv: Same as 'superv' but using keys [ALT][v]. no: Do not share clipboard. -g, --gpu [=yes|no|iglx|virgl] GPU access for hardware accelerated OpenGL. Works best with open source drivers on host and in image. For closed source nvidia drivers regard terminal output. Direct rendering supported by few X server options only. Arg 'iglx' enables indirect rendering (--xorg only). Arg 'virgl' allows GPU access for all X servers, but with limited performance and with --xc only. -I, --network [=NET] Allow internet access. (i.e. allow Docker default.) For optional argument NET see Docker documentation of docker run option --network. Docker default is bridge. -l, --lang [=LOCALE] Set language variable LANG=LOCALE in container. Without arg LOCALE host variable --lang=\$LANG is used. If LOCALE is missing in image, x11docker generates it with 'localedef' in container (needs 'locales' package). Examples for LOCALE: ru, en, de, zh_CN, cz, fr, fr_BE. -P, --printer [=MODE] Share host printers through CUPS server. Optional MODE can be 'socket' or 'tcp'. Default: socket -p, --pulseaudio [=MODE] Sound with pulseaudio. Needs 'pulseaudio' on host and in image. Optional arg MODE can be 'socket', 'tcp' or 'host'. tcp mode needs network access with --network. --webcam Share host webcam device files. Shared host folders or volumes: -m, --home [=ARG] Create a persistent HOME folder for data storage. Default: Uses ~/.local/share/x11docker/IMAGENAME. ARG can be another host folder or a volume. (~/.local/share/x11docker has a softlink to ~/x11docker.) (Use --homebasedir to change this base storage folder.) --share=ARG Share host file or folder ARG. Read-only with ARG:ro Device files in /dev can be shared, too. ARG can also be a volume instead of a host folder. X server options: --auto Automatically choose X server (default). Influenced notably by options --desktop, --gpu, --wayland, --wm. -h, --hostdisplay Share host display :0. Quite bad container isolation! Least overhead of all X server options. -a, --xpra Nested X server supporting seamless and --desktop mode. --xpra2 Like --xpra --xc, but runs xpra client on host. -A, --xpra-xwayland Like --xpra, but supports option --gpu. --xpra2-xwayland Like --xpra2, but supports option --gpu. -n, --nxagent Nested X server supporting seamless and --desktop mode. Faster than --xpra, but can have compositing issues. -y, --xephyr Nested X server for --desktop mode. Without --desktop a host window manager will be provided (option --wm). -Y, --weston-xwayland Desktop mode like --xephyr, but supports option --gpu. Runs from console, within X and within Wayland. -x, --xorg Core Xorg server. Runs ootb from console. Switch tty with ..... Always switch to a black tty before switching to X to avoid crashes. Special X server options: -t, --tty Terminal only mode. Does not run an X or Wayland server. --xvfb Invisible X server using Xvfb. Can be used for custom access with xpra or VNC. -X, --xwayland Blanc Xwayland, needs a running Wayland compositor. --xwin X server to run in Cygwin/X on MS Windows. --runx X server wrapper for VcXsrv and Xwin on MS Windows. Wayland instead of X: -W, --wayland Automatically set up a Wayland environment. Chooses one of following options and regards --desktop. -T, --weston Weston without X for pure Wayland applications. Runs in X, in Wayland or from console. -K, --kwin KWin without X for pure Wayland applications. Runs in X, in Wayland or from console. -H, --hostwayland Share host Wayland without X for pure Wayland apps. X and Wayland appearance options: --border [=COLOR] Draw a colored border in windows of --xpra[-xwayland]. Argument COLOR can be e.g. 'orange' or '#F00'. Thickness can be specified, too, e.g. 'red,3'. Default: 'blue,1' --dpi=N dpi value (dots per inch) to submit to X clients. Influences font size of some applications. -f, --fullscreen Run in fullscreen mode. --output-count=N Multiple virtual monitors for Weston or KWin. --rotate=N Rotate display (--xorg, --weston and --weston-xwayland) Allowed values: 0, 90, 180, 270, flipped, flipped-90, flipped-180, flipped-270. (flipped means mirrored) --scale=N Scale/zoom factor N for xpra, Xorg or Weston. Allowed for --xpra* and --xorg: 0.25...8.0. Allowed for --weston and --weston-xwayland: 1...9. --size=WxH Screen size of new X server (e.g. 800x600). -w, --wm [=ARG] Provide a host window manager to container applications. Possible ARG: host: autodetection of a host window manager. COMMAND: command of a desired host window manager. none: Run without a window manager. Same as --desktop. -F, --xfishtank Show fish tank on new X server. X and Wayland special configuration: --checkwindow [=ARG] Run container until all X windows are closed. If ARG is provided, run container as long as 'grep' can find ARG in output of 'xwininfo -root -children'. This option helps to keep alive containers with self-forking applications like gnome-terminal or to stop endless running ones like chromium. --clean-xhost Disable xhost access policies on host display. --composite [=yes|no] Enable or disable X extension Composite. Default is yes except for --nxagent. Can cause or fix issues with some applications on nxagent. --display=N Use display number N for new X server. --keymap=LAYOUT Set keyboard layout for new X server, e.g. de, us, ru. For possible LAYOUT look at /usr/share/X11/xkb/symbols. --vt [=N] Use vt / tty N. Without N search an unused tty. --westonini=FILE Custom weston.ini for --weston and --weston-xwayland. --xhost [=STR] Set \"xhost STR\" on new X server (see 'man xhost'). Without STR will set: +SI:localuser:\$USER (Use with care. '--xhost=+' allows access for everyone). --xoverip [=yes|no|listentcp|socat] Connect to X over TCP network. Special setups only, usually only enabled by x11docker itself. yes: Use listentcp if possible, otherwise socat. no: Use shared unix socket (default). listentcp: Use X option '-listen tcp'. socat: Use socat to create a fake TCP connection. --xauth [=yes|trusted|untrusted|no] Configure X cookie authentication. Possible arguments: yes|trusted: Enable cookie authentication with trusted cookies. (General x11docker default.) untrusted: Untrusted cookie for untrusted apps limiting access to X resources. Useful to avoid MIT-SHM with --hostdisplay. no: Disable cookie authentication. Dangerous! --xtest [=yes|no] Enable or disable X extension XTEST. Default is yes for --xpra and --xvfb, no for other X servers. Needed to allow keyboard and mouse control with xpra. Container user settings: --group-add=GROUP Add container user to group GROUP. --hostuser=USER Run X (and container user) as user USER. Default is result of \$(logname). (x11docker must run as root). --password [=WORD] Change container user password and exit. Interactive input if argument WORD is not provided. Stored encrypted in ~/.config/x11docker/passwd. --sudouser [=nopasswd] Allow su and sudo for container user. Use with care, severe reduction of default x11docker security! Optionally passwordless sudo with argument nopasswd. Default password is '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. Container capabilities: In most setups x11docker sets --cap-drop=ALL --security-opt=no-new-privileges and shows warnings if doing otherwise. Custom capabilities can be added with --cap-add=CAP after -- --cap-default Allow default container capabilities. Includes --newprivileges=yes. --ipc [=ARG] Without ARG sets run option --ipc=host. (Discouraged) For other possible ARG see docker run reference. --limit [=FACTOR] Limit CPU and RAM usage of container to currently free RAM x FACTOR and available CPUs x FACTOR. Allowed range is 0 < FACTOR <= 1. Default for --limit without argument FACTOR: 0.5 --newprivileges [=yes|no|auto] Set or unset run option --security-opt=no-new-privileges. Default with no argument is 'yes'. Default for most cases is 'no'. Container init system, elogind and DBus daemon: --dbus [=system] Run DBus user session daemon for container command. With argument 'system' also run a DBus system daemon. (To run a DBus system daemon rather use one of --init=systemd|openrc|runit|sysvinit ) --hostdbus Connect to DBus user session from host. --init [=INITSYSTEM] Run an init system as PID 1 in container. Solves the zombie reaping issue. INITSYSTEM can be: tini: Default. Mostly present as docker-init on host. x11docker might as well use catatonit provided by podman. systemd: systemd in container. sysvinit: sysvinit in container. runit: runit in container openrc: openrc in container s6-overlay: s6-overlay in container. none: No init system, CMD will be PID 1. --sharecgroup Share /sys/fs/cgroup. Allows elogind in container if used with one of --init=openrc|runit|sysvinit Container special configuration: --env VAR=value Set custom environment variable VAR=value --name=NAME Specify container name NAME. --no-entrypoint Disable ENTRYPOINT in image to allow other commands, too --no-setup No x11docker setup in running container. Disallows several other options. See also --user=RETAIN. --runtime=RUNTIME Specify container runtime. Known by x11docker: runc: Docker default runtime. crun: Fast replacement for runc written in C. nvidia: Runtime for nvidia/nvidia-docker images. sysbox-runc: Runtime for powerful root in container. --shell=SHELL Set preferred user shell. Example: --shell=/bin/zsh --snap Enable support for Docker in snap. --stdin Forward stdin of x11docker to container command. --workdir=DIR Set working directory DIR. Additional commands: (You might need to move them to background with 'CMD &'.) --runasroot=CMD Run command CMD as root in container. --runasuser=CMD Run command CMD with user privileges in container before running image command. --runfromhost=CMD Run host command CMD on new X server. Miscellaneous: --build IMAGE Build an image from a Dockerfile from x11docker repository. Example: 'x11docker --build x11docker/fvwm' Works for all repositories beginning with 'dockerfile' at https://github.com/mviereck?tab=repositories Regards (only) option --backend=BACKEND. --cachebasedir=DIR Custom base folder for cache files. --homebasedir=DIR Custom base folder for option --home. --fallback [=yes|no] Allow or deny fallbacks if a chosen option cannot be fulfilled. By default fallbacks are allowed. --launcher Create application launcher with current options on desktop and exit. You can get a menu entry moving the created .desktop file to ~/.local/share/applications --mobyvm Use MobyVM (for WSL2 only that defaults to Linux Docker). --preset=FILE Read a set of predefined options stored in file FILE. Useful to shortcut often used option combinations. FILE is searched in directory /etc/x11docker/preset, or in directory ~/.config/x11docker/preset. - Multiple lines in FILE are allowed. - Comment lines must begin with # - Local presets supersede global ones in /etc Special case: A preset file with file name 'default' will be applied automatically for all x11docker sessions. Output of parseable information on stdout: Get output e.g. with: read xenv < <(x11docker --printenv x11docker/check) Optional argument FILE allows to print the information into a file. --printenv [=FILE] Print variables to access new display. --printid [=FILE] Print container ID. --printinfofile [=FILE] Print path to internal x11docker info storage. --printpid1 [=FILE] Print host PID of container PID 1. Verbosity options: -D, --debug Debug mode: Show some less verbose debug output and enable rigorous error control. --printcheck Show dependency check messages. -q, --quiet Suppress x11docker terminal messages. -v, --verbose Be verbose. Output of x11docker.log on stderr. -V Be verbose with colored output. Cleanup options (might need root permissions): --cleanup Clean up orphaned containers and cache files. Those can remain if x11docker still runs on system shutdown. Terminates currently running x11docker containers, too. Regards (only) option --backend=BACKEND." case "$Packagedversion" in yes) ;; *) echo " Installation options (need root permissions): --install Install x11docker from current folder. Useful to install from an extracted zip file. --update [=diff] Download and install latest release from github. --update-master [=diff] Download and install latest master version. Optional argument 'diff' shows the difference between installed and new version without installing it. --remove Remove x11docker from your system. Includes --cleanup. Preserves ~/.local/share/x11docker from option --home. --remove-oldprefix Before version 7.6.0 x11docker installed itself into /usr/bin. Now it installs into /usr/local/bin. Use --remove-oldprefix to remove /usr/bin installations." ;; esac echo " Exit codes: 0: Success 64: x11docker error 130: Terminated by ctrl-c other: Exit code of command in container x11docker version: $Version Please report issues and get help at: https://github.com/mviereck/x11docker " } license() { # --license: show license (MIT) echo ' MIT License Copyright (c) 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 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 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" ||: } } || { notify-send "$Title: $Message" 2>/dev/null } || { warning "Could not display message on X: $Message" return 1 } return 0 } debugnote() { # show debug output $* [ "$Debugmode" = "yes" ] && [ "$Verbose" != "yes" ] && echo "${Colblue}DEBUGNOTE[$(timestamp)]:${Colnorm} $*" >&${FDstderr} logentry "DEBUGNOTE[$(timestamp)]: $*" return 0 } error() { # show error message and exit local Message break >/dev/null 2>&1 # just in case error occurred in a loop Message="$* Type 'x11docker --help' for usage information Debug options: '--verbose' (full log) or '--debug' (log excerpt). Logfile will be: $Logfilebackup Please report issues at https://github.com/mviereck/x11docker" Message="$(rmcr <<< "$Message")" # output to terminal echo -e " ${Colredbg}x11docker ERROR:${Colnorm} $Message " >&2 # output to logfile logentry "x11docker ERROR: $Message " saygoodbye error storeinfo test error && waitfortheend storeinfo error=64 # output to X dialogbox if not running in terminal [ "$Runsinterminal" = "no" ] && [ "$Silent" = "no" ] && export ${Hostxenv:-DISPLAY} && alertbox "x11docker ERROR" "$Message" & finish } logentry() { # write into logfile [ -e "$Logfile" ] && { [ -n "$Logmessages" ] && echo "$Logmessages" >> "$Messagelogfile" 2>/dev/null && Logmessages="" echo "$*" >> "$Messagelogfile" 2>/dev/null : } || Logmessages="$Logmessages $*" } note() { # show notice messages [ "$Verbose" = "yes" ] || echo "${Colgreen}x11docker note:${Colnorm} $* " >&${FDstderr} logentry "x11docker note: $* " } traperror() { # trap ERR: --debug: Output for 'set -o errtrace' debugnote "traperror: Command at Line ${2:-} returned with error code ${1:-}: ${4:-} ${3:-} - ${5:-}" storeinfo error=64 saygoodbye traperror } verbose() { # show verbose messages # only logfile notes here, terminal output is done with tail in setup_verbosity() logentry "x11docker[$(timestamp)]: $* " } warning() { # show warning messages [ "$Verbose" = "yes" ] || echo "${Colyellow}x11docker WARNING:${Colnorm} $* " >&${FDstderr} logentry "x11docker WARNING: $* " } watchmessagefifo() { # watch for messages coming from inside of container # message in fifo must end with :$Messagetype local Line= Message= Messagetype= trap '' SIGINT while [ -e "$Cachefolder" ]; do IFS= read -r Line <&${FDmessage} ||: [ "$Line" ] || sleep 2 # sleep for MSYS2/CYGWIN workaround [ "$Line" ] && Message="$Message $Line" grep -q -E ":WARNING|:NOTE|:DEBUGNOTE|:VERBOSE|:ERROR|:STDOUT" <<< "$Line" && { Messagetype=":$(echo "$Line" | rev | cut -d: -f1 | rev | tr -d ' ')" Message="${Message%$Messagetype}" Message="$(tail -n +2 <<< "$Message")" # remove leading newline case "$Messagetype" in :WARNING) warning "$Message" ;; :NOTE) note "$Message" ;; :DEBUGNOTE) debugnote "$Message" ;; :ERROR) error "$Message" ;; :VERBOSE) [ "-d " = "$(cut -c1-3 <<<"$Message" | head -n1)" ] && debugnote "$(tail -c +4 <<< "$Message")" || verbose "$Message" ;; :STDOUT) echo "$Message" ;; esac Message= Messagetype= } done } #### exit finish() { # trap EXIT routine to clean up background processes and cache local Pid Name Zeit Exitcode Pid1pid= Watchmessagefifopid= Count Xpid1pid # do not finish() in subshell, just give signal to all other processes and terminate subshell [ "$$" = "$BASHPID" ] || { saygoodbye finish-subshell exit 0 } debugnote "Terminating x11docker." saygoodbye "finish" trap - EXIT trap - ERR trap - SIGINT while read -r Line ; do Pid="$(echo "$Line" | awk '{print $1}')" Name="$(echo "$Line" | awk '{print $2}')" debugnote "finish(): Checking pid $Pid ($Name): $(pspid "$Pid" || echo '(already gone)')" checkpid "$Pid" && { case "$Name" in watchmessagefifo) ;; containerpid1) Pid1pid="$Pid" #[ "$Winsubsystem" ] && Pid1pid="" termpid "$Pid1pid" "$Name" || Debugmode="yes" # Give container time for graceful shutdown for Count in 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0; do checkpid "$Pid1pid" || break mysleep "$(awk "BEGIN { print $Count * 0.1 }")" debugnote "finish(): Waiting for container PID 1: $Pid1pid to terminate." done checkpid "$Pid1pid" && unpriv_backend "$Backendbin stop $Containername" ;; Xcontainerpid1) Xpid1pid="$Pid" #[ "$Winsubsystem" ] && Pid1pid="" termpid "$Xpid1pid" "$Name" || Debugmode="yes" # Give container time for graceful shutdown for Count in 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0; do checkpid "$Xpid1pid" || break mysleep "$(awk "BEGIN { print $Count * 0.1 }")" debugnote "finish(): Waiting for X container PID 1: $Xpid1pid to terminate." done checkpid "$Xpid1pid" && unpriv_xcbackend "$Xcontainerbackend stop $Xcontainername" ;; *) termpid "$Pid" "$Name" ;; esac } done < <(tac "$Storepidfile" 2>/dev/null) # --pulseaudio: unload module Pulseaudiomoduleid="$(storeinfo dump pulseaudiomoduleid)" [ "$Pulseaudiomoduleid" ] && unpriv "pactl unload-module '$Pulseaudiomoduleid'" # --xc --xoverip: remove possible internal network setup_xcnetwork remove # Check if container is still running -> docker stop case "$Backend" in docker|podman|nerdctl) containerisrunning && { Debugmode="yes" debugnote "finish(): Container still running. Executing 'docker stop'. Will wait up to 15 seconds for docker to finish." unpriv_backend "$Backendbin stop $Containername" >> "$Containerlogfile" 2>&1 Zeit="$(date +%s)" while :; do containerisrunning || break debugnote "finish(): Waiting for container to terminate ..." sleep 1 [ 15 -lt $(($(date +%s) - $Zeit)) ] && break done containerisrunning && { Exitcode="64" debugnote "finish(): Container did not terminate as it should. Will not clean cache to avoid file permission issues. You can remove the new container with command: docker rm -f $Containername Afterwards, remove cache files with: rm -R $Cachefolder or let x11docker do the cleanup work for you: x11docker --cleanup" } || debugnote "finish(): Container terminated successfully" } ;; esac # Stop watching for messages, check others again while read -r Line ; do Pid="$(echo "$Line" | awk '{print $1}')" Name="$(echo "$Line" | awk '{print $2}')" checkpid "$Pid" && termpid "$Pid" "$Name" checkpid "$Pid" && { # should never happen warning "Failed to terminate pid $Pid ($Name): $(pspid "$Pid" ||:)" storeinfo error=64 } done < <(tac "$Storepidfile" 2>/dev/null) Exitcode=$(storeinfo dump error) Exitcode="${Exitcode:-0}" debugnote "x11docker exit code: $Exitcode" storeinfo test cmdexitcode && { Exitcode=$(storeinfo dump cmdexitcode) debugnote "CMD exit code: $Exitcode" } # backup of logfile in $Cachebasefolder [ -e "$Logfile" ] && { [ "$Verbose" ] && sleep 1 unpriv "cp '$Logfile' '$Logfilebackup'" case "$Winsubsystem" in WSL1|WSL2) [ "$Mobyvm" = "yes" ] && unpriv "cp -T '$Logfilebackup' '$Hostuserhome/.cache/x11docker/x11docker.log'" ;; esac #unpriv "rmcr '$Logfilebackup'" } # softlink to X unix socket in container case "$Xcontainer" in yes) [ -L "/tmp/.X11-unix/X$Newdisplaynumber" ] && rm "/tmp/.X11-unix/X$Newdisplaynumber" [ -L "$XDG_RUNTIME_DIR/wayland-$Newdisplaynumber" ] && rm "$XDG_RUNTIME_DIR/wayland-$Newdisplaynumber" ;; esac # close file descriptors mysleep 0.2 for Descriptor in ${FDcmdstdin} ${FDmessage} ${FDstderr} ${FDtimetosaygoodbye} ${FDwatchpid} ; do exec {Descriptor}>&- done # remove cache files [ "$Preservecachefiles" = "no" ] && grep -q cache <<< "$Cachefolder" && grep -q x11docker <<< "$Cachefolder" && [ "x11docker" != "$(basename "$Cachefolder")" ] && unpriv "rm -f -R '$Cachefolder'" case "$Runssourced" in yes) return "$Exitcode" ;; *) exit "$Exitcode" ;; esac } finish_sigint() { # trap SIGINT to activate debug mode on finish() local Pid1pid X11dockerpid X11dockerpid="$(storeinfo dump x11dockerpid)" Debugmode="yes" [ "$$" = "$X11dockerpid" ] && { debugnote "Received SIGINT" storeinfo error=130 finish : } || { debugnote "Received SIGINT in subshell $SHLVL: $$" kill -s SIGINT "$X11dockerpid" trap - EXIT exit 130 } } saygoodbye() { # create file signaling watching processes to terminate debugnote "time to say goodbye ($*)" [ -e "$Timetosaygoodbyefile" ] && echo timetosaygoodbye >> "$Timetosaygoodbyefile" [ -e "$Timetosaygoodbyefifo" ] && echo timetosaygoodbye >> "$Timetosaygoodbyefifo" return 0 } #### watching processes checkpid() { # check if PID $1 is active #ps -p ${1:-} >/dev/null 2>&1 [ -e "/proc/${1:-NONSENSE}" ] } containerisrunning() { # check if container is running storeinfo test containerid || return 1 case "$Mobyvm" in no) checkpid "$(storeinfo dump pid1pid)" ;; yes) unpriv_backend "$Backendbin inspect '$(storeinfo dump containerid)'" >/dev/null 2>&1 ;; esac } pspid() { # ps -p $1 --no-headers # On some systems ps does not have option --no-headers. # On some systems (busybox) ps -p is not supported ### FIXME # return 1 if not found LC_ALL=C ps -p "${1:-}" 2>/dev/null | grep -v 'TIME' } rocknroll() { # check whether x11docker session is still running [ -s "$Timetosaygoodbyefile" ] && return 1 [ -e "$Timetosaygoodbyefile" ] || return 1 return 0 } setonwatchpidlist() { # add PID $1 to watchpidlist() debugnote "watchpidlist(): Setting pid ${1:-} on watchlist: ${2:-}" echo "${1:-}" >> "$Watchpidfifo" # add to list of background processes grep -q CONTAINER <<< "${1:-}" || storepid "${1:-}" "${2:-}" } storepid() { # store pid $1 and name $2 of process in file $Storepidfile. # Store pid and process name of background processes in a file # Used in finish() to clean up background processes # Store: # $1 Pid # $2 codename # Test for stored pid or codename: # $1 test # $2 pid or codename # Dump stored pid: # $1 dump # $2 codename case "${1:-}" in dump) grep -w "${2:-}" "$Storepidfile" | cut -d' ' -f1 ;; test) grep -q -w "${2:-}" "$Storepidfile" ;; *) echo "${1:-NOPID}" "${2:-NONAME}" >> "$Storepidfile" debugnote "storepid(): Stored pid '${1:-}' of '${2:-}': $(pspid "${1:-}" ||:)" ;; esac } termpid() { # kill PID $1 with codename $2 # TERM debugnote "termpid(): Terminating ${1:-} (${2:-}): $(pspid "${1:-}" ||:)" checkpid "${1:-}" && { kill "${1:-}" 2>/dev/null : } || return 0 mysleep 0.1 checkpid "${1:-}" && mysleep 0.4 || return 0 # KILL debugnote "termpid(): Killing ${1:-} (${2:-}): $(pspid "${1:-}" ||:)" checkpid "${1:-}" && kill -s KILL "${1:-}" 2>/dev/null mysleep 0.2 checkpid "${1:-}" && { note "Failed to terminate ${1:-} (${2:-}): $(ps -u -p "${1:-}" 2>/dev/null | tail -n1)" return 1 } return 0 } waitfortheend() { # wait for end of x11docker session # signal is byte in $Timetosaygoodbyefifo # decent read to wait for signal to terminate case "$Usemkfifo" in yes) while rocknroll; do bash -c "read -n1 <${FDtimetosaygoodbye}" && saygoodbye timetosaygoodbyefifo || sleep 1 done ;; no|"") # Reading from fifo fails on Windows, workaround while rocknroll; do sleep 2 done ;; esac return 0 } watchpidlist() { # watch list of important pids # terminate x11docker if a PID in $Watchpidlist terminates # serves mainly watching X server, Wayland compositor, container and hostexe # echo PIDs to watch into >{FDwatchpid} (setonwatchpidlist()) local Pid= Containername= Line= Watchpidlist= trap '' SIGINT while rocknroll; do # check for new Pid once a second read -t1 Pid <&${FDwatchpid} ||: [ "$Usemkfifo" = "no" ] && sleep 2 # read does not wait if not a fifo # Got new pid [ "$Pid" ] && { [ "${Pid:0:9}" = "CONTAINER" ] && { # Workaround for MS Windows where the pid cannot be watched Containername="${Pid#CONTAINER}" debugnote "watchpidlist(): Watching Container: $Containername" } || { Watchpidlist="$Watchpidlist $Pid" debugnote "watchpidlist(): Watching pids: $(for Line in $Watchpidlist; do pspid "$Line" || echo "(pid $Line not found)" ; done)" } } # check all stored pids for Pid in $Watchpidlist; do [ -e "/proc/$Pid" ] || { debugnote "watchpidlist(): PID $Pid has terminated" saygoodbye "watchpidlist $Pid" } done # Container PID not watchable in MSYS2/Cygwin/WSL1. [ "$Containername" ] && { unpriv_backend "$Backendbin inspect '$Containername'" >/dev/null || { debugnote "watchpidlist(): Container $Containername has terminated" saygoodbye "watchpidlist $Containername" } } done saygoodbye "watchpidlist" } #### more or less general routines askyesno() { # ask Yes/no question. Default 'yes' for ENTER, timeout with 'no' after 60s local Choice read -t60 -n1 -p "(timeout after 60s assuming no) [Y|n]" Choice [ "$?" = '0' ] && { [[ "$Choice" == [YyJj]* ]] || [ -z "$Choice" ] && return 0 } return 1 } check_envvar() { # allow only chars in string $1 that can be expected in environment variables # Allows only chars in "a-zA-Z0-9_:/.,@=-" # Option -w allows whitespace, too. Can be needed for PATH. # Char * as in LS_COLORS is not allowed to avoid abuse. # Replaces forbidden chars with X and returns 1 # Returns 0 if no change occurred. # Echoes result. local Newvar Space= case "${1:-}" in -w) Space=" " ; shift ;; esac Newvar="$(printf %s "${1:-}" | LC_ALL=C tr -c "a-zA-Z0-9_:/.,@=${Space}-" "X" )" printf %s "$Newvar" printf "\n" [ "$Newvar" = "${1:-}" ] && return 0 debugnote "check_envvar(): Input string has been changed. Result: $Newvar" return 1 } check_parent_sshd() { # check whether pid $1 runs in SSH session local Wanted_pid="${1:-}" Process_line local Return= ps -p 1 >/dev/null 2>&1 || { debugnote "check_parent_sshd(): Failed to check for sshd. ps -p not supported." return 1 } while [ "$Wanted_pid" -ne 1 ] ; do Process_line="$(ps -f -p "$Wanted_pid"| tail -n1)" Wanted_pid="$(echo "$Process_line" | awk '{print $3}')" [[ $Process_line =~ sshd ]] && Return=0 [ "$Return" ] && break done return "${Return:-1}" } cookiebaker() { # create an X cookie without xauth # $1 DISPLAY # Write directly to file, bash cannot store nullbytes in a string. # Based on https://stackoverflow.com/questions/70932880/what-is-the-internal-format-of-xauthority-file # and chapter 6.2.5 in https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Desktop-generic/LSB-Desktop-generic/libx11-ddefs.html local Display local Address Addresslength Displaynumber Displaynumberlength Data Part Code Display="${1:-$DISPLAY}" Address="$(printf "%s" "$Display" | cut -d: -f1)" case "$Address" in "") Address="$(hostname)/unix" Addresslength="$(strlenhex "$Address")" ;; *.*.*.*) Data="$Address" Address="" while [ "$(printf "%s" "$Data" | wc -c)" -gt 0 ]; do Part="$( printf "%s" "$Data" | cut -d. -f1)" Address="${Address}\x$(printf "%x" "$Part")" Data="$( printf "%s" "$Data" | cut -s -d. -f2-)" done Addresslength="4" ;; *) Addresslength="$(strlenhex "$Address")" ;; esac Displaynumber="$(printf "%s" "$Display" | cut -d: -f2)" Displaynumber="$(printf "%s" "$Displaynumber" | cut -d. -f1)" Displaynumberlength="$(strlenhex "$Displaynumber")" Data="$(makecookie)" while [ "$(printf "%s" "$Data" | wc -c)" -gt 0 ]; do Part="$( printf "%s" "$Data" | cut -c1-2)" Code="${Code}\x$Part" Data="$( printf "%s" "$Data" | cut -c3-)" done awk "BEGIN{ printf \"\xFF\xFF\" printf \"\x00\x${Addresslength}\" printf \"${Address}\" printf \"\x00\x${Displaynumberlength}\" printf \"${Displaynumber}\" printf \"\x00\x12\" printf \"MIT-MAGIC-COOKIE-1\" printf \"\x00\x10\" printf \"${Code}\" }" } devicelist_gpu() { # print list of GPU devices find /dev/dri/* /dev/nvidia* /dev/vga_arbiter /dev/nvhost* /dev/nvmap -maxdepth 0 -type c 2>/dev/null ||: } devicelist_input() { # print list of input devices find /dev/input/* -maxdepth 0 -type c 2>/dev/null ||: } download() { # download file at URL $1 and store it in file $2 # Uses wget or curl. If both are missing, returns 1. # With no arguments it checks for curl/wget without downloading. # Download follows redirects. local Downloader= command -v wget >/dev/null && Downloader="wget" command -v curl >/dev/null && Downloader="curl" [ "$Downloader" ] || return 1 [ "${1:-}" ] || return 0 case "$Downloader" in wget) wget "${1:-}" -O "${2:-}" || return 1;; curl) curl -L "${1:-}" --fail --output "${2:-}" || return 1;; esac return $? } escapestring() { # escape special chars of $1 # escape all characters except those described in [^a-zA-Z0-9,._+@=:/-] grep -q "'" <<< "${1:-}" && { error "escapestring(): x11docker cannot escape char ' in : ${1:-}" return 1 } echo "${1:-}" | LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@=:/-]/\\&/g; ' } getrandomnumber() { # get random number # chosen by fair dice roll # guaranteed to be random echo "4" } isint() { # check if $1 is a positive integer #[[ $var =~ ^-?[0-9]+$ ]] [[ "${1:-EMPTY}" =~ ^[0-9]+$ ]] } isnum() { # check if $1 is a number [ "1" = "$(awk -v a="${1:-}" 'BEGIN {print (a == a + 0)}')" ] } makecookie() { # bake a cookie mcookie 2>/dev/null || echo $RANDOM$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM | cut -b1-32 } mygetent() { # custom getent command # fixes #477 #496 command -v getent >/dev/null 2>&1 && getent "$1" "$2" || \ grep "^${2:-NOENTRY}:" < "/etc/${1:-NOFILESPECIFIED}" } mysleep() { # catch cases where sleep only supports integer sleep "${1:-1}" 2>/dev/null || sleep 1 } oneline() { # remove \\ and newline from string echo "${1:-}" | tr -d '\\\n' } parse_inspect() { # parse json of inspect output using python # parse for keys in output of docker|podman|nerdctl inspect. # Uses python json parser. # $1 String containing inspect output # $2...$n Key. For second level keys provide e.g. "jsonstring" "Config" "Cmd" local Parserscript local Jsonstring Keystring Output [[ "$Jqbin" ]] && { Jsonstring="${1:-}" ; shift # If we have an array, get the first item [[ $("$Jqbin" -r 'type' <<< "$Jsonstring" 2>/dev/null) == array ]] && Keystring=".[0]" || Keystring="" # Recursively find key using the sequence of keys while [ $# -gt 0 ]; do Keystring="$Keystring.${1:-}" shift done Output="$("$Jqbin" "$Keystring" <<< "$Jsonstring")" # If we have an array, single-quote each item and join with spaces if [[ $("$Jqbin" -r 'type' <<< "$Output" 2>/dev/null) == array ]]; then Output="$("$Jqbin" -r $'map(tostring | "\'" + . + "\'") | join(" ")' <<< "$Output" )" fi # If we have a string, output the raw contents without quotes if [[ $("$Jqbin" -r 'type' <<< "$Output" 2>/dev/null) == string ]]; then Output="$("$Jqbin" -r '.' <<< "$Output")" fi [ "$Output" = "null" ] && Output="" echo "$Output" return } Parserscript="#! $Pythonbin"$' import json,sys def parse_inspect(*args): """ parse output of docker|podman|nerdctl inspect args: 0: ignored 1: string containing inspect output 2..n: json keys. For second level keys provide e.g. "Config","Cmd" Prints key value as a string. Prints empty string if key not found. A list is printed as a string with \'\' around each element. """ output="" inspect=args[1] inspect=inspect.strip() obj=json.loads(inspect) if "\'list\'" in str(type(obj)): obj = obj[0] for arg in args[2:]: # recursively find the desired object. Command.Cmd is found with args "Command" , "Cmd" try: obj=obj[arg] except: obj="" objtype=str(type(obj)) if "\'list\'" in objtype: for i in obj: output += "\'" + str(i) + "\' " output = output.strip() else: output = str(obj) if output == "None": output="" print(output) parse_inspect(*sys.argv) ' echo "$Parserscript" | $Pythonbin - "$@" || warning "parse_inspect(): Error while parsing json for ${2:-} ${3:-} ${4:-} ${5:-} ${6:-} (Line $BASH_LINENO)" } storeinfo() { # store some information for later use # store and provide pieces of information # replace entry if codeword is already present # Store as codeword=string: # $1 codeword=string # Dump stored string: # $1 dump # #2 codeword # Drop stored string: # $1 drop # #2 codeword # Test for codeword: (return 1 if not found) # $1 test # $2 codeword # # note: sed -i causes file permission issues if called in container in Cygwin, compare ticket #187 # chmod 666 for $Sharefolder could probably fix that. (FIXME) # [ -e "$Storeinfofile" ] || return 1 case "${1:-}" in dump) grep "^${2:-}=" "$Storeinfofile" | sed "s/^${2:-}=//" ;; # dump entry drop) sed -i "/^${2:-}=/d" "$Storeinfofile" ;; # drop entry test) grep -q "^${2:-}=" "$Storeinfofile" ;; # test for entry *) # store entry debugnote "storeinfo(): ${1:-}" grep -q "^$(echo "${1:-}" | cut -d= -f1)=" "$Storeinfofile" && { sed -i "/^$(echo "${1:-}" | cut -d= -f1)=/d" "$Storeinfofile" # drop possible old entry } echo "${1:-}" >> "$Storeinfofile" ;; esac } rmcr() { # remove carriage return to translate DOS/Windows newlines into UNIX newlines # convert stdin if $1 is empty. Otherwise convert file $1. case "${1:-}" in "") sed "s/$(printf "\r")//g" ;; *) sed -i "s/$(printf "\r")//g" "${1:-}" ;; esac } strlenhex() { # print byte length of string $1 as hex value printf '%x' "$(printf "%s" "${1:-}" | wc -c)" } timestamp() { # print HH:MM:SS,NNN date +%T,%N | cut -c1-12 return 0 } unspecialstring() { # replace special chars of $1 with - # Replace all characters except those described in "a-zA-Z0-9_" with a '-'. # Replace newlines, too. # Allow additional chars in $2 # Remove leading and trailing '-' # Avoid double '--' # Return empty string if only special chars are given. printf %s "${1:-}" | LC_ALL=C tr -cs "a-zA-Z${2:-}0-9_" "-" | sed -e 's/^-// ; s/-$//' } verlt() { # version number check $1 less than $2 [ "${1:-}" = "${2:-}" ] && return 1 || { verlte "${1:-}" "${2:-}" && return 0 || return 1 ; } } verlte() { # version number check $1 less than or equal $2 [ "${1:-}" = "$(echo -e "${1:-}\n${2:-}" | sort -V | head -n1)" ] && return 0 || return 1 } wincmd() { # execute a command on MS Windows with cmd.exe MSYS2_ARG_CONV_EXCL='*' cmd.exe /C "${@//&/^&}" | rmcr } #### file routines convertpath() { # convert unix and windows paths # $1: Mode: # windows echo Windows path - result: c:/path # unix echo unix path - result: /c/path # subsystem echo path within subsystem - result: /cygdrive/c/path or /path or /mnt/c/path # volume echo --volume compatible syntax - result: 'unixpath':'containerpath':rw (or ":ro") # container echo path of volume in container - result: /path # share echo path of $Sharefolder/file in container - result: /containerpath # $2: Path to convert. Arbitrary syntax, can be C:/path, /c/path, /cygdrive/c/path, /path # Can have suffix :rw or :ro. If none is given, return with :rw # $3: Optional for mode volume: containerpath local Mode Path Drive= Readwritemode Readwritemode_mount 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" [ "$Readwritemode" = ":ro" ] && Readwritemode_mount=",readonly" || Readwritemode_mount="" # replace ~ with HOME Path="$(sed "s%~%${Hostuserhome:-${HOME:-}}%" <<< "$Path")" # share: Replace $Sharefolder with $Sharefoldercontainer [ "$Mode" = "share" ] && { [ -z "$Path" ] && { echo "" return 0 } echo "${Sharefoldercontainer}${Path#$Sharefolder}" return 0 } # replace \ with / and // with / Path="$(sed 's%//%/%g ; s%\\%/%g' <<< "$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")" Path="${Path:-"/"}" } # Given format is C:/ [ "$(cut -c2 <<< "$Path")" = ":" ] && { Drive="$(cut -c1 <<< "$Path")" Path="$(cut -c3- <<< "$Path")" } # change C to c Drive="${Drive,}" # docker volume (same for Windows and Unix) [ "${Path:0:1}" = "/" ] || { case "$Mode" in unix|subsystem|windows) echo "$Path" ; debugnote "convertpath() $Mode: Docker volumes do not have a specified path on host: $Path" ;; volume) echo "'$Path':'${3:-/$Path}'${Readwritemode}" ;; mount) echo "type=volume,source='$Path',target='${3:-/$Path}'${Readwritemode_mount}" ;; container) echo "${3:-/$Path}" ;; esac return 0 } Containerpath="$Path" [ "$Createcontaineruser" = "no" ] || { [ "$Sharehome" = "host" ] || { [ -n "$Containeruserhome" ] && grep -q "^$Containeruserhome" <<< "$Path" && Containerpath="$(sed "s%^$Containeruserhome%/home.host%" <<< "$Containerpath")" } [ "$Containerpath" = "$Containeruserhosthome" ] && [ "$Persistanthomevolume" != "$Containeruserhosthome" ] && Containerpath="/home.host/$Containeruser" } # not on Windows [ -z "$Winsubsystem" ] && { case "$Mode" in unix|subsystem) echo "$Path" ;; windows) warning "convertpath(): Nonsense path conversion $Mode: $Path" ; return 1 ;; #volume) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; #container) echo "${3:-$Path}" ;; volume) echo "'$Path':'${3:-$Containerpath}'${Readwritemode}" ;; mount) echo "type=bind,source='$Path',target='${3:-/$Containerpath}'${Readwritemode_mount}" ;; container) echo "${3:-$Containerpath}" ;; esac return 0 } case "$Winsubsystem" in WSL1) [ -z "$Drive" ] && case "$Mode" in windows|unix|volume) debugnote "convertpath(): Request of WSL path: $Path" grep -q "$Cachefolder" <<< "$Path" || { [ "$Readwritemode" = ":rw" ] && warning "Request of Windows path to path within WSL: $Path Write access from Windows host to WSL files can damage the WSL file system. Read-only access is ok. Option --share: You can add :ro to the path to allow read-only access. Example: --share='$Path:ro'" } ;; esac ;; esac case "$Drive" in "") # Path points into subsystem Path="${Path#"$Winsubpath"}" Drive="$(cut -c2 <<<"$Winsubpath")" case "$Mode" in windows) echo "${Drive^}:$(cut -c3- <<< "$Winsubpath")$Path" ;; unix) echo "$Winsubpath$Path" ;; subsystem) echo "$Path" ;; volume) case "$Mobyvm" in no) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; yes) echo "'$Winsubpath$Path':'${3:-$Path}'$Readwritemode" ;; esac ;; mount) case "$Mobyvm" in no) echo "type=bind,source='$Path',target='${3:-$Path}'$Readwritemode_mount" ;; yes) echo "type=bind,source='$Winsubpath$Path',target='${3:-$Path}'$Readwritemode_mount" ;; esac ;; container) echo "${3:-$Path}" ;; esac ;; *) # Path outside of subsystem case "$Mode" in windows) echo "${Drive^}:$Path" ;; unix) echo "/$Drive$Path" ;; subsystem) echo "$Winsubmount/$Drive$Path" ;; volume) echo "'/$Drive$Path':'${3:-/$Drive$Path}'$Readwritemode" ;; mount) echo "type=bind,source='/$Drive$Path',target='${3:-/$Drive$Path}'$Readwritemode_mount" ;; container) echo "${3:-/$Drive$Path}" ;; esac ;; esac return 0 } getwslpath() { # get path to currently running WSL system # Fork from https://github.com/Microsoft/WSL/issues/2578#issuecomment-354010141 local RUN_ID= BASEPATH= RUN_ID="/tmp/$(makecookie)" # Mark our filesystem with a temporary file having an unique name. touch "${RUN_ID}" powershell.exe -Command '(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath.replace(":", "").replace("\", "/")' | while IFS= read -r BASEPATH; do # Remove trailing whitespaces. BASEPATH="${BASEPATH%"${BASEPATH##*[![:space:]]}"}" # Build the path on WSL. BASEPATH="/mnt/${BASEPATH,}/rootfs" # Current WSL instance doesn't have an access to its mount from within # itself despite all others are available. That's the hacky way we're # using to determine current instance. # # The second of part of the condition is a fallback for a case if our # trick will stop working. For that we've created a temporary file with # an unique name and now seeking it among all WLSs. if ! ls "${BASEPATH}" > /dev/null 2>&1 || [ -f "${BASEPATH}${RUN_ID}" ]; then echo "${BASEPATH}" # You can create and simultaneously run multiple WSL instances, comment # out the "break", run this script within each one and it'll return only # single value. break fi done rm "${RUN_ID}" return 0 } get_xpath() { # --xc: path to X server case "$Xcontainer" in yes) echo "/usr/bin/${1:-}" ;; no) command -v "${1:-}" ;; esac } mkfile() { # create file $1 owned by $Hostuser :> "${1:-}" || return 1 chown "$Hostuser" "${1:-}" || return 1 chgrp "$Hostusergid" "${1:-}" || return 1 chmod 644 "${1:-}" || return 1 [ -n "${2:-}" ] && { chmod "${2:-}" "${1:-}" || return 1 } return 0 } mkfolder() { # create folder $1 owned by $Hostuser local Step Path= Return= for Step in $(tr '/' '\n' <<< "${1:-}" | grep .) ; do Path="$Path/$Step" [ -d "$Path" ] || { unpriv "mkdir -p '$Path'" || Return=1 chmod "${2:-755}" "$Path" || Return=1 } [ "$Return" ] && break done return ${Return:-0} #unpriv "mkdir -p '${1:-}'" || return 1 } myrealpath() { # real path of possible symlink [ -z "$*" ] && return 1 command -v realpath >/dev/null && { realpath "$*" } || { [ -h "$*" ] && warning "Could not check for symbolic links. Please install 'realpath' (package 'coreutils'), or provide real file path instead of symbolic link path. Possible symbolic link: $*" echo "$*" ### FIXME: Maybe workaround with ls return 1 } } waitforlogentry() { # wait for entry $3 in logfile $2 of application $1 # $1 is the application we are waiting for to be ready # $2 points to logfile # $3 keyword to wait for # $4 possible error keywords # $5 time to wait in seconds or infinity. default: 60 local Startzeit Uhrzeit Dauer Count=0 Schlaf local Errorkeys="${4:-}" local Warten="${5:-60}" local Error= Startzeit="$(date +%s ||:)" Startzeit="${Startzeit:-0}" [ "$Warten" = "infinity" ] && Warten=32000 debugnote "waitforlogentry(): ${1:-}: Waiting for logentry \"${3:-}\" in $(basename "${2:-}")" while ! grep -q "${3:-}" <"${2:-}" ; do Count="$(( Count + 1 ))" Uhrzeit="$(date +%s ||:)" Uhrzeit="${Uhrzeit:-0}" Dauer="$(( Uhrzeit - Startzeit ))" Schlaf="$(( Count / 10 ))" [ "$Schlaf" = "0" ] && Schlaf="0.5" mysleep "$Schlaf" [ "$Dauer" -gt "10" ] && debugnote "waitforlogentry(): ${1:-}: Waiting since ${Dauer}s for log entry \"${3:-}\" in $(basename "${2:-}")" [ "$Dauer" -gt "$Warten" ] && error "waitforlogentry(): ${1:-}: Timeout waiting for entry \"${3:-}\" in $(basename "${2:-}") Last lines of $(basename "${2:-}"): $(tail "${2:-}")" # grep -i -q -E 'xinit: giving up|unable to connect to X server|Connection refused|server error|Only console users are allowed|Failed to process Wayland|failed to create display|] fatal:' <"${2:-}" && \ [ "$Errorkeys" ] && grep -i -q -E "$Errorkeys" <"${2:-}" && \ error "waitforlogentry(): ${1:-}: Found error message in logfile. Last lines of logfile $(basename "${2:-}"): $(tail "${2:-}")" rocknroll || { debugnote "waitforlogentry(): ${1:-}: Stopped waiting for ${3:-} in $(basename "${2:-}") due to terminating signal." Error=1 break } done [ "$Error" ] && return 1 debugnote "waitforlogentry(): ${1:-}: Found log entry \"${3:-}\" in $(basename "${2:-}")." return 0 } writeaccess() { # check if useruid $1 has write access to folder $2 local dirVals= gMember= IFS= IFS=$'\t' read -a dirVals < <(stat -Lc "%U %G %A" "${2:-}") [ "$(id -u "$dirVals")" == "${1:-}" ] && [ "${dirVals[2]:2:1}" == "w" ] && return 0 [ "${dirVals[2]:8:1}" == "w" ] && return 0 [ "${dirVals[2]:5:1}" == "w" ] && { gMember="$(groups "${1:-}" 2>/dev/null)" [[ "${gMember[*]:2}" =~ ^(.* |)${dirVals[1]}( .*|)$ ]] && return 0 } [ "w" = "$(getfacl -pn "${2:-}" | grep "user:${1:-}:" | rev | cut -c2)" ] && return 0 || return 1 } #### special jobs of x11docker buildimage() { # --build: build image from x11docker repository Dockerfile # Build image $1 from x11docker repository local Wwwpath Buildpath Imagename # check image name, should have leading 'x11docker/' Imagename="${1:-}" grep -q "x11docker" <<< "$Imagename" || Imagename="x11docker/$Imagename" # remote and local paths Wwwpath="https://raw.githubusercontent.com/mviereck/dockerfile-$(tr / - <<< "$Imagename")/master" Buildpath="${HOME:-/tmp}/x11docker-build-$(unspecialstring "$Imagename")" grep -q "dockerfile-x11docker-" <<< "$Wwwpath" || error "Option --build: x11docker only supports building of images provided at x11docker repository https://github.com/mviereck" mkdir -p "$Buildpath" cd "$Buildpath" download || error "Option --build: Please install 'curl' or 'wget' to allow a download" note "Download of $Wwwpath/Dockerfile" download "$Wwwpath/Dockerfile" "$Buildpath/Dockerfile" || error "Option --build: Did not find a Dockerfile for $Imagename at $Wwwpath Option --build only supports Dockerfiles from https://github.com/mviereck. For other sources please download Dockerfile and use 'docker build'." [ "$Imagename" = "x11docker/xserver" ] && { note "Download of $Wwwpath/XlibNoSHM.c" download "$Wwwpath/XlibNoSHM.c" "$Buildpath/XlibNoSHM.c" || error "Option --build: Downloading XlibNoSHM.c failed." } note "Building $Imagename" unpriv_backend "$Backendbin build -t $Imagename $Buildpath" || error "Option --build: Building image '$Imagename' failed." cd "${HOME:-/tmp}" rm -rf "$Buildpath" return 0 } cleanup() { # --cleanup : check for non-removed containers and left cache files # Cleans x11docker cache and removes running and stopped x11docker containers. # Does not change --home folders. local Orphanedcontainers= Orphanedfolders= Line= Containerinspect Containerid note "x11docker will check for orphaned containers from earlier sessions for current --backend=$Backend 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/%%" )" case "$Backend" in docker|podman|nerdctl) Orphanedcontainers="$(unpriv_backend "$Backendbin ps -a" | grep x11docker_X | rev | cut -d' ' -f1 | rev)" Orphanedcontainers="$Orphanedcontainers $(find "$Cachebasefolder" -mindepth 2 -maxdepth 2 -type f -name 'container.id' -exec cat {} \;)" Orphanedcontainers="$(env IFS='' echo $Orphanedcontainers)" # check for double entries name/id, check for already non-existing containers for Line in $Orphanedcontainers; do Containerinspect="$(unpriv_backend "$Backendbin inspect '$Line'" 2>/dev/null)" [ -n "$Containerinspect" ] && { Containerid="$(parse_inspect "$Containerinspect" "Id")" Orphanedcontainers="$(sed "s%$Line%$Containerid%" <<< "$Orphanedcontainers")" : } || { Orphanedcontainers="$(sed s/$Line// <<< "$Orphanedcontainers")" } done Orphanedcontainers="$(sort <<< "$Orphanedcontainers" | uniq)" ;; esac [ -z "$Orphanedcontainers$Orphanedfolders" ] && { note "No orphaned containers or cache files found. good luck!" } || { note "Found orphaned containers: $Orphanedcontainers" note "Found orphaned folders in $Cachebasefolder: $Orphanedfolders" for Line in $Orphanedfolders ; do [ -d "$Cachebasefolder/$Line/share" ] && [ ! -s "$Cachebasefolder/$Line/share/timetosaygoodbye" ] && { note "Found possibly active container for cache dir $Line. Will summon it to terminate itself." echo timetosaygoodbye >> "$Cachebasefolder/$Line/share/timetosaygoodbye" } done [ -n "$Orphanedfolders" ] && sleep 3 [ -n "$Orphanedcontainers" ] && { note "Removing containers with: $Backendbin rm -f $Orphanedcontainers" unpriv_backend "$Backendbin rm -f $Orphanedcontainers" 2>&1 } [ -n "$Orphanedfolders" ] && { note "Removing cache files with: rm -R -f $Orphanedfolders" rm -R -f $Orphanedfolders 2>&1 } } # remove internal networks case "$Backend" in docker|podman|nerdctl) for Line in $(unpriv_backend "$Backendbin network ls" | grep x11docker_X); do note "Removing internal network $Line" unpriv_backend "$Backendbin network rm $(awk '{print $1}' <<< "$Line")" done ;; esac Logfile= note "Removing remaining files with: rm -Rf -v \"$Cachebasefolder\"/*" rm -Rf -v "${Cachebasefolder:-NONSENSE}"/* 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." case "$Backend" in docker) unpriv_backend "$Backendbin info" >/dev/null 2>/dev/null || warning "Could not check for $Backend containers. Please run 'x11docker --cleanup' as root to make sure that no orphaned containers are left." ;; esac note "Cleanup ready." } create_launcher() { # --launcher: create application launcher on desktop local Name= command -v xdg-desktop-icon >/dev/null || error "Command 'xdg-desktop-icon' not found. x11docker needs it to place the new icon on your desktop. Please install xdg-utils" note "Will create a new application launcher icon on your desktop. If you move the new file to: $Hostuserhome/.local/share/applications it will appear in your applications menu." Name="$Codename" [ "$Codename" = "xonly" ] && Name="$(echo "$Xserver" | tr -d '-')" Name="${Name% }" read -re -p "Please choose a name for your application launcher: " -i "$Name" Name [ -z "$Name" ] && return 1 ### FIXME: check for valid file name / invalid chars? Parsedoptions_global="${Parsedoptions_global//--launcher/}" Parsedoptions_global="${Parsedoptions_global//--starter/}" mkfile "$Cachefolder/$Name.desktop" { echo "#!/usr/bin/xdg-open [Desktop Entry] # x11docker desktop file Type=Application Name=$Name Exec=x11docker $Parsedoptions_global Icon=x11docker Comment= Categories=System Keywords=docker x11docker $(echo "$Name" | tr -c '[:alpha:][:digit:][:blank:]' ' ' ) " case "$(command -v x11docker)" in "")echo "TryExec=$0 $Parsedoptions_global" ;; *) echo "TryExec=x11docker $Parsedoptions_global" ;; esac } >> "$Cachefolder/$Name.desktop" unpriv "xdg-desktop-icon install --novendor '$Cachefolder/$Name.desktop'" } installer() { # --install, --update, --update-master, --remove: Installer for x11docker # --install: # - copies x11docker 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, regard existing installation location # --update-master: # - download and install latest master version from github, regard existing installation location # --remove # - remove installed files ### FIXME: (--update) ### maybe not install additional files if x11docker is owned by user local Key1= Key2= Oldversion= Newversion= Format= Prefix Prefix="/usr/local" case "${1:-}" in --remove-oldprefix) Prefix="/usr" ;; *) [ -x "/usr/bin/x11docker" ] && warning "Option ${1:-}: Detected x11docker installation in /usr/bin Since version 7.6.0 x11docker defaults to ${Prefix}/bin instead. If this was a custom installation without your package manager, you can remove the old installation as root with: rm /usr/bin/x11docker rm -R /usr/share/doc/x11docker rm /usr/share/man/man1/x11docker.1.gz or with x11docker option --remove-oldprefix" ;; esac [ "$Startuser" != "root" ] && { case "$Winsubsystem" in CYGWIN|MSYS2) ;; *) case "$Installerarg" in diff) ;; *) error "Must run as root to install, update or remove x11docker system wide." return 1 ;; esac ;; esac } # Preparing case "${1:-}" in --install) [ -f "./x11docker" ] || { error "File x11docker not found in current folder. Try 'x11docker --update' instead." return 1 } [ -x "${Prefix}/bin/x11docker" ] && { warning "x11docker seems to be installed already at ${Prefix}/bin/x11docker. Will overwrite existing installation. Consider to use option '--update' or '--update-master' instead." } ;; --update|--update-master) [ -x "${Prefix}/bin/x11docker" ] && { Oldversion="$(${Prefix}/bin/x11docker --version)" note "Current installed version: x11docker $Oldversion at ${Prefix}/bin/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." return 1 } download || { error "Neither wget nor curl found. Need 'wget' or 'curl' for download. Please install wget or curl." return 1 } command -v unzip >/dev/null && Format="zip" command -v tar >/dev/null && Format="tar.gz" [ "$Format" ] || { error "Cannot extract archive. Please install 'unzip' or 'tar'." return 1 } case "${1:-}" in --update-master) note "Downloading latest x11docker master version from github." download "https://codeload.github.com/mviereck/x11docker/$Format/master" "x11docker-update.$Format" || { error "Failed to download x11docker from github." return 1 } ;; --update) download "https://raw.githubusercontent.com/mviereck/x11docker/master/CHANGELOG.md" "CHANGELOG.md" || { error "Failed to download CHANGELOG.md from github." return 1 } Releaseversion="v$(cat "CHANGELOG.md" | grep "## \[" | grep -v 'Unreleased' | head -n1 | cut -d[ -f2 | cut -d] -f1)" note "Downloading latest x11docker release $Releaseversion from github." download "https://codeload.github.com/mviereck/x11docker/$Format/$Releaseversion" "x11docker-update.$Format" || { error "Failed to download x11docker from github." return 1 } ;; esac note "Extracting $Format archive." case "$Format" in zip) unzip "x11docker-update.$Format" ;; tar.gz) tar xzf "x11docker-update.$Format" ;; esac || { error "Failed to extract $Format archive." return 1 } 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)" return 1 } ;; esac # New version number case "${1:-}" in --install|--update|--update-master) Newversion="$(bash "$(pwd)/x11docker" --version)" ;; esac # Changelog excerpt case "${1:-}" in --update) echo "$Oldversion" | grep -q beta && { warning "You are switching from master branch to stable releases. To get latest master beta version, use option --update-master instead" Key1="\[${Newversion}\]" Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" } || { Key1="\[${Newversion}\]" Key2="\[${Oldversion}\]" [ "$Newversion" = "$Oldversion" ] && { Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" note "Version $Newversion was already installed before this update. If you want the latest beta version from master branch, use --update-master." } [ -z "$Oldversion" ] && Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" } ;; --update-master) echo "$Oldversion" | grep -q beta && { Key1="\[Unreleased\]" Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" } || { Key1="\[Unreleased\]" Key2="\[${Oldversion}\]" [ -z "$Oldversion" ] && Key2="https:\/\/github.com\/mviereck\/x11docker\/releases" } ;; esac case "${1:-}" in --update|--update-master) note "Excerpt of x11docker changelog: $(sed -n "/${Key1}/,/${Key2}/p" "CHANGELOG.md" | head -n-1)" ;; esac # Show diff [ "$Installerarg" = "diff" ] && { case "${1:-}" in --update|--update-master) note "Difference of installed version to new version: $ diff -u -s -Z '$(command -v x11docker)' '$(pwd)/x11docker' $(diff -u -s -Z "$(command -v x11docker)" "$(pwd)/x11docker" 2>&1 | sed "s/^+.*/${Colgreenbg}\0${Colnorm}/ ; s/^-.*/${Colredbg}\0${Colnorm}/ ; s/^@.*/${Coluline}\0${Colnorm}/")" ;; esac } # Doing [ -z "$Installerarg" ] && case "${1:-}" in --install|--update|--update-master) note "Installing x11docker to ${Prefix}/bin" cp "x11docker" "${Prefix}/bin/" || { error "Could not copy x11docker to ${Prefix}/bin" return 1 } chmod 755 "${Prefix}/bin/x11docker" || { error "Could not set executable bit on x11docker" return 1 } chown "root:root" "${Prefix}/bin/x11docker" || warning "Could not set ownership 'root:root' to '${Prefix}/bin/x11docker'" note "Installing icon for x11docker with xdg-icon-resource" xdg-icon-resource install --context apps --novendor --mode system --size 64 "$(pwd)/x11docker.png" "x11docker" || warning "Could not install icon for x11docker. Is 'xdg-icon-resource' (xdg-utils) installed on your system?" note "Storing README.md, CHANGELOG.md and LICENSE.txt in ${Prefix}/share/doc/x11docker" mkdir -p "${Prefix}/share/doc/x11docker" && { cp "README.md" "${Prefix}/share/doc/x11docker/" cp "CHANGELOG.md" "${Prefix}/share/doc/x11docker/" cp "LICENSE.txt" "${Prefix}/share/doc/x11docker/" } || note "Error while creating /usr/share/doc/x11docker" note "Storing man page in ${Prefix}/share/man/man1/x11docker.1.gz" command -v gzip >/dev/null && { gzip -c "x11docker.man" > "${Prefix}/share/man/man1/x11docker.1.gz" } || note "Error storing man page." note "Installed x11docker version $Newversion" ;; --remove|--remove-oldprefix) note "Removing x11docker from your system." [ "${1:-}" = "--remove" ] && [ -n "$Backendbin" ] && { note "Running --cleanup" cleanup } [ -x "${Prefix}/bin/x11docker" ] && rm -v "${Prefix}/bin/x11docker" [ -e "${Prefix}/share/doc/x11docker" ] && rm -v -R "${Prefix}/share/doc/x11docker" [ -e "${Prefix}/share/man/man1/x11docker.1.gz" ] && rm -v "${Prefix}/share/man/man1/x11docker.1.gz" [ "${1:-}" = "--remove" ] && xdg-icon-resource uninstall --size 64 "x11docker" ||: note "Will not remove files in your home folder. There might be files left in \$HOME/.local/share/x11docker The symbolic link \$HOME/x11docker might 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 note "Ready." } mkpasswd() { # Create a salted password suitable for /etc/shadow local Password Salt Password="${1:-}" command -v perl >/dev/null || { error "Option --password: command 'perl' not found. perl is needed to generate an encrypted password." return 1 } Salt="$(LC_ALL=C base64 <<< "$RANDOM" | cut -c1-2)" Password=$(perl -le 'my $Salt=shift; my $Password=shift; print crypt($Password, "\$6\$".$Salt."\$")' "$Salt" "$Password") Containeruserpassword="$Password" } set_password() { # option --password: set container user password local Password Password="${1:-}" [ "$Password" = "INTERACTIVE" ] && { read -rs -p "Please type in a new container user password (chars are invisible): " Password echo "" [ -z "$Password" ] && { error "Empty input, password not changed." return 1 } } mkpasswd "$Password" mkfolder "$(dirname "$Passwordfile")" echo "$Containeruserpassword" > "$Passwordfile" chmod 600 "$Passwordfile" note "Option --password: Password changed, exiting." return 0 } #### host integration features check_windowmanager() { # option --wm: search a host window manager # check --wm arguments, adjust mode case "$Xcontainer" in yes) case "$Windowmanagermode" in auto|host) Windowmanagermode="host" Windowmanagercommand="openbox --sm-disable" ;; *) Windowmanagermode="none" ;; esac return 0 ;; esac case "$Windowmanagermode" in ""|none) Windowmanagermode="none" return 0 ;; auto) Windowmanagermode="host" [ -n "$Windowmanagercommand" ] && { command -v "$(cut -d' ' -f1 <<< "$Windowmanagercommand")" >/dev/null && { Windowmanagermode="host" } || { note "Option --wm: Did not find command on host: $Windowmanagercommand" check_fallback } } ;; host) ;; esac [ "$Windowmanagermode" = "none" ] && return 0 # Find a host window manager [ "$Windowmanagercommand" ] || for Windowmanagercommand in $Wm_all WM_NOT_FOUND; do command -v "$Windowmanagercommand" >/dev/null && break done [ "$Windowmanagercommand" = "WM_NOT_FOUND" ] && { Windowmanagercommand="" note "Option --wm: No host window manager found. Please install a supported one. Recommended: $Wm_recommended_nodesktop_light Fallback: Setting --wm=none" check_fallback Windowmanagermode="none" } [ "$Windowmanagermode" = "none" ] && return 0 [ "$Xtest" = "yes" ] && warning "Options --xtest --wm: Did not disable X extension XTEST for X server $Xserver. If your host window manager '$Windowmanagercommand' 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!" # command adjustment for some host window managers case "$(basename "$(cut -d' ' -f1 <<< "$Windowmanagercommand")")" in cinnamon|cinnamon-session) Windowmanagercommand="cinnamon --sm-disable";; compiz) # if none, create minimal config to have usable window decoration and can move windows [ -e "$Hostuserhome/.config/compiz-1/compizconfig/Default.ini" ] || { mkfolder "$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) Windowmanagercommand="enlightenment_start" ;; matchbox) Windowmanagercommand="matchbox-window-manager" ;; mate|mate-session) Windowmanagercommand="mate-session -f" ;; mate-wm) Windowmanagercommand="marco --sm-disable" ;; openbox) Windowmanagercommand="openbox --sm-disable" [ -e "/etc/xdg/openbox/rc.xml" ] && { cp /etc/xdg/openbox/rc.xml "$Sharefolder/openbox-nomenu.rc" sed -i /ShowMenu/d "$Sharefolder/openbox-nomenu.rc" sed -i s/NLIMC/NLMC/ "$Sharefolder/openbox-nomenu.rc" Windowmanagercommand="$Windowmanagercommand --config-file '$Sharefolder/openbox-nomenu.rc'" } ;; esac case "$Windowmanagermode" in host) xtool --check "wmctrl" ;; esac verbose "Detected host window manager: ${Windowmanagercommand:-"(none)"}" return 0 } clean_xhost() { # option --clean-xhost: disable xhost policies on host X [ -z "$Hostdisplay" ] && note "Option --clean-xhost: No host X display found." && return 1 [ -z "$Hostxauthority" ] && warning "Option --clean-xhost: You host X server does not provide an authentication cookie in \$XAUTHORITY. Host applications started after xhost cleanup might fail to start." echo "Option --clean-xhost:" >> "$Xinitlogfile" disable_xhost "DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority" >> "$Xinitlogfile" 2>&1 } create_clipboardrc() { # option --clipboard: create shareclipboard script echo "#! /usr/bin/env bash $(declare -f rocknroll) $(declare -f mysleep) Timetosaygoodbyefile='$Timetosaygoodbyefile' X1auth='DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority WAYLAND_DISPLAY=$Hostwaylandsocket XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR' X2auth='DISPLAY=$Newdisplay XAUTHORITY=$Xclientcookie WAYLAND_DISPLAY=$Newwaylandsocket XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR' " echo ' clipexchange() { # Send clipboard content of one X server to another X server and update on changes. # Run function twice with switched credentials to sync the clipboards. # $1 credentials to access 1. X server # $2 credentials to access 2. X server # $3 selection to use (primary or clipboard) # X credentials look like "DISPLAY=:0 XAUTHORITY=~/.Xauthority" local Selection Targets Imagetarget Clip Clipold Clipold2 Selection="${3:-}" while rocknroll; do export ${1:-} case "$DISPLAY" in "") case "$Selection" in primary) Targets="$(wl-paste --list-types --primary)" ;; clipboard) Targets="$(wl-paste --list-types)" ;; esac ;; *) Targets="$(xclip -out -selection $Selection -t TARGETS 2>/dev/null)" ;; esac [ -n "$Targets" ] && { # check for image clip grep -q "image/png" <<< "$Targets" && Imagetarget="-t image/png" || Imagetarget="" # read content of $Selection of X server $1 export ${1:-} case "$DISPLAY" in "") case "$Selection" in primary) Clip="$(wl-paste --primary $Imagetarget | base64)" ;; clipboard) Clip="$(wl-paste $Imagetarget | base64)" ;; esac ;; *) Clip="$(xclip -out -selection $Selection $Imagetarget | base64)" ;; esac [ -n "$Clip" ] && [ "$Clip" != "$Clipold" ] && { Clipold="$Clip" # send only to clipboard of other X server if it has different content. Important to keep highlighting of text for primary/middle mouse click. export ${2:-} case "$DISPLAY" in "") case "$Selection" in primary) Clipold2="$(wl-paste --primary $Imagetarget | base64)" ;; clipboard) Clipold2="$(wl-paste $Imagetarget | base64)" ;; esac ;; *) Clipold2="$(xclip -out -selection $Selection $Imagetarget | base64)" ;; esac [ "$Clipold2" != "$Clip" ] && { export ${2:-} case "$DISPLAY" in "") case "$Selection" in primary) base64 -d <<< "$Clip" | wl-copy --primary $Imagetarget ;; clipboard) base64 -d <<< "$Clip" | wl-copy $Imagetarget ;; esac ;; *) base64 -d <<< "$Clip" | xclip -in -selection $Selection $Imagetarget ;; esac #echo "SEND $Selection to ${2:-} : $(base64 -d <<< "$Clip")" } } } # wait a bit to avoid high CPU usage mysleep 0.5 done }' case "$Shareclipboard" in yes) # bidirectional echo ' clipexchange "$X1auth" "$X2auth" clipboard & clipexchange "$X1auth" "$X2auth" primary & sleep 1 # copy from 1 to 2 will take precedence at startup clipexchange "$X2auth" "$X1auth" clipboard & clipexchange "$X2auth" "$X1auth" primary & ' ;; oneway) # only container to host echo ' clipexchange "$X2auth" "$X1auth" clipboard & clipexchange "$X2auth" "$X1auth" primary & ' ;; wayland) # limited fallback for wayland clients with --clipboard=yes. host to container only, selection clipboard only. echo ' clipexchange "$X1auth" "$X2auth" clipboard & ' ;; superv|altv) # always container to host, only with super + v or alt +v host to container echo ' sendclip() { # send clipboard content (selection clipboard) from X server $1 to X server $2 # used for --clipboard=superv Targets="$(env ${1:-} xclip -out -selection clipboard -t TARGETS 2>/dev/null)" [ -n "$Targets" ] && { grep -q "image/png" <<< "$Targets" && Imagetarget="-t image/png" || Imagetarget="" env ${1:-} xclip -out -selection clipboard $Imagetarget | env ${2:-} xclip -in -selection clipboard $Imagetarget } } case "${1:-}" in superv|altv) sendclip "$X1auth" "$X2auth" ;; "") clipexchange "$X2auth" "$X1auth" clipboard & clipexchange "$X2auth" "$X1auth" primary & case "'$Shareclipboard'" in altv) Xbindkeycodes=" \"bash '$Clipboardrc' altv\" Mod1 + v " ;; superv) Xbindkeycodes=" \"bash '$Clipboardrc' superv\" Mod4 + v " ;; esac env $X2auth xbindkeys -n -f <(echo "$Xbindkeycodes") & Xbindkeyspid=$! # wait for the end case "'$Usemkfifo'" in yes) read Var <'$Timetosaygoodbyefifo' ;; no) while rocknroll; do sleep 1; done ;; esac kill $Xbindkeyspid ;; esac ' ;; esac } 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. # Possibilities: # - Install NVIDIA driver in image. It must be the very same version as on your host. # The image is not portable anymore. # - x11docker can install NVIDIA driver on the fly in running container. See notes below. # # g $Nvidiainstallerfile nvidia driver file to install in container in containerrootrc # g $Nvidiaversion nvidia driver version on host local Gpudevice Drivername Containerusergroups="$Containerusergroups video render" # share device files while read -r Gpudevice ; do store_runoption volume "$Gpudevice" done < <(devicelist_gpu) # NVIDIA # check for closed source nvidia driver on host, provide automated installation, warn about disadvantages [ -z "$Nvidiaversion" ] && return 0 debugnote "NVIDIA: Detected driver version $Nvidiaversion on host." [ "$Runtime" = "nvidia" ] && { debugnote "NVIDIA: Option --runtime=nvidia: Skipping driver installation." Nvidiainstallerfile="" return 0 } Nvidiainstallerfile="$(find /usr/local/share/x11docker/NVIDIA*$Nvidiaversion*.run $Localsharedir/NVIDIA*$Nvidiaversion*.run 2>/dev/null | tail -n1 )" Nvidiainstallerfile="$(myrealpath "$Nvidiainstallerfile" 2>/dev/null)" [ -e "$Nvidiainstallerfile" ] && { debugnote "NVIDIA: Found proprietary closed source NVIDIA driver installer $Nvidiainstallerfile" [ "$Containersetup" = "no" ] && { note "Options --no-setup --gpu: Cannot install NVIDIA driver with option --no-setup. Fallback: Disabling option --gpu" Sharegpu="no" return 1 } [ "$Capdropall" = "yes" ] && warning "Option --gpu: Installing NVIDIA driver in container requires container privileges that x11docker would drop otherwise. Though, they are still within default docker capabilities." Allownewprivileges="yes" store_runoption cap CHOWN store_runoption cap FOWNER return 0 } Nvidiainstallerfile="" note "Option --gpu: You are using the closed source NVIDIA driver. GPU acceleration will only work if you have installed the very same driver version in image. That makes images less portable. It is recommended to use free open source nouveau driver on host instead. Ask NVIDIA corporation to at least publish their closed source API, or even better to actively support open source driver nouveau. To use the GPU without NVIDIA driver in container, you can use less performant options --gpu=iglx (--xorg only) or --gpu=virgl (needs --xc)." note "Option --gpu: x11docker can try to automatically install NVIDIA driver version $Nvidiaversion in container on every container startup. Drawbacks: Container startup is a bit slower and its security will be reduced. You can look here for a driver installer file: https://www.nvidia.com/Download/index.aspx https://http.download.nvidia.com/ A direct download URL is probably: https://http.download.nvidia.com/XFree86/Linux-x86_64/$Nvidiaversion/NVIDIA-Linux-x86_64-$Nvidiaversion.run If you got a driver, store it at one of the following locations: $Localsharedir/ /usr/local/share/x11docker/ Be aware that the version number must match exactly the version on host. The file name must begin with 'NVIDIA', contain the version number $Nvidiaversion and end with suffix '.run'." return 0 } setup_hostdbus() { # option --hostdbus: connect to host DBus session daemon. warning "Option --hostdbus: Connecting container to host DBus degrades container isolation. Container applications might send malicious requests." Dbusrunsession="no" [ "$DBUS_SESSION_BUS_ADDRESS" ] || { # no running DBus session? command -v dbus-launch >/dev/null && { note "Option --hostdbus: DBUS_SESSION_BUS_ADDRESS is empty. Creating abstract DBus socket with dbus-launch." export $(dbus-launch) } || { note "Option --hostdbus: Is DBus running on host? Did not find an active session and did not find dbus-launch. DBUS_SESSION_BUS_ADDRESS is empty. $Wikipackages" check_fallback return 1 } } grep -q "unix:path" <<< "$DBUS_SESSION_BUS_ADDRESS" && { # DBus socket file case "$Runtime" in kata-runtime) note "Option --hostdbus with a unix socket is not possible with --runtime=$Runtime." check_fallback return 1 ;; *) store_runoption env "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" store_runoption volume "/$(cut -d/ -f2- <<<"$DBUS_SESSION_BUS_ADDRESS")" ;; esac } grep -q "unix:abstract" <<< "$DBUS_SESSION_BUS_ADDRESS" && { # DBus abstract socket (dbus-launch) store_runoption env "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" [ "${DBUS_SESSION_BUS_PID:-}" ] && store_runoption env "${DBUS_SESSION_BUS_PID:-}" [ "${DBUS_SESSION_BUS_WINDOWID:-}" ] && store_runoption env "${DBUS_SESSION_BUS_WINDOWID:-}" [ "$Network" != "host" ] && { note "Option --hostdbus: Did not find a DBus session socket file but an abstract unix socket. To allow access for container, x11docker needs insecure option '--network=host'. Sharing host dbus will fail." check_fallback return 1 } } return 0 } setup_printer() { # option --printer: connect to cups printer server # Default CUPS setups create a unix socket /run/cups/cups.sock as given from 'lpstat -H'. # Sharing this socket and pointing environment variable CUPS_SERVER to it serves most cases. # Possible CUPS network setups need to allow access from container, see note below. local Cupsserver= command -v lpstat >/dev/null || { note "Option --printer: command lpstat not found. Is cups printer server installed on your system? Error: Cannot share access to printer." Sharecupsmode="" return 1 } case "$Sharecupsmode" in auto) Sharecupsmode="socket" [ "$Snapsupport" = "yes" ] && Sharecupsmode="tcp" [ "$Runtime" = "kata-runtime" ] && Sharecupsmode="tcp" #[ "$Runtime" = "sysbox-runc" ] && Sharecupsmode="tcp" ;; esac case "$Sharecupsmode" in tcp) Cupsserver="$Hostip:631" [ "$Network" = "none" ] && { note "Option --printer=tcp does not work without network access. Consider to enable option -I, --network." check_fallback return 1 } ;; socket) Cupsserver="$(lpstat -H)" ;; esac grep -q ":" <<< "$Cupsserver" && { [ "$(cut -d: -f1 <<< "$Cupsserver")" = "localhost" ] && Cupsserver="$Hostip:$(cut -d: -f2 <<< "$Cupsserver")" [ "$(cut -d: -f1 <<< "$Cupsserver")" = "127.0.0.1" ] && Cupsserver="$Hostip:$(cut -d: -f2 <<< "$Cupsserver")" note "Option --printer: Network setup for CUPS detected. Server address: $Cupsserver You may need to allow container access in /etc/cups/cupsd.conf, e.g.: Port 631 # Allow remote access... Order allow,deny Allow 172.17.0.* Allow 172.20.0.* Allow 127.0.0.1 Also you need to allow network access for each printer in a CUPS GUI." } [ "$Cupsserver" ] && store_runoption env "CUPS_SERVER=$Cupsserver" [ -e "$Cupsserver" ] && { [ "$(dirname "$Cupsserver")" = "/run/cups" ] && store_runoption volume "/run/cups" || store_runoption volume "$Cupsserver" } return 0 } setup_sound_alsa() { # option --alsa: share sound devices # Sound with ALSA is directly supported by the kernel and only needs to share devices in /dev/snd. # libasound2 in image is recommended. # 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 "Option --alsa: ALSA sound degrades container isolation. Sharing 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 "Option --alsa: It seems that pulseaudio is running on your host. Pulseaudio can interfere with ALSA sound. Host sound may not work while container is playing sound and vice versa. Alternative: with pulseaudio on host and in image, use option --pulseaudio." [ -d /dev/snd ] && store_runoption volume "/dev/snd" || { warning "Option --alsa: /dev/snd not found. Sound support not possible." check_fallback Sharealsa="no" return 1 } Containerusergroups="$Containerusergroups audio" return 0 } setup_sound_pulseaudio() { # option --pulseaudio: set up pulseaudio connection # Allowing container access to Pulseaudio on host can be done with a shared socket or over TCP. # Sharing host user socket in XDG_RUNTIME_DIR fails since Pulseaudio v12.0. # Instead, a new socket is created with pactl. # TCP module is created after container startup to authenticate it with container IP. # Detailed documentation at: https://github.com/mviereck/x11docker/wiki/Container-sound:-ALSA-or-Pulseaudio # # g $Pulseaudiomode =tcp or =socket or=host: Connect over tcp or with shared socket # g $Pulseaudioport TCP port local Lowerport= Upperport= Setcookie= command -v pactl >/dev/null || { note "Option --pulseaudio: pactl not found. Is pulseaudio installed and running on your host system? Fallback: Disabling option --pulseaudio. You can try option --alsa instead." check_fallback Pulseaudiomode="" return 1 } [ -z "$Pulseaudiomode" ] && Pulseaudiomode="socket" [ "$Pulseaudiomode" = "auto" ] && { Pulseaudiomode="socket" [ "$Containeruser" = "$Hostuser" ] || Pulseaudiomode="tcp" [ "$Runtime" = "kata-runtime" ] && Pulseaudiomode="tcp" [ "$Runtime" = "sysbox-runc" ] && Pulseaudiomode="tcp" [ "$Snapsupport" = "yes" ] && Pulseaudiomode="tcp" unpriv 'LC_ALL=C pactl info | grep -q "User Name: pulse"' && Pulseaudiomode="tcp" [ "$Pulseaudiomode" = "tcp" ] && note "Option --pulseaudio: Enabling TCP mode" } [ "$Pulseaudiomode" = "tcp" ] && [ "$Network" = "none" ] && { note "Option --pulseaudio: Without option -I, --network pulseaudio sound over TCP is not possible but needed in your setup." Pulseaudiomode="" check_fallback return 1 } warning "Option --pulseaudio allows container applications to catch your audio output and microphone input." case "$Pulseaudiomode" in socket|tcp) grep -q -i "PipeWire" <<< "$(unpriv "pactl info")" && note "Option --pulseaudio: Found pipewire sound server. If pulseaudio sound fails, you can try --pulseaudio=tcp or --pulseaudio=host instead." ;; esac case "$Pulseaudiomode" in socket) # create pulseaudio socket Pulseaudiomoduleid="$(unpriv "pactl load-module module-native-protocol-unix socket='$Pulseaudiosocket' 2>&1 ||:")" #Pulseaudiomoduleid="$(unpriv "pactl load-module libpipewire-module-protocol-native socket='$Pulseaudiosocket' 2>&1 ||:")" # fails with pipewire isint "$Pulseaudiomoduleid" && { storeinfo "pulseaudiomoduleid=$Pulseaudiomoduleid" store_runoption env "PULSE_SERVER=unix:$(convertpath share "$Pulseaudiosocket")" Setcookie="yes" } || { note "Option --pulseaudio: command pactl failed with output: '$Pulseaudiomoduleid' Is pulseaudio running at all on your host? Fallback: Disabling option --pulseaudio. You can try option --alsa instead." check_fallback Pulseaudiomode="" return 1 } echo "# Connect to host pulseaudio server using mounted UNIX socket default-server = unix:$(convertpath share "$Pulseaudiosocket") # Prevent a server running in container autospawn = no daemon-binary = /bin/true # Prevent use of shared memory enable-shm = false " >> "$Pulseaudioconf" verbose "Generated pulseaudio client.conf: $(nl -ba < "$Pulseaudioconf")" ;; tcp) read Lowerport Upperport < /proc/sys/net/ipv4/ip_local_port_range 2>/dev/null [ "$Lowerport" ] || Lowerport=33000 [ "$Upperport" ] || Upperport=60000 while : ; do Pulseaudioport="$(shuf -i $Lowerport-$Upperport -n1)" ss -lpn | grep -q ":$Pulseaudioport " || break done [ -e "$Hostuserhome/.config/pulse/cookie" ] && cp "$Hostuserhome/.config/pulse/cookie" "$Pulseaudiocookie" || note "Option --pulseaudio: Did not find cookie $Hostuserhome/.config/pulse/cookie" store_runoption env "PULSE_SERVER=tcp:$Hostip:$Pulseaudioport" Setcookie="yes" ;; host) Pulseaudiosocket="$(unpriv "env LC_ALL=C pactl info" | grep 'Server String' |rev|cut -d' ' -f1|rev)" store_runoption env "PULSE_SERVER=unix:/tmp/pulseaudio.socket.host" Setcookie="yes" ;; esac [ "$Setcookie" = "yes" ] && { [ -e "$Hostuserhome/.config/pulse/cookie" ] && { cp "$Hostuserhome/.config/pulse/cookie" "$Pulseaudiocookie" store_runoption env "PULSE_COOKIE=$(convertpath share "$Pulseaudiocookie")" } || { note "Option --pulseaudio: Did not find cookie $Hostuserhome/.config/pulse/cookie" } } return 0 } setup_vaapi() { # option --gpu: setup for VAAPI and VDPAU video decoding acceleration # support for prime-run / discrete NVIDIA card #394 [ -n "$__NV_PRIME_RENDER_OFFLOAD" ] && DRI_PRIME="1" && store_runoption env "__NV_PRIME_RENDER_OFFLOAD=$__NV_PRIME_RENDER_OFFLOAD" [ -n "$__VK_LAYER_NV_optimus" ] && DRI_PRIME="1" && store_runoption env "__VK_LAYER_NV_optimus=$__VK_LAYER_NV_optimus" [ -n "$__GLX_VENDOR_LIBRARY_NAME" ] && DRI_PRIME="1" && store_runoption env "__GLX_VENDOR_LIBRARY_NAME=$__GLX_VENDOR_LIBRARY_NAME" # VA-API and VDPAU #443 # Infos at https://wiki.archlinux.org/title/Hardware_video_acceleration # check VA-API driver on host [ -z "${LIBVA_DRIVER_NAME:-}" ] && { LIBVA_DRIVER_NAME="$(xtool "vainfo 2>&1 | grep drv_video.so | rev | cut -d/ -f1 | rev" ||:)" LIBVA_DRIVER_NAME="${LIBVA_DRIVER_NAME%"_drv_video.so"}" } # Guess driver based on kernel modules if vainfo is not available [ -z "$LIBVA_DRIVER_NAME" ] && { lspci -k | grep -q -e amdgpu -e radeon && { LIBVA_DRIVER_NAME="${LIBVA_DRIVER_NAME:-radeonsi}" } lspci -k | grep -q nouveau && { LIBVA_DRIVER_NAME="${LIBVA_DRIVER_NAME:-nouveau}" } lspci -k | grep -q nvidia && { LIBVA_DRIVER_NAME="${LIBVA_DRIVER_NAME:-nvidia}" note "Option --gpu: Enabling VA-API video decoding support with NVIDIA NVDECODE. If you want to use NVIDIA VDPAU instead, please run x11docker with '--env LIBVA_DRIVER_NAME=vdpau' or set the variable globally with 'export LIBVA_DRIVER_NAME=vdpau'." } lspci -k | grep -q i915 && { [ -n "${DRI_PRIME:-}" ] || { LIBVA_DRIVER_NAME="${LIBVA_DRIVER_NAME:-i965}" note "Option --gpu: Enabling VA-API intel driver i965. If you want to use the newer iHD intel driver instead, please run x11docker with '--env LIBVA_DRIVER_NAME=iHD' or set the variable globally with 'export LIBVA_DRIVER_NAME=iHD'." } } } case "${LIBVA_DRIVER_NAME:-}" in radeonsi|r600) VDPAU_DRIVER="${VDPAU_DRIVER:-radeonsi}" ;; i965|iHD) VDPAU_DRIVER="${VDPAU_DRIVER:-va_gl}" ;; nouveau) VDPAU_DRIVER="${VDPAU_DRIVER:-nouveau}" ;; nvidia|vdpau) VDPAU_DRIVER="${VDPAU_DRIVER:-nvidia}" ;; "") note "Option --gpu: Could not estimate drivers for VA-API and VDPAU video decoding acceleration support. This will likely fail in container." ;; esac [ -n "${LIBVA_DRIVER_NAME:-}" ] && store_runoption env "LIBVA_DRIVER_NAME=${LIBVA_DRIVER_NAME:-}" [ -n "${VDPAU_DRIVER:-}" ] && store_runoption env "VDPAU_DRIVER=${VDPAU_DRIVER:-}" [ -n "${DRI_PRIME:-}" ] && store_runoption env "DRI_PRIME=${DRI_PRIME:-1}" return 0 } setup_webcam() { # option --webcam: share webcam devices # Webcam devices appear as /dev/video* files. # Unprivileged users need to be in group video. # (This works only if webcam is plugged in before container starts. # Hotplug support would have to be different.) local Webcamdevice [ "$Sharewebcam" = "yes" ] && warning "Option --webcam: Container applications might look at you and also might take screenshots of your Desktop." while read -r Webcamdevice ; do store_runoption volume "$Webcamdevice" done < <(find /dev/video* -maxdepth 0 2>/dev/null || note "Option --webcam: No webcam devices /dev/video* found. Webcam in container will fail.") Containerusergroups="$Containerusergroups video" # at least cheese and gnome-ring need some device information from udev. store_runoption volume "/run/udev/data:ro" return 0 } #### X server setup check_newxenv() { # find free display local Line # find free display number [ "$Newdisplaynumber" ] || { Newdisplaynumber="100" while :; do case "$Xserver" in --xwin|--runx) Newdisplaynumber="$((RANDOM / 10 + 200))" ;; *) Newdisplaynumber="$((Newdisplaynumber + 1))" ;; esac grep -q -x "$Newdisplaynumber" < "$Numbersinusefile" || [ -n "$(find "/tmp/.X11-unix/X$Newdisplaynumber" "/tmp/.X$Newdisplaynumber-lock" "$XDG_RUNTIME_DIR/wayland-$Newdisplaynumber" 2>/dev/null)" ] || break done } echo "$Newdisplaynumber" >> "$Numbersinusefile" # --xoverip [ -z "$Xoverip" ] && { case "$Mobyvm" in yes) Xoverip="listentcp" ;; esac case "$Runtime" in kata-runtime) note "Option --runtime=$Runtime works only with X over IP. Enabling option --xoverip." Xoverip="yes" ;; esac case "$Xserver" in --xwin|--runx) Xoverip="listentcp" [ "$Network" = "none" ] && { note "Option $Xserver needs network access. Enabling option -I, --network." check_fallback Network="" } ;; --hostdisplay) [ "$Hostxoverip" = "yes" ] && { note "Option $Xserver needs --xoverip because host X server DISPLAY==$Hostdisplay uses X over TCP/IP. Enabling option --xoverip." Xoverip="listentcp" } ;; esac } case "$Xoverip" in yes) case "$Xserver" in --hostdisplay) grep -q -- '^:' <<< "$Hostdisplay" && Xoverip="socat" || Xoverip="listentcp" ;; --xwayland|--weston-xwayland|--kwin-xwayland|--xpra-xwayland|--xpra2-xwayland) Xoverip="socat" ;; *) Xoverip="listentcp" ;; esac ;; esac Xoverip="${Xoverip:-no}" case "$Xoverip" in socat|listentcp) case "$Backend" in docker|podman|nerdctl) case "$Xcontainer" in yes) ;; no) [ "$Network" = "none" ] && error "Option --xoverip needs network access. Please enable option -I, --network. Alternatively provide image x11docker/xserver (option --xc) to allow an isolated internal network." ;; esac ;; esac ;; esac # --xc --xoverip Xcnetworkname="x11docker_X${Newdisplaynumber}_network_${Cachenumber}" ### # set $Newdisplay (DISPLAY of container) and $Newxsocket case "$Xserver" in --hostdisplay) case "$Xoverip" in listentcp) [ "$(cut -c1 <<< "$Hostdisplay")" = ":" ] && Newdisplay="${Hostip}${Hostdisplay}" || Newdisplay="$Hostdisplay" ;; no) Newdisplay="$Hostdisplay" Newdisplaynumber="$(echo "$Newdisplay" | cut -d: -f2 | cut -d. -f1)" ;; socat) case "$Xcontainer" in yes) Newdisplay="XCONTAINERIP:$Newdisplaynumber" ;; no) Newdisplay="$Hostip:$Newdisplaynumber" ;; esac ;; esac Newxsocket="$Hostxsocket" ;; --weston|--kwin|--hostwayland|--tty) Newdisplay="" Newxsocket="" Xclientcookie="" Xservercookie="" ;; *) case "$Xoverip" in socat|listentcp) case "$Xcontainer" in yes) Newdisplay="XCONTAINERIP:$Newdisplaynumber" ;; no) Newdisplay="$Hostip:$Newdisplaynumber" ;; esac ;; no) Newdisplay=":$Newdisplaynumber" Newxlock="/tmp/.X$Newdisplaynumber-lock" [ -n "$(find "$Newxsocket" "$Newxlock" 2>/dev/null)" ] && error "Display $Newdisplay is already in use." ;; esac Newxsocket="/tmp/.X11-unix/X$Newdisplaynumber" ;; esac # set $Newwaylandsocket case "$Xserver" in --weston*|--kwin*|--xpra-xwayland|--xpra2-xwayland) Newwaylandsocket="wayland-$Newdisplaynumber" ;; --hostwayland|--xwayland) Newwaylandsocket="$Hostwaylandsocket" ;; esac return 0 } check_screensize() { # check physical and virtual screen size (also option --size) local Line= Xrandroutput= [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && verbose "check_screensize(): Skipping check on pure Wayland environment" # check whole display size, can include multiple monitors [ -n "$Hostdisplay" ] && [ -z "$Newxvt" ] && { Xrandroutput="$(xtool "xrandr 2>>$Xinitlogfile" ||:)" [ -n "$Xrandroutput" ] && { Line="$(grep current <<< "$Xrandroutput" | head -n1 | cut -d, -f2)" Maxxaxis="$(echo "$Line" | cut -d' ' -f3)" Maxyaxis="$(echo "$Line" | cut -d' ' -f5)" [ -z "$Maxxaxis" ] && { Maxxaxis="$(grep ' connected' <<< "$Xrandroutput" | head -n1 | cut -dx -f1 | rev | cut -d' ' -f1 | rev)" Maxyaxis="$(grep ' connected' <<< "$Xrandroutput" | head -n1 | cut -dx -f2 | cut -d' ' -f1 | cut -d+ -f1)" } } } [ -n "$Screensize" ] && { Xaxis="${Screensize%x*}" Yaxis="${Screensize#*x}" } [ -z "$Maxxaxis" ] && { Maxxaxis="1920" Maxyaxis="1200" [ "$Maxxaxis" -lt "${Xaxis:-0}" ] && Maxxaxis="$Xaxis" [ "$Maxyaxis" -lt "${Yaxis:-0}" ] && Maxyaxis="$Yaxis" } # size for windowed desktops, roughly maximized relative to primary monitor case "$Xserver" in --xpra*) [ "$Desktopmode" = "yes" ] && Xserver="${Xserver}-desktop" ;; esac [ -z "$Screensize" ] && [ -z "$Newxvt" ] && case "$Xserver" in --xephyr|--weston-xwayland|--weston|--kwin|--kwin-xwayland|--nxagent|--xpra*-desktop) Xaxis="$((Maxxaxis-96))" Yaxis="$((Maxyaxis-96))" Xaxis="$(( $(( $Xaxis / 8 )) * 8 ))" # avoid grey edge in Xwayland, needs full byte x width ;; esac Xserver="${Xserver%-desktop}" [ "$Fullscreen" = "yes" ] && [ -z "$Newxvt" ] && Screensize="${Maxxaxis}x${Maxyaxis}" [ -z "$Xaxis" ] && { Xaxis="$Maxxaxis" Yaxis="$Maxyaxis" } # 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%.*}" } [ -z "$Screensize" ] && case "$Xserver" in --xorg) ;; # Xorg autodetects screen size, preset only with option --size --weston-xwayland|--weston|--kwin|--kwin-xwayland) [ -z "$Newxvt" ] && Screensize="${Xaxis}x${Yaxis}" ;; *) Screensize="${Xaxis}x${Yaxis}" ;; esac xtool --check cvt && { Modeline="$(xtool "cvt '$Xaxis' '$Yaxis'" | tail -n1 | cut -d' ' -f2-)" Modeline="$(echo $Modeline | cut -d_ -f1)\" $(echo $Modeline | cut -d_ -f2- | cut -d' ' -f2-)" } verbose "Virtual screen size: $Screensize" verbose "Physical screen size: $(grep Screen <<< "$Xrandroutput" ||:)" # create set of Modelines if needed case "$Xserver" in --xpra|--xpra2|--xvfb) xtool --check cvt && Modelinefile="$(create_modelinefile "${Maxxaxis}x${Maxyaxis}")" ;; esac return 0 } check_vt() { # option --xorg: find free vt / tty local Line= Ttyinuse= # if started from console, use current tty case "$Newxvt" in "") tty -s && [ "$Runsonconsole" = "yes" ] && { Newxvt="${Newxvt#/dev/tty}" } ;; auto) Newxvt="" ;; *) return 0 ;; esac # check ttys currently in use [ "$Newxvt" ] || { for Line in $(find /sys/class/vc/vcsa*); do Ttyinuse="$Ttyinuse ${Line#/sys/class/vc/vcsa} " done debugnote "check_vt(): TTYs currently known to kernel: $Ttyinuse" } [ "$Newxvt" ] && grep -q " $Newxvt " <<< "$Ttyinuse" && warning "TTY $Newxvt seems to be already in use." # try to find free tty within range of 8..12 [ "$Newxvt" ] || { for ((Newxvt=8 ; Newxvt<=12 ; Newxvt++)) ; do grep -q " $Newxvt " <<< "$Ttyinuse" || break done } # try to find free tty within range of 1..7 [ "$Newxvt" ] || { for ((Newxvt=1 ; Newxvt<=7 ; Newxvt++)) ; do grep -q " $Newxvt " <<< "$Ttyinuse" || break done } # try to find free tty with fgconsole. Fails without privileges within X. [ "$Newxvt" ] || Newxvt="$(fgconsole --next-available 2>/dev/null)" [ "$Newxvt" ] || Newxvt="$(fgconsole --next-available 2>/dev/null < "/dev/tty${XDG_VTNR:-}")" # try to find free tty within range of 13..63 [ "$Newxvt" ] || { for ((Newxvt=13 ; Newxvt<=63 ; Newxvt++)) ; do grep -q " $Newxvt " <<< "$Ttyinuse" || break done } [ "$Newxvt" ] || error "Could not identify a free tty for --xorg. You can specify a desired tty number N with option --vt=N." [ "${XDG_VTNR:-}" ] && [ "$Hostdisplay$Hostwaylandsocket" ] && note "Current X server $Hostdisplay runs on tty ${XDG_VTNR:-}. Access it with [CTRL][ALT][F${XDG_VTNR:-}]." [ "${Newxvt:-999}" -gt "12" ] && { fgconsole --next-available 1>/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 X server $Xserver $Newdisplay will run on tty $Newxvt. Access it with [CTRL][ALT][F$Newxvt]." } return 0 } check_xcontainer() { # option --xc: check backend, rootless and image x11docker/xserver local Auto= Fail= # workaround: X server options not supported by --xc already caught here if they are specified on cli case "$Xserver" in --tty|--hostwayland) check_xdepends "$Xserver" && Xcontainer="no" ;; esac # check if X container should run rootful or rootless (for tty access it must be rootful) case "$Backendrootless" in yes) { [ "$Xserver" = "--xorg" ] || [ -n "$Newxvt" ] ; } && Xcrootless="no" || Xcrootless="yes" experimental "run X container rootless" && Xcrootless="yes" ;; no) Xcrootless="no" ;; esac # check if image x11docker/xserver is available case "$Xcontainer" in no) ;; yes|auto) case "$Backend" in docker|podman|nerdctl) Xcrootless="${Xcrootless:-$Backendrootless}" Xcontainerbackend="$Backend" unpriv_xcbackend "$Xcontainerbackend inspect $Xcontainerimage" >/dev/null 2>&1 || Fail="yes" ;; host|proot) Xcrootless="yes" unpriv_xcbackend "docker inspect $Xcontainerimage" >/dev/null 2>&1 && Xcontainerbackend="docker" && Xcrootless="yes" [ -z "$Xcontainerbackend" ] && { Xcrootless="no" unpriv_xcbackend "docker inspect $Xcontainerimage" >/dev/null 2>&1 && Xcontainerbackend="docker" && Xcrootless="no" } [ -z "$Xcontainerbackend" ] && { unpriv_xcbackend "podman inspect $Xcontainerimage" >/dev/null 2>&1 && Xcontainerbackend="podman" && Xcrootless="${Xcrootless:-yes}" } [ "$Xcontainer" = "yes" ] && [ -z "$Xcontainerbackend" ] && Xcontainerbackend="docker or podman" && Fail="yes" ;; esac ;; docker) Xcontainerbackend="$Xcontainer" Xcontainer="yes" [ -z "$Xcrootless" ] && { [ -n "$DOCKER_HOST" ] && Xcrootless="yes" || Xcrootless="no" } unpriv_xcbackend "$Xcontainerbackend inspect $Xcontainerimage" >/dev/null 2>&1 || Fail="yes" ;; podman|nerdctl) Xcontainerbackend="$Xcontainer" Xcontainer="yes" Xcrootless="${Xcrootless:-yes}" unpriv_xcbackend "$Xcontainerbackend inspect $Xcontainerimage" >/dev/null 2>&1 && Xcrootless="${Xcrootless:-yes}" || Fail="yes" [ "$Fail" = "yes" ] && [ -z "$Xcrootless" ] && [ "$Startuser" = "root" ] && { Fail="" Xcrootless="no" unpriv_xcbackend "$Xcontainerbackend inspect $Xcontainerimage" >/dev/null 2>&1 || Fail="yes" } ;; esac [ "$Fail" = "yes" ] && { [ "$Xcontainer" = "yes" ] && { note "Option --xc: Image $Xcontainerimage not found at backend '$Xcontainerbackend' (rootless: $Xcrootless). Fallback: Disabling option --xc" check_fallback } Xcontainer="no" } [ -z "$Xcontainerbackend" ] && Xcontainer="no" case "$Xcontainer" in yes|auto) case "$Xcontainerbackend" in podman|nerdctl) [ "$Startuser" != "root" ] && [ "$Xcrootless" = "no" ] && { [ "$Xcontainer" = "yes" ] && { note "Option --xc with rootful X container backend $Xcontainerbackend needs to be started with root privileges. root needed to claim a tty. Fallback: Disabling option --xc" check_fallback } Xcontainer="no" Auto="yes" experimental "allow rootless Xorg container" && Xcontainer="yes" } ;; esac [ "$Xcontainer" = "auto" ] && Xcontainer="yes" && Auto="yes" || Auto="no" [ "$Winsubsystem" ] && Xcontainer="no" case "$Runtime" in kata-runtime) Xcontainer="no" ;; esac case "$Auto" in yes) [ "$Xcontainer" = "yes" ] && note "Option --xc for X in container enabled automatically." ;; no) [ "$Xcontainer" = "no" ] && { note "Option --xc not possible: - without image x11docker/xserver. - with runtime kata-runtime - on MS Windows Fallback: Setting --xc=no" check_fallback } ;; esac ;; esac case "$Xcontainer" in yes) Xcontainerinspect="$(unpriv_xcbackend "$Xcontainerbackend inspect $Xcontainerimage")" Xcontaineroptions="$(parse_inspect "$Xcontainerinspect" "Config" "Labels" "options")" Xcontaineroptions="${Xcontaineroptions:-"--nxagent --xpra --xpra2 --xpra2-xwayland --xephyr --weston-xwayland --xvfb --xwayland --weston"}" Xcontaineroptionsconsole="$(parse_inspect "$Xcontainerinspect" "Config" "Labels" "options_console")" Xcontainertools="$(parse_inspect "$Xcontainerinspect" "Config" "Labels" "tools")" ;; no) debugnote "check_xcontainer(): --xc disabled" ;; esac return 0 } check_xdepends() { # check dependencies on host for X server option $1 # Return 1 if something is missing or not possible local Return= Message= Xcopt= case "$Autochooseserver" in yes) [ "$Printcheck" = "yes" ] && Message="note" || Message="debugnote" ;; no) Message="note" ;; esac [ "${1:-}" = "--tty" ] && return 0 [ "$Lastcheckedxserver" = "${1:-}" ] && debugnote "Dependencies of ${1:-} already checked: $Lastcheckedxserverresult " && return "$Lastcheckedxserverresult" case "$Runsonconsole" in no) grep -q -w -- "${1:-}" <<< "$Xcontaineroptions --hostdisplay" && Xcopt="yes" ;; yes) grep -q -w -- "${1:-}" <<< "$Xcontaineroptionsconsole" && Xcopt="yes" ;; esac # Wayland case "${1:-}" in --xpra-xwayland|--xpra2-xwayland|--weston-xwayland|--xwayland|--weston|--kwin|--kwin-xwayland|--hostwayland) [ "$Nvidiaversion" ] && verlt "$Nvidiaversion" "470" && { $Message "${1:-}: Closed source NVIDIA driver $Nvidiaversion < version 470.x does not support Wayland. You would need NVIDIA driver>=v470.x and Xwayland>=21.1.2" Return=1 } case "$Runtime" in kata-runtime) $Message "${1:-} is not supported with option --runtime=$Runtime" Return=1 ;; esac case "$Mobyvm" in yes) $Message "${1:-} is not supported with MobyVM / docker-for-win" Return=1 ;; esac ;; esac case "${1:-}" in --weston|--kwin) [ "$Containeruser" != "$Hostuser" ] && { $Message "${1:-} does not run with a container user different from host user $Hostuser (option --user=$Containeruser)." Return=1 } ;; esac [ "$Setupwayland" = "yes" ] && case "$Xserver" in --weston|--kwin|--hostwayland) ;; *) $Message "${1:-} does not support a Wayland environment." Return=1 ;; esac # xinit case "${1:-}" in --xephyr|--xpra|--xpra-xwayland|--nxagent|--xvfb|--xwayland|--weston-xwayland|--kwin-xwayland|--xorg) xtool --check --quiet xinit || { $Message "${1:-}: xinit not found. $Wikipackagesimage" Return=1 } ;; esac # X command case "${1:-}" in --xpra2*) command -v "xpra" >/dev/null || { $Message "${1:-}: xpra not found on host. $Wikipackages" Return=1 } ;; esac [ "$Xcopt" ] || case "${1:-}" in --xpra) command -v "xpra" >/dev/null || { $Message "${1:-}: xpra not found. $Wikipackagesimage" Return=1 } command -v "Xvfb" >/dev/null || { $Message "${1:-}: Xvfb not found. $Wikipackagesimage" Return=1 } ;; --xpra-xwayland) command -v "xpra" >/dev/null || { $Message "${1:-}: xpra not found. $Wikipackagesimage" Return=1 } ;; --xephyr) command -v "Xephyr" >/dev/null || { $Message "${1:-}: Xephyr not found. $Wikipackagesimage" Return=1 } ;; --nxagent) command -v "nxagent" >/dev/null || { $Message "${1:-}: nxagent not found. $Wikipackagesimage" Return=1 } ;; --xvfb) command -v "Xvfb" >/dev/null || { $Message "${1:-}: Xvfb not found. $Wikipackagesimage" Return=1 } ;; --xorg) command -v "Xorg" >/dev/null || { $Message "${1:-}: Xorg not found. $Wikipackages" Return=1 } ;; --xwin) case "$Winsubsystem" in CYGWIN) command -v XWin >/dev/null || { $Message "${1:-}: XWin not found. Need packages 'xinit', 'xauth' and 'xwininfo' in Cygwin (X11 section)." Return=1 } ;; WSL1|WSL2) $Message "${1:-}: XWin is available in Cygwin on MS Windows only. Use runx to provide XWin in WSL: https://github.com/mviereck/runx" Return=1 ;; MSYS2) $Message "${1:-}: XWin is available in Cygwin on MS Windows only. With runx XWin is available in WSL, too. In MSYS2 you can only use runx with VcXsrv to provide an X server: https://github.com/mviereck/runx" Return=1 ;; "") $Message "${1:-}: XWin is available in Cygwin on MS Windows only." Return=1 ;; esac command -v xwininfo >/dev/null || { $Message "${1:-}: xwininfo not found. Need 'xwininfo' package from Cygwin/X (X11 section)." Return=1 } [ "$Hostip" ] || { $Message "${1:-}: Failed to get host IP address." Return=1 } ;; --runx) [ "$Winsubsystem" ] || { $Message "${1:-}: runx is available on MS Windows only." Return=1 } command -v runx >/dev/null || { $Message "${1:-}: runx not found. Need runx from https://github.com/mviereck/runx" Return=1 } ;; esac # Wayland command [ "$Xcopt" ] || case "${1:-}" in --weston|--xpra-xwayland|--weston-xwayland) command -v "weston" >/dev/null || { $Message "${1:-}: weston not found. $Wikipackagesimage" Return=1 } ;; --kwin|--kwin-xwayland) command -v "kwin_wayland" >/dev/null || { $Message "${1:-}: kwin_wayland not found. $Wikipackagesimage" Return=1 } ;; esac [ "$Xcopt" ] || case "${1:-}" in --xpra-xwayland|--weston-xwayland|--kwin-xwayland|--xwayland) command -v "Xwayland" >/dev/null || { $Message "${1:-}: Xwayland not found. $Wikipackagesimage" Return=1 } ;; esac case "${1:-}" in --xpra-xwayland) xtool --check --quiet xdotool || { $Message "${1:-}: xdotool not found. $Wikipackagesimage" Return=1 } ;; esac # xpra version [ "$Xcopt" ] || case "${1:-}" in --xpra*) [ "$Return" = "1" ] || { [ "$Xpraversion" ] || { Xpraversion="$(xpra --version 2>/dev/null | cut -d' ' -f2)" verbose "Xpra version: ${Xpraversion:-XPRA_NOT_FOUND}" [ "$Xprahelp" ] || Xprahelp="$(xpra --help 2>/dev/null)" } } ;; esac case "${1:-}" in --hostdisplay|--xpra-xwayland|--xpra2-xwayland|--xephyr|--nxagent) [ "$Hostdisplay" ] || { $Message "${1:-} needs a running X server. DISPLAY is empty." Return=1 } ;; --hostwayland|--xwayland) [ "$Hostwaylandsocket" ] || { $Message "${1:-} needs a running Wayland compositor. WAYLAND_DISPLAY is empty." Return=1 } ;; esac [ "$Winsubsystem" ] && { case "${1:-}" in --xwin|--runx) ;; --xpra*) $Message "${1:-} is not supported on MS Windows." Return=1 ;; *) [ -z "$Hostdisplay" ] && { case "$Winsubsystem" in Cygwin) $Message "${1:-} needs a running X server. DISPLAY is empty. Please install packages in Cygwin: xinit xauth xwininfo or use runx to provide an X server on MS Windows: https://github.com/mviereck/runx" ;; MSYS2|WSL1|WSL2) $Message "${1:-} needs a running X server. DISPLAY is empty. Please use runx to provide an X server on MS Windows: https://github.com/mviereck/runx" ;; esac Return=1 } ;; esac } # --border [ "$Xpraborder" ] && { case "$Xserver" in --xpra*) ;; *) $Message "${1:-} does not support option --border. Try one of --xpra* options instead." Return=1 ;; esac } # --checkwindow [ -n "$Checkforopenwindow" ] && { case "$Xserver" in --weston|--kwin|--hostwayland) $Message "${1:-} does not support option --checkwindow." Return=1 ;; esac } # --desktop case "$Xserver" in --hostdisplay|--hostwayland) [ "$Desktopmode" = "yes" ] && { $Message "${1:-} does not support option --desktop." Return=1 } ;; esac # --dpi [ -n "$Dpi" ] && case "$Xserver" in --weston|--kwin|--hostwayland|--hostdisplay) $Message "${1:-} does not support option --dpi." Return=1 ;; esac # --gpu case "$Sharegpu" in yes|no) ;; direct) case "${1:-}" in --xorg) ;; --weston|--kwin|--hostwayland) ;; --xpra-xwayland|--xpra2-xwayland|--weston-xwayland|--kwin-xwayland|--xwayland) ;; --hostdisplay) [ "$Xcontainer" = "no" ] && [ "$Shareipc" != "host" ] && { $Message "${1:-}: --gpu=$Sharegpu would need insecure and discouraged option --ipc=host to avoid MIT-SHM errors. Alternatively you can provide image x11docker/xserver (option --xc) that contains a fake MIT-SHM library." Return=1 } ;; *) $Message "${1:-}: --gpu=$Sharegpu not possible. You can try --gpu=virgl." Return=1 ;; esac ;; iglx) case "${1:-}" in --xorg|--xwin|--runx) ;; *) $Message "${1:-}: --gpu=$Sharegpu not possible. You can try --gpu=virgl." Return=1 ;; esac ;; virgl) case "${1:-}" in --weston|--kwin|--hostwayland) $Message "${1:-}: --gpu=$Sharegpu not possible. You can try --gpu=direct." Return=1 ;; esac ;; esac # --rotate [ -n "$Rotation" ] && { case "$Xserver" in --weston|--weston-xwayland|--xorg) ;; *) $Message "${1:-} does not support option --rotate. Try --weston-xwayland, --weston or --xorg." Return=1 ;; esac } # --scale [ "$Scaling" ] && { case "${1:-}" in --xpra*|--xorg) ;; --weston|--weston-xwayland) isint "$Scaling" || { $Message "${1:-} does support full integers only for option --scale. Try one of the --xpra* options instead." Return=1 } ;; *) $Message "${1:-} does not support scaling (option --scale). Try one of the --xpra* or --weston* options instead." Return=1 ;; esac } # --output-count [ "$Outputcount" != "1" ] && { case "$Xserver" in --weston|--kwin|--weston-xwayland|--kwin-xwayland|--xwin) ;; *) $Message "${1:-} does not support option --output-count." Return=1 ;; esac } # --vt [ "$Newxvt" ] && { case "${1:-}" in --kwin) [ "$Newxvt" != "auto" ] && { $Message "${1:-} cannot claim a different console (option --vt)." Return=1 } ;; --xorg|--weston|--weston-wayland) [ "$Xcrootless" = "yes" ] && { $Message "${1:-} cannot claim a new virtual terminal (option --vt) with rootless X container (option --xc)." Return=1 experimental "allow rootless X container" && Return=0 } ;; *) $Message "${1:-} cannot claim a new virtual terminal (option --vt)." Return=1 ;; esac } # --xc case "$Xcontainer" in yes) case "$Xcopt" in "") case "${1:-}" in --xorg|--kwin) $Message "${1:-} is not supported by installed version of x11docker/xserver (option --xc). Please update image." Return=1 ;; --weston|--weston-xwayland) $Message "${1:-} on console is not supported by installed version of x11docker/xserver (option --xc). Please update image." Return=1 ;; esac ;; esac ;; no) case "${1:-}" in --xpra2*) $Message "${1:-} needs image x11docker/xserver (option --xc)." Return=1 ;; esac ;; esac # --xfishtank [ "$Xfishtank" = "yes" ] && { xtool --check --quiet xfishtank || { $Message "xfishtank not found. Can not show a fish tank. Please install 'xfishtank' for option --xfishtank to show a fish tank. $Wikipackagesimage" Return=1 } case "$Xserver" in --weston|--kwin|--hostwayland|--hostdisplay) $Message "Option ${1:-} does not support option --xfishtank" Return=1 ;; esac } # --xoverip case "$Xoverip" in yes|socat) case "${1:-}" in --xephyr|--xorg|--nxagent|--xpra|--xpra2|--xvfb|--xwin|--runx) ;; --hostdisplay|--xwayland|--weston-xwayland|--kwin-xwayland|--xpra-xwayland|--xpra2-xwayland) case "$Xcontainer" in yes) grep -q socat <<< "$Xcontainertools" || { $Message "${1:-} --xoverip needs socat. Please update image x11docker/xserver." Return=1 } ;; no) command -v socat >/dev/null || { $Message "${1:-} --xoverip needs socat. socat is not installed. $Wikipackagesimage" Return=1 } ;; esac [ "$Xserver" = "--hostdisplay" ] && { [ -n "$(cut -d: -f1 <<< "$Hostdisplay")" ] && { [ "$Xoverip" = "socat" ] && { $Message "${1:-} --xoverip: host already runs with X over IP. Setting up an additional socat connection is not supported." Return=1 } } } ;; --weston|--kwin|--hostwayland) $Message "${1:-} does not support X over TCP (--xoverip)." ;; esac ;; listentcp) case "${1:-}" in --xephyr|--xorg|--nxagent|--xpra|--xpra2|--xvfb|--xwin|--runx) ;; *) $Message "${1:-} does not support --xoverip=listentcp." Return=1 ;; esac ;; esac Return="${Return:-"0"}" debugnote "Dependency check for ${1:-}: $Return" [ "$Return" = "1" ] && { check_fallback Autochooseserver="yes" [ "$Printcheck" = "no" ] && grep -q -w "note" <<< "$Message" && note "check_xdepends(): ${1:-} is not possible, see message(s) above. Will search for another X server option. You can see intermediate X dependency check messages with option --printcheck. You can disable the search with option --fallback=no." } Lastcheckedxserver="${1:-}" Lastcheckedxserverresult="$Return" return "$Return" } check_xpraoption() { # check if xpra option $1 is available local Option [ "$Xcontainer" = "yes" ] && [ "$Xserver" != "--xpra2" ] && { echo "$@" return 0 } Option="$(cut -d= -f1 <<< "${1:-}")" grep -q "noprobe" <<< "${1:-}" && { grep "OpenGL" <<< "$Xprahelp" | grep -q "probe" && echo "$@" || { debugnote "Xpra option $Option not supported: $*" return 1 } return 0 } grep -q -- "$Option" <<< "$Xprahelp" && echo "$@" || { debugnote "Xpra option not found: $Option" return 1 } } check_xserver() { # check chosen X server, auto-choose X server local Xorgautochoice= [ -n "$Xserver" ] && Autochooseserver="no" [ "$Fullscreen" = "yes" ] && Desktopmode="yes" && Windowmanagermode="${Windowmanagermode:-auto}" ## default option '--auto': Try to automatically choose best matching and available X server [ "$Autochooseserver" = "yes" ] && { Xserver="--nxagent" [ "$Sharegpu" = "yes" ] && Xserver="--xpra2-xwayland" [ "$Sharegpu" = "direct" ] && Xserver="--xpra2-xwayland" [ "$Xfishtank" = "yes" ] && Xserver="--xephyr" [ "$Desktopmode" = "yes" ] && Xserver="--xephyr" [ "$Xpraborder" ] && Xserver="--xpra2" [ "$Xserver" = "--xephyr" ] && { check_xdepends --xephyr || Xserver="--nxagent" ; } ### FIXME: don't use check_xdepends() here [ "$Sharegpu" = "yes" ] && [ "$Xserver" = "--xephyr" ] && Xserver="--weston-xwayland" [ "$Sharegpu" = "direct" ] && [ "$Xserver" = "--xephyr" ] && Xserver="--weston-xwayland" [ "$Outputcount" != "1" ] && Xserver="--weston-xwayland" [ -n "$Rotation" ] && Xserver="--weston-xwayland" [ "$Runsonconsole" = "yes" ] && Xserver="--xorg" [ -n "$Newxvt" ] && Xserver="--xorg" [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && Xserver="--xpra2" [ -z "$Hostdisplay" ] && [ -n "$Hostwaylandsocket" ] && [ "$Desktopmode" = "yes" ] && Xserver="--weston-xwayland" [ "$Winsubsystem" ] && Xserver="--runx" [ "$Winsubsystem" = "CYGWIN" ] && Xserver="--xwin" [ "$Setupwayland" = "yes" ] && { [ -n "$Hostwaylandsocket" ] && [ "$Desktopmode" = "no" ] && Xserver="--hostwayland" || Xserver="--weston" ; } } [ "$Printcheck" = "yes" ] && note "--printcheck: Starting checks with $Xserver" [ "$Autochooseserver" = "no" ] && [ "$Xserver" = "--xorg" ] && Newxvt="${Newxvt:-auto}" grep -q -i "GNOME" <<< "$XDG_CURRENT_DESKTOP" && { Gnomeversion="$(gnome-shell --version)" [ "$Gnomeversion" ] && verlt "$Gnomeversion" "GNOME Shell 3.38" && { case "$Xserver" in --hostdisplay|--xorg) ;; *) warning "You are running GNOME desktop in outdated version $Gnomeversion This might cause issues with host applications if using additional X servers. It is recommended to use another desktop environment or GNOME >= 3.38. Only --xorg or discouraged option --hostdisplay might work as expected." [ "$Autochooseserver" = "yes" ] && case "$Desktopmode" in "yes") Xserver="--xorg" && Xorgautochoice="yes" ;; "no") Xserver="--hostdisplay" ;; esac ;; esac } } # some first --gpu checks case "$Sharegpu" in yes|direct) [ "$Nvidiaversion" ] && case "$Xserver" in --xpra-xwayland|--xpra2-xwayland|--weston-xwayland|--xwayland|--weston|--kwin|--kwin-xwayland|--hostwayland) verlt "$Nvidiaversion" "470" && note "Option $Xserver --gpu: Your system uses closed source NVIDIA driver version $Nvidiaversion. GPU support will work only with options --hostdisplay and --xorg. Consider to use free open source driver nouveau instead. Using NVIDIA driver>=v470.x and Xwayland>=v21.1.2 might work for $Xserver --gpu." ;; esac ;; esac case "$Runtime" in kata-runtime|sysbox-runc) case "$Sharegpu" in yes) case "$Xcontainer" in yes) Sharegpu="virgl" ;; no) Sharegpu="iglx" ;; esac [ "$Runtime" = "kata-runtime" ] && Sharegpu="iglx" ;; no) ;; virgl|iglx) ;; *) note "Option --gpu=$Sharegpu is not supported with --runtime=$Runtime. You can try --gpu=iglx instead (--xorg only) or you can try --gpu=virgl (Needs image x11docker/xserver). Fallback: Disabling option --gpu." check_fallback Sharegpu="no" ;; esac ;; esac [ "$Runsonconsole" = "no" ] && [ "$Runsoverssh" = "no" ] && [ -z "$Hostdisplay$Hostwaylandsocket" ] && [ "$Xserver" != "--tty" ] && [ -z "$Winsubsystem" ] && { warning "Environment variables DISPLAY and WAYLAND_DISPLAY are empty, but it looks like 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" && Xorgautochoice="yes" } [ "$Runsoverssh" = "yes" ] && [ -z "$Hostdisplay$Hostwaylandsocket" ] && [ "$Xserver" != "--tty" ] && { note "You are running x11docker over SSH without providing a display. DISPLAY and WAYLAND_DISPLAY are empty. You might need to run with 'ssh -X' or 'ssh -Y'. If there is already an X or Wayland session running on the remote system that you want to use, please set either DISPLAY and XAUTHORITY or WAYLAND_DISPLAY and XDG_RUNTIME_DIR accordingly." } # --wayland case "$Xserver" in --weston|--kwin|--hostwayland) Setupwayland="yes" ;; esac [ "$Setupwayland" = "yes" ] && { check_xdepends "$Xserver" || Xserver="--hostwayland" ; } [ "$Xserver" = "--kwin" ] && { check_xdepends --kwin || Xserver="--weston" ; } [ "$Xserver" = "--hostwayland" ] && { check_xdepends --hostwayland || Xserver="--weston" ; } [ "$Xserver" = "--weston" ] && { check_xdepends --weston || Xserver="--kwin" ; } [ "$Setupwayland" = "yes" ] && { Autochooseserver="no" check_xdepends $Xserver || error "Failed to set up a Wayland environment. Please install 'weston' or 'kwin_wayland'." ; } ## check if dependencies for chosen X server are installed, fall back to best alternatives if not [ "$Xserver" = "--xwin" ] && { check_xdepends --xwin || Xserver="--runx" ; } [ "$Xserver" = "--runx" ] && { check_xdepends --runx || Xserver="--hostdisplay" ; } [ "$Xserver" = "--hostdisplay" ] && { check_xdepends --hostdisplay || { [ "$Sharegpu" = "yes" ] && Xserver="--xpra2-xwayland" || Xserver="--xpra2" ; } ; } [ "$Xserver" = "--hostdisplay" ] && { check_xdepends --hostdisplay || { [ "$Sharegpu" = "direct" ] && Xserver="--xpra2-xwayland" || Xserver="--xpra2" ; } ; } [ "$Xserver" = "--xephyr" ] && { check_xdepends --xephyr || Xserver="--nxagent" ; } [ "$Xserver" = "--nxagent" ] && { check_xdepends --nxagent || { [ "$Desktopmode" = "yes" ] && Xserver="--xephyr" || Xserver="--xpra2" ; } ; } [ "$Xserver" = "--xpra2" ] && { check_xdepends --xpra2 || Xserver="--xpra" ; } [ "$Xserver" = "--xpra" ] && { check_xdepends --xpra || { check_xdepends --nxagent && Xserver="--nxagent" || Xserver="--xephyr" ; } ; } [ "$Xserver" = "--xorg" ] && { check_xdepends --xorg || Xserver="--weston-xwayland" ; } [ "$Xserver" = "--xpra2-xwayland" ] && { check_xdepends --xpra2-xwayland || Xserver="--xpra-xwayland" ; } [ "$Xserver" = "--xpra-xwayland" ] && { check_xdepends --xpra-xwayland || Xserver="--weston-xwayland" ; } [ "$Xserver" = "--xwayland" ] && { check_xdepends --xwayland || Xserver="--weston-xwayland" ; } #[ "$Xserver" = "--xpra-xwayland" ] && { check_xdepends --xpra-xwayland || { [ "$Desktopmode" = "yes" ] && Xserver="--kwin-xwayland" || Xserver="--hostdisplay" ; } ; } [ "$Xserver" = "--kwin-xwayland" ] && { check_xdepends --kwin-xwayland || Xserver="--weston-xwayland" ; } #[ "$Xserver" = "--weston-xwayland" ] && { check_xdepends --weston-xwayland || Xserver="--kwin-xwayland" ; } # Xephyr as fallback for all options. Last fallback: Xorg check_xdepends $Xserver || Xserver="--xephyr" [ "$Xserver" = "--xephyr" ] && { check_xdepends --xephyr || { #check_xdepends --kwin-xwayland && Xserver="--kwin-xwayland" check_xdepends --hostdisplay && [ "$Desktopmode" = "no" ] && Xserver="--hostdisplay" check_xdepends --runx && Xserver="--runx" check_xdepends --xwin && Xserver="--xwin" check_xdepends --nxagent && Xserver="--nxagent" check_xdepends --weston-xwayland && Xserver="--weston-xwayland" check_xdepends --xpra && Xserver="--xpra" check_xdepends --xpra2 && Xserver="--xpra2" } case "$Sharegpu" in yes|direct) case "$Desktopmode" in yes) check_xdepends --weston-xwayland && Xserver="--weston-xwayland" ;; no) check_xdepends --hostdisplay && Xserver="--hostdisplay" ;; esac ;; esac [ "$Runsonconsole" = "yes" ] && { #check_xdepends --kwin-xwayland && Xserver="--kwin-xwayland" check_xdepends --weston-xwayland && Xserver="--weston-xwayland" check_xdepends --xorg && Xserver="--xorg" } check_xdepends $Xserver || { Xserver="--xorg" && [ "$Autochooseserver" = "yes" ] && Xorgautochoice="yes" ; } } Autochooseserver="no" check_xdepends $Xserver || { case "$Winsubsystem" in "") error "Did not find a possibility to provide a display. Recommendations: Either pull image x11docker/xserver that provides almost everything that x11docker could use, or install 'xinit' and 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: Xorg, or weston and Xwayland $Wikipackagesimage It might help you to see dependency check messages with option --printcheck." ;; CYGWIN) error "Did not find a possibility to provide a display. Please install packages 'xinit' and 'xauth' in Cygwin, or run x11docker with --runx: https://github.com/mviereck/runx" ;; MSYS2|WSL1|WSL2) [ "$Hostdisplay" ] && { error "Did not find a possibility to provide a nested display. Please install package 'xinit' and one or all of: nxagent Xephyr xpra $Wikipackages" } || { error "Did not find a possibility to provide a display. Please use --runx to provide an X server on MS Windows: https://github.com/mviereck/runx" } ;; esac } [ "$Xorgautochoice" = "yes" ] && [ "$Runsonconsole" = "no" ] && [ -z "$Newxvt" ] && error "Option --xorg was chosen automatically as the best one fitting your specified options and installed dependencies. However, x11docker does not run another Xorg without being specified. Please run with option --xorg. It might help you to see dependency check messages with option --printcheck." case "$Xserver" in --hostwayland) [ "$Xcontainer" = "yes" ] && { Xcontainer="no" note "Option --xc disabled for option --hostwayland." } ;; esac [ "$Autochooseserver" = "yes" ] && note "Using X server option $Xserver" storeinfo "xserver=$Xserver" return 0 } create_modelinefile() { # generate a set of smaller modelines for screen size $1 and store them in a cache file local Newmodelinefile Modeline Size X Y Xcount Ycount Size="${1:-}" X="$(echo "$Size" | cut -dx -f1)" Y="$(echo "$Size" | cut -dx -f2)" Newmodelinefile="$Modelinefilebasepath/$Size" [ -e "$Newmodelinefile" ] || { debugnote "$Xserver: Generating modelines for $Size" mkfile "$Newmodelinefile" for Ycount in 25 30 40 45 50 55 60 65 70 75 80 85 90 95 100; do for Xcount in 25 30 40 45 50 55 60 65 70 75 80 85 90 95 100; do Modeline="$(cvt "$(awk -v a="$X" -v b="$Xcount" 'BEGIN {print (a * b / 100)}')" "$(awk -v a="$Y" -v b="$Ycount" 'BEGIN {print (a * b / 100)}' )" | tail -n1)" Modeline="${Modeline//"_60.00"/""}" echo "$Modeline" >> "$Newmodelinefile" done done } echo "$Newmodelinefile" } create_xcommand() { ### create command to start X server and/or Wayland compositor local Xserveroptions_custom= Xpraoptions= Nxagentoptions= Compositorpid= Weston= Westonoutput= Count= Usemitshm Xkbmodel Xdpyinfooutput local Connector Connectorlist Status Xserveroptions_custom="$Xserveroptions" Xserveroptions="" Xdpyinfooutput="$(xtool "xdpyinfo 2>> $Xinitlogfile" ||:)" [ "$Xcontainer" = "yes" ] && [ "$Xcontainerbackend" = "$Backend" ] && Usemitshm="yes" [ "$Xcontainer" = "yes" ] && [ "$Xcontainerbackend" = "nerdctl" ] && Usemitshm="no" [ "$Shareipc" = "host" ] && Usemitshm="yes" [ "$Xcontainer" = "yes" ] && [ -n "$Newxvt" ] && Usemitshm="no" [ "$Xcontainer" = "no" ] && [ "$Shareipc" != "host" ] && Usemitshm="no" [ "$Containeruser" != "$Hostuser" ] && Usemitshm="no" [ "$Xoverip" != "no" ] && Usemitshm="no" case "$Runtime" in kata-runtime|sysbox-runc) Usemitshm="no" ;; esac Usemitshm="${Usemitshm:-"no"}" #### General X server options case "$Xserver" in --nxagent) case "$Usemitshm" in yes) Xserveroptions="$Xserveroptions \\ -shmem \\ -shpix" ;; no) Xserveroptions="$Xserveroptions \\ -noshmem \\ -noshpix" ;; esac ;; *) Xserveroptions=" \\ -retro \\ +extension RANDR \\ +extension RENDER \\ +extension GLX \\ +extension XVideo \\ +extension DOUBLE-BUFFER \\ +extension SECURITY \\ +extension DAMAGE \\ +extension X-Resource \\ -extension XINERAMA -xinerama" case "$Usemitshm" in yes) Xserveroptions="$Xserveroptions \\ +extension MIT-SHM" ;; no) Xserveroptions="$Xserveroptions \\ -extension MIT-SHM" Xprashm="XPRA_XSHM=0" ;; esac ;; esac # X extension COMPOSITE [ "$Xcomposite" ] || case "$Xserver" in --nxagent|--xwin) Xcomposite="no" ;; *) Xcomposite="yes" ;; esac case "$Xcomposite" in yes) # Old X servers have extension "Composite", recent ones call it "COMPOSITE". Xserveroptions="$Xserveroptions \\ +extension Composite +extension COMPOSITE" ;; no) Xserveroptions="$Xserveroptions \\ -extension Composite -extension COMPOSITE" [ "$Xserver" = "nxagent" ] && Xserveroptions="Xserveroptions \\ -nocomposite" ;; esac # X extension XTEST Xtest="${Xtest:-no}" case "$Xtest" in yes) Xserveroptions="$Xserveroptions \\ +extension XTEST" ;; no) Xserveroptions="$Xserveroptions \\ -extension XTEST -tst" ;; esac # Disable screensaver Xserveroptions="$Xserveroptions \\ -dpms \\ -s off" # X cookie authentication case "$Xauthentication" in yes|trusted|untrusted) Xserveroptions="$Xserveroptions \\ -auth $Xservercookie" ;; no) Xserveroptions="$Xserveroptions \\ -ac" case "$Xoverip" in socat|listentcp) warning "Option --xauth=no --xoverip=$Xoverip: SECURITY RISK! Allowing access to new X server for everyone. Your X server is accessible over TCP network without any restriction. That can be abused to take control over your system." ;; no) case "$Xserver" in --hostdisplay|--hostwayland|--weston|--kwin|--tty) ;; *) warning "Option --xauth=no: SECURITY RISK! Allowing access to new X server for everyone." ;; esac ;; esac ;; esac # X over IP/TCP case "$Xoverip" in listentcp) case "$Xserver" in --nxagent) ;; *) Xserveroptions="$Xserveroptions \\ -listen tcp" ;; esac ;; no|socat) Xserveroptions="$Xserveroptions \\ -nolisten tcp" ;; esac # check DPI case "$Xserver" in --weston|--kwin|--tty|--hostdisplay) ;; --xwin|--runx) ;; *) [ -z "$Dpi" ] && [ -n "$Xdpyinfooutput" ] && Dpi="$(grep dots <<< "$Xdpyinfooutput" | cut -dx -f2 | cut -d' ' -f1)" [ -n "$Dpi" ] && { case "$Xserver" in --xpra*) [ "$Scaling" ] && [ "$Desktopmode" = "no" ] && { Dpi="$(awk -v a="$Scaling" -v b="$Dpi" 'BEGIN {print (b * a * a)}')" Dpi="${Dpi%.*}" } ;; esac } ;; esac [ -n "$Dpi" ] && Xserveroptions="$Xserveroptions \\ -dpi $Dpi" # --keymap # Regard possible custom keyboard model like rmlvo. # Unofficial feature, see ticket #208. Currently regarded with --nxagent only. Xkbmodel="$(cut -d/ -f1 -s <<< "$Xkblayout")" [ -n "$Xkbmodel" ] && Xkblayout="$(cut -d/ -f2- <<< "$Xkblayout")" Xkbmodel="${Xkbmodel:-evdev}" #### xpra server and client command case "$Xserver" in --xpra*) [ "$Xprahelp" ] || Xprahelp="$(xpra --help 2>/dev/null)" # $(check_xpraoption --csc-modules=none) \\ # $(check_xpraoption --encodings=rgb) \\ Xpraoptions="\\ $(check_xpraoption --microphone=no ||:) \\ $(check_xpraoption --mmap=$Sharefolder/xpra.mmap ||:) \\ $(check_xpraoption --notifications=no ||:) \\ $(check_xpraoption --pulseaudio=no ||:) \\ $(check_xpraoption --socket-dirs="$Sharefolder" ||:) \\ $(check_xpraoption --speaker=no ||:) \\ $(check_xpraoption --start-via-proxy=no ||:) \\ $(check_xpraoption --system-tray=yes ||:) \\ $(check_xpraoption --webcam=no ||:) \\ $(check_xpraoption --xsettings=no ||:)" # $(check_xpraoption --clipboard-direction=both ||:) \\ # --keymap [ "$Xkblayout" ] && Xpraoptions="$Xpraoptions \\ $(check_xpraoption --keyboard-layout="'$Xkblayout'" ||:) \\ $(check_xpraoption --keyboard-raw=yes ||:)" # Xpraoptions="$Xpraoptions $(check_xpraoption --debug=all ||:)" ; Preservecachefiles="yes" # Debugging only # xpra server command [ "$Desktopmode" = "yes" ] && Xpraservercommand="xpra start-desktop" || Xpraservercommand="xpra start" # Xpraservercommand="$Xpraservercommand :$Newdisplaynumber --use-display $Xpraoptions \\ Xpraservercommand="$Xpraservercommand :$Newdisplaynumber --use-display $Xpraoptions \\ $(check_xpraoption --clipboard=no ||:)\\ $(check_xpraoption --dbus-launch=no ||:) \\ $(check_xpraoption --dbus-proxy=no ||:) \\ $(check_xpraoption --daemon=no ||:) \\ $(check_xpraoption --fake-xinerama=no ||:) \\ $(check_xpraoption --file-transfer=off ||:) \\ $(check_xpraoption --html=off ||:) \\ $(check_xpraoption --opengl=noprobe ||:) \\ $(check_xpraoption --mdns=no ||:) \\ $(check_xpraoption --printing=no ||:) \\ $(check_xpraoption --session-name="$Codename" ||:) \\ $(check_xpraoption --start-new-commands=no ||:) \\ $(check_xpraoption --systemd-run=no ||:)" # $(check_xpraoption --video-encoders=none ||:)" [ -n "$Dpi" ] && Xpraservercommand="$Xpraservercommand \\ $(check_xpraoption --dpi="$Dpi" ||:)" case "$Xserver" in --xpra2*) Xpraservercommand="$Xpraservercommand \\ --bind=$Sharefolder/$(hostname)-$Newdisplaynumber" ;; esac # xpra client command # $(check_xpraoption --compress=0 ||:) \\ # $(check_xpraoption --quality=100 ||:) \\ # $(check_xpraoption --video-decoders=none ||:)" # $(check_xpraoption --clipboard=$Shareclipboard ||:) \\ Xpraclientcommand="xpra attach :$Newdisplaynumber $Xpraoptions \\ $(check_xpraoption --modal-windows=no ||:) \\ $(check_xpraoption --opengl=noprobe ||:) \\ $(check_xpraoption --reconnect=no ||:) \\ $(check_xpraoption --tray=no ||:)" [ "$Fullscreen" = "yes" ] && Xpraclientcommand="$Xpraclientcommand \\ $(check_xpraoption --desktop-fullscreen=yes ||:)" [ "$Scaling" ] && Xpraclientcommand="$Xpraclientcommand \\ $(check_xpraoption --desktop-scaling="$Scaling" ||:)" # [ -n "$Dpi" ] && Xpraclientcommand="$Xpraclientcommand \\ # $(check_xpraoption --dpi="'$Dpi'")" [ "$Xpraborder" ] && Xpraclientcommand="$Xpraclientcommand \\ $(check_xpraoption --border="$Xpraborder" ||:)" # case "$Desktopmode" in ### FIXME # yes) Xpraclientcommand="$Xpraclientcommand \\ # $(check_xpraoption --title="'$Codename on $Newdisplay [in container] (shift+F11 toggles fullscreen)'" ||:)" ;; # no) Xpraclientcommand="$Xpraclientcommand \\ # $(check_xpraoption --title="'@title@ [in container]'" ||:)" ;; # esac # xpra environment variables for Line in $Xpracontainerenv; do store_runoption env "$Line" done ;; esac #### Prepare weston.ini: config file for Weston case "$Xserver" in --weston|--weston-xwayland|--xpra-xwayland|--xpra2-xwayland) command -v weston-launch >/dev/null && [ -n "$Newxvt" ] && [ "$Runsinteractive" = "yes" ] && Weston="weston-launch -v --" || Weston="weston" echo "[core] shell=desktop-shell.so idle-time=0 [shell] panel-location=none panel-position=none locking=false background-color=0xff002244 animation=fade startup-animation=fade [keyboard]" >> "$Westonini" # --keymap: keyboard layout [ -n "$Xkblayout" ] && echo "keymap_layout=$Xkblayout" >> "$Westonini" [ -z "$Xkblayout" ] && [ -n "$Newxvt" ] && echo "$(echo -n "keymap_layout=" && grep XKBLAYOUT <"/etc/default/keyboard" | cut -d= -f2 | cut -d'"' -f2)" >> "$Westonini" case "$Newxvt" in "") # Display prefix X or WL; needed to indicate if host Wayland or host X provides the nested window. #[ -n "$Hostwaylandsocket" ] && [ "$Xserver" != "--xpra-xwayland" ] && [ "$Hostsystem" != "ubuntu" ] && [ "$Fullscreen" = "no" ] && Westonoutput="WL" [ -n "$Hostdisplay" ] && Westonoutput="X" [ -z "$Westonoutput" ] && [ -n "$Hostwaylandsocket" ] && Westonoutput="WL" ;; *) # get monitor name(s) for Status in /sys/class/drm/*/status; do Connector="${Status%/status}" Connector="${Connector#*/card?-}" [ "$(cat $Status)" = "connected" ] && Connectorlist="$Connectorlist $Connector" done ;; esac ;; esac #### create command to run X server case "$Xserver" in --xorg) Xserveroptions="$Xserveroptions \\ -verbose" [ "$Xorgconf" ] && Xserveroptions="$Xserveroptions \\ -config '$Xorgconf'" # --xorgconf Xcommand="$(get_xpath Xorg) :$Newdisplaynumber vt$Newxvt $Xserveroptions" ;; --xpra|--xpra2) Xcommand="$(get_xpath Xvfb) :$Newdisplaynumber $Xserveroptions \\ -screen 0 ${Xaxis}x${Yaxis}x24" ;; --xvfb) Xcommand="$(get_xpath Xvfb) :$Newdisplaynumber $Xserveroptions \\ -screen 0 ${Screensize}x24" ### FIXME: hardcoded setting of depth 24. Could be better? ;; --xephyr) Xserveroptions="$Xserveroptions \\ -resizeable \\ -noxv" # Xserveroptions="$Xserveroptions \\ # -glamor" # disabled because of lagginess reported in #196 case "$Fullscreen" in yes) Xserveroptions="$Xserveroptions \\ -fullscreen" ;; no) grep -q -- "-output " <<< "$Xserveroptions_custom" || Xserveroptions="$Xserveroptions \\ -screen $Screensize" ;; esac Xcommand="$(get_xpath Xephyr) :$Newdisplaynumber $Xserveroptions" ;; --xwayland) Xcommand="$(get_xpath Xwayland) :$Newdisplaynumber -ac $Xserveroptions" ;; --xpra-xwayland|--xpra2-xwayland) Xcommand="$(get_xpath Xwayland) :$Newdisplaynumber -ac $Xserveroptions" echo "[output] name=${Westonoutput}1 mode=$Screensize" >> "$Westonini" [ -n "$Customwestonini" ] && Westonini="$Customwestonini" Compositorcommand="$Weston \\ --socket=$Newwaylandsocket \\ --backend=x11-backend.so \\ --config='$Westonini'" case "$Xserver" in --xpra-xwayland|--xpra2-xwayland) case "$Scaling" in "") Compositorcommand="$Compositorcommand \\ --fullscreen" ;; *) Compositorcommand="$Compositorcommand \\ --width=$(cut -dx -f1 <<< "$Screensize") --height=$(cut -dx -f2 <<< "$Screensize")" ;; esac ;; esac ;; --weston|--weston-xwayland) Xcommand="$(get_xpath Xwayland) :$Newdisplaynumber -ac $Xserveroptions" [ -n "${Westonoutput:-$Connectorlist}" ] && for ((Count=1 ; Count<="$Outputcount" ; Count++)) ; do [ "$Westonoutput" = "WL" ] || [ "$Westonoutput" = "X" ] || { Count="" [ -z "$Screensize" ] && Screensize="preferred" } for Connector in ${Westonoutput:-$Connectorlist}; do echo "[output] name=$Connector$Count mode=$Screensize" >> "$Westonini" [ "$Scaling" ] && echo "scale=$Scaling" >> "$Westonini" [ -n "$Rotation" ] && echo "transform=$Rotation" >> "$Westonini" done [ "$Count" ] || break done Compositorcommand="$Weston \\ --socket=$Newwaylandsocket" [ "$Fullscreen" = "yes" ] && Compositorcommand="$Compositorcommand \\ --fullscreen" [ "$Outputcount" = "1" ] || Compositorcommand="$Compositorcommand \\ --output-count=$Outputcount" case "$Westonoutput" in WL) Compositorcommand="$Compositorcommand \\ --backend=wayland-backend.so" ;; X) Compositorcommand="$Compositorcommand \\ --backend=x11-backend.so" ;; *) case "$Newxvt" in "") Compositorcommand="$Compositorcommand \\ --backend=x11-backend.so" ;; *) Compositorcommand="$Compositorcommand \\ --backend=drm-backend.so" ;; esac ;; esac [ -n "$Customwestonini" ] && Westonini="$Customwestonini" Compositorcommand="$Compositorcommand \\ --config='$Westonini'" ;; --kwin|--kwin-xwayland) Xcommand="$(get_xpath Xwayland) :$Newdisplaynumber -ac $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" [ -n "$Newxvt" ] && Compositorcommand="$Compositorcommand \\ --drm" ;; --nxagent) # files needed by nxagent export NX_CLIENT="$Nxagentclientrc" Xserveroptions="$Xserveroptions \\ -norootlessexit \\ -ac \\ -options $Nxagentoptionsfile \\ -keystrokefile $Nxagentkeysfile" case "$Desktopmode" in "yes") Xserveroptions="$Xserveroptions \\ -D \\ -name '${Imagename}_on_${Newdisplay}_(shift+F11_toggles_fullscreen)'" ;; "no") Xserveroptions="$Xserveroptions \\ -R" ;; esac Xcommand="$(get_xpath nxagent) :$Newdisplaynumber $Xserveroptions" # Some additional nxagent options are stored in a file Nxagentoptions="nx/nx" Nxagentoptions="$Nxagentoptions,clipboard=none" case "$Fullscreen" in yes) Nxagentoptions="$Nxagentoptions,fullscreen=1" ;; no) [ -n "$Screensize" ] && Nxagentoptions="$Nxagentoptions,geometry=$Screensize" ;; esac # --composite case "$Xcomposite" in yes) Nxagentoptions="$Nxagentoptions,composite=1" ;; no) Nxagentoptions="$Nxagentoptions,composite=0" ;; esac # --keymap: set keyboard layout case "$Xkblayout" in ""|clone) Nxagentoptions="$Nxagentoptions,keyboard=clone" ;; *) Nxagentoptions="$Nxagentoptions,keyboard=${Xkbmodel}/${Xkblayout}" ;; esac Nxagentoptions="${Nxagentoptions}:${Newdisplaynumber}" echo "$Nxagentoptions" >> "$Nxagentoptionsfile" debugnote "$Xserver: Additional nxagent options: $Nxagentoptions" # Workaround as nxagent ignores XAUTHORITY and fails to start if option -auth is given without containing the cookie from host display. # Option -ac above complies "xhost +" and is reverted in xinitrc. [ "$Xauthentication" != "no" ] && unpriv "cp '$Hostxauthority' '$Xservercookie'" # fake NXclient echo '#! /usr/bin/env bash # helper script to terminate nxagent. # nxagent runs program noted in NX_CLIENT if window close button is pressed. # (real nxclient does not exist) echo "NXclient: $*" >> '"$Xinitlogfile"' parsed="$(getopt --options="" --longoptions="parent:,display:,dialog:,caption:,window:,message:" -- "$@")" eval set -- $parsed echo "$parsed" >> '$Xinitlogfile' while [ -n "${1:-}" ] ; do case "${1:-}" in --dialog) dialog="${2:-}" && shift ;; --display|--caption|--message) shift ;; --window) shift ;; --parent) pid="${2:-}" && shift ;; --) ;; esac shift done case $dialog in pulldown) ;; yesnosuspend) kill $pid echo timetosaygoodbye >> '"$Timetosaygoodbyefile"' ;; esac ' >> "$Nxagentclientrc" unpriv "chmod +x '$Nxagentclientrc'" echo ' ' >> "$Nxagentkeysfile" ;; --xwin) case "$Sharegpu" in no) Xserveroptions="$Xserveroptions \\ -nowgl" ;; *) Xserveroptions="$Xserveroptions \\ -wgl" ;; esac case "$Fullscreen" in yes) Xserveroptions="$Xserveroptions \\ -fullscreen" ;; no) Xserveroptions="$Xserveroptions \\ -lesspointer" case "$Desktopmode" in yes) for ((Count=0 ; Count<$Outputcount ; Count++)); do Xserveroptions="$Xserveroptions \\ -screen $Count $Screensize" done ;; no) Xserveroptions="$Xserveroptions \\ -multiwindow" ;; esac ;; esac case "$Shareclipboard" in yes) Xserveroptions="$Xserveroptions \\ -clipboard" ;; no) Xserveroptions="$Xserveroptions \\ -noclipboard" ;; esac Xcommand="$(command -v XWin) :$Newdisplaynumber $Xserveroptions" ;; --runx) Xserveroptions="--display $Newdisplaynumber \ --verbose" [ "$Xauthentication" = "no" ] && Xserveroptions="$Xserveroptions \ --no-auth" [ "$Desktopmode" = "yes" ] && Xserveroptions="$Xserveroptions \ --desktop" [ "$Shareclipboard" = "yes" ] && Xserveroptions="$Xserveroptions \ --clipboard" [ "$Screensize" ] && Xserveroptions="$Xserveroptions \ --size=$Screensize" [ "$Sharegpu" = "iglx" ] && { Xserveroptions="$Xserveroptions \ --gpu" } Xcommand="$(command -v runx) $Xserveroptions" ;; --hostwayland|--hostdisplay|--tty) ;; esac case "$Xserver" in --tty|--hostdisplay|--runx) ;; --weston|--kwin|--hostwayland) ;; --nxagent) ;; *) case "$Sharegpu" in iglx) Xcommand="$Xcommand \\ +iglx" ;; *) Xcommand="$Xcommand \\ -iglx" ;; esac ;; esac # --xopt Xcommand="$Xcommand \\ $Xserveroptions_custom" case "$Xserver" in --weston|--kwin|--hostwayland|--hostdisplay|--tty) Xcommand="" ;; esac case "$Xserver" in --weston|--kwin|--weston-xwayland|--kwin-xwayland|--xpra-xwayland|--xpra2-xwayland) ;; *) Compositorcommand="" ;; esac return 0 } create_xcontainercommand() { # option --xc: create docker command for X in container local Xcontainerrc Gpudevice local Xc_hostx= Xc_hostwayland= Xc_containerwayland Xc_gpu= Xc_nomitshm local Xc_capdrop Xc_nopriv Xc_user Xc_console Xc_systemd Xcontainername="x11docker_X${Newdisplaynumber}_xserver_${Cachenumber}" Xcontainerrc="$Cachefolder/xcontainerrc" mkfile "$Xcontainerrc" mkfile "$Cachefolder/etcpasswd.xcontainer" echo "root:x:0:0:root:/root:/bin/bash" >> "$Cachefolder/etcpasswd.xcontainer" echo "$Containeruser:x:${Containeruseruid:-$Hostuseruid}:${Containerusergid:-$Hostusergid}:$Containeruser,,,:/tmp:/bin/bash" >> "$Cachefolder/etcpasswd.xcontainer" mkfile "$Cachefolder/etcgroup.xcontainer" echo "video:x:$(mygetent group video | cut -d: -f3):$Containeruser" >> "$Cachefolder/etcgroup.xcontainer" echo "render:x:$(mygetent group render | cut -d: -f3):$Containeruser" >> "$Cachefolder/etcgroup.xcontainer" echo "weston-launch:x:104:$Containeruser" >> "$Cachefolder/etcgroup.xcontainer" ## code snippets: # drop privileges Xc_capdrop="\\ --cap-drop ALL" Xc_nopriv="\\ --security-opt=no-new-privileges" #[ "$Xcrootless" = "yes" ] && Xc_capdrop="" && Xc_nopriv="" case "$Backend" in nerdctl) Xc_capdrop="" Xc_nopriv="" ;; esac # user case "$Xcrootless" in yes) case "$Xcontainerbackend" in podman) Xc_user="\\ --user ${Hostuseruid}:${Hostusergid} \\ --userns=keep-id" ;; *) Xc_user="\\ --user 0:0" # maps to same uid as unprivileged host user ;; esac ;; no) Xc_user="\\ --user ${Hostuseruid}:${Hostusergid}" ;; esac # X in container Xc_containerx="\\ --mount type=bind,source=$Cachefolder/tmp,target=/tmp \\ --mount type=bind,source=$Xservercookie,target=$Xservercookie \\ --mount type=bind,source=$Modelinefilebasepath,target=$Modelinefilebasepath,readonly" # access to host X [ -n "$Hostdisplay" ] && { Xc_hostx="\\ --env DISPLAY=$Hostdisplay" [ -S "/tmp/.X11-unix/X$Hostdisplaynumber" ] && { case "$Xserver" in --hostdisplay) Xc_hostx="$Xc_hostx \\ --mount type=bind,source=/tmp/.X11-unix/X$Hostdisplaynumber,target=/tmp/.X11-unix/X$Hostdisplaynumber,readonly" ;; *) Xc_hostx="$Xc_hostx \\ --mount type=bind,source=/tmp/.X11-unix/X$Hostdisplaynumber,target=/X$Hostdisplaynumber,readonly" ;; esac } [ -s "$Hostxauthority" ] && Xc_hostx="$Xc_hostx \\ --env XAUTHORITY=$Hostxauthority \\ --mount type=bind,source=$Hostxauthority,target=$Hostxauthority" } # access to host Wayland [ -S "$XDG_RUNTIME_DIR/$Hostwaylandsocket" ] && Xc_hostwayland="\\ --mount type=bind,source=$XDG_RUNTIME_DIR/$Hostwaylandsocket,target=$XDG_RUNTIME_DIR/$Hostwaylandsocket,readonly \\ --env XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \\ --env GDK_BACKEND=wayland \\ --env WAYLAND_DISPLAY=$Hostwaylandsocket" # Wayland in container Xc_containerwayland="\\ --env XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \\ --mount type=bind,source=$Cachefolder/XDG_RUNTIME_DIR,target=$XDG_RUNTIME_DIR" # container X should not use MIT-SHM Xc_nomitshm="\\ --env LD_PRELOAD=/lib/x86_64-linux-gnu/libdl.so.2:$Sharefolder/XlibNoSHM.so" # GPU while read -r Gpudevice ; do Xc_gpu="$Xc_gpu \\ --device $Gpudevice:$Gpudevice" done < <(devicelist_gpu) # nvidia driver [ "$Nvidiainstallerfile" ] && { Xc_gpu="$Xc_gpu \\ --mount type=bind,source=$Nvidiainstallerfile,target=$Nvidiacontainerfile,readonly" mkfile "$Sharefolder/nvidia_installer" rootrc_nvidia_installer >> "$Sharefolder/nvidia_installer" } case "$Xcontainerbackend" in docker|podman) mygetent group video >/dev/null && Xc_gpu="$Xc_gpu \\ --group-add $(mygetent group video | cut -d: -f3)" mygetent group render >/dev/null && Xc_gpu="$Xc_gpu \\ --group-add $(mygetent group render | cut -d: -f3)" ;; esac # console Xc_console="\\ --cap-add SYS_TTY_CONFIG \\ --cap-add DAC_OVERRIDE \\ --cap-add KILL \\ --mount type=bind,source=/var/run/dbus,target=/var/run/dbus \\ --mount type=bind,source=/run/udev/data,target=/run/udev/data,readonly \\ --device=/dev/tty${Newxvt}" while read Line; do Xc_console="$Xc_console \\ --device=$Line" done < <(devicelist_input) # Weston Xc_weston="\\ --cap-add SETUID \\ --cap-add SETGID \\ --cap-add CHOWN \\ --env XDG_VTNR=$Newxvt \\ --mount type=bind,source=$Compositorlogfile,target=/x11docker/compositor.log" case "$Xcontainerbackend" in docker|podman) Xc_weston="$Xc_weston \\ --group-add 104" ;; esac # connect to systemd from host Xc_systemd="\\ --pid=host \\ --env XDG_SEAT=$XDG_SEAT \\ --env XDG_SESSION_ID=$XDG_SESSION_ID \\ --mount type=bind,source=/run/systemd,target=/run/systemd \\ --mount type=bind,source=/run/user,target=/run/user \\ --mount type=bind,source=/run/dbus,target=/run/dbus \\ --mount type=bind,source=/sys/fs/cgroup,target=/sys/fs/cgroup" ## create command ## ### FIXME --xc=nerdctl does not take combination --detach --rm Xcontainercommand="$Xcontainerbackend run --pull=never \\ --detach \\ --name $Xcontainername \\ --mount type=bind,source=$Sharefolder,target=$Sharefolder \\ --mount type=bind,source=$Cachefolder/etcpasswd.xcontainer,target=/etc/passwd,readonly \\ --mount type=bind,source=$Cachefolder/etcgroup.xcontainer,target=/etc/group,readonly \\ --mount type=bind,source=$Xcontainerrc,target=/xcontainerrc,readonly" case "$Backend" in docker|podman) Xcontainercommand="$Xcontainercommand \\ --rm \\ --security-opt label=type:container_runtime_t" ;; nerdctl) ;; esac case "$Hostxoverip" in yes) grep -q "localhost" <<< "$Hostdisplay" && { warning "Option --xc: Sharing host network stack with container of x11docker/xserver to support 'ssh -X'." Xcontainercommand="$Xcontainercommand \\ --network=host" } || { Xcontainercommand="$Xcontainercommand \\ --network=bridge" } ;; no) case "$Xoverip" in no) Xcontainercommand="$Xcontainercommand \\ --network=none" ;; listentcp|socat) [ -n "$Xcnetworkid" ] && { Xcontainercommand="$Xcontainercommand \\ --network=$Xcnetworkname" } ;; esac ;; esac case "$Shareipc" in host) Xcontainercommand="$Xcontainercommand \\ --ipc=host" ;; no) case "$Xcontainerbackend" in docker) Xcontainercommand="$Xcontainercommand \\ --ipc=shareable" ;; esac ;; esac case "$Runtime" in "") ;; kata-runtime) ;; ### FIXME check sysbox-runc) [ "$Sharegpu" = "virgl" ] || grep -q "xwayland" <<< "$Xserver" && { # virgl and Xwayland need shared device files what is not supported yet by sysbox case "$Backend" in docker) Xcontainercommand="$Xcontainercommand \\ --runtime runc" ;; podman) Xcontainercommand="$Xcontainercommand \\ --runtime crun" ;; *) ;; esac } || { Xcontainercommand="$Xcontainercommand \\ --runtime $Runtime" } ;; *) Xcontainercommand="$Xcontainercommand \\ --runtime $Runtime" ;; esac # add code snippets case "$Xserver" in --hostdisplay) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_nomitshm" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; --xorg) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_gpu" Xcontainercommand="$Xcontainercommand $Xc_console" experimental "add --pid=host to Xorg container" && Xcontainercommand="$Xcontainercommand --pid=host" ;; --xephyr) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_nomitshm" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; --xvfb) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; --nxagent) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_nomitshm" Xcontainercommand="$Xcontainercommand $Xc_gpu" Xcontainercommand="$Xcontainercommand \\ --env NX_CLIENT=$Nxagentclientrc" ;; --xpra) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" [ -n "$Hostdisplay" ] && [ -z "$Hostwaylandsocket" ] && { Xcontainercommand="$Xcontainercommand $Xc_hostx" } [ "$Hostwaylandsocket" ] && { Xcontainercommand="$Xcontainercommand $Xc_hostwayland" } Xcontainercommand="$Xcontainercommand $Xc_nomitshm" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; --xpra2) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; --xpra-xwayland) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_containerwayland" Xcontainercommand="$Xcontainercommand $Xc_gpu" Xcontainercommand="$Xcontainercommand $Xc_nomitshm" ;; --weston-xwayland) case "$Newxvt" in "") Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_hostwayland" Xcontainercommand="$Xcontainercommand $Xc_containerwayland" Xcontainercommand="$Xcontainercommand $Xc_nomitshm" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; *) Xcontainercommand="$Xcontainercommand $Xc_capdrop" #Xcontainercommand="$Xcontainercommand $Xc_user" # disabled to allow tty switch Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_containerwayland" Xcontainercommand="$Xcontainercommand $Xc_gpu" Xcontainercommand="$Xcontainercommand $Xc_weston" Xcontainercommand="$Xcontainercommand $Xc_console" ;; esac ;; --xpra2-xwayland) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_containerwayland" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; --xwayland) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerx" Xcontainercommand="$Xcontainercommand $Xc_hostwayland" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; --weston) case "$Newxvt" in "") Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_hostwayland" Xcontainercommand="$Xcontainercommand $Xc_containerwayland" Xcontainercommand="$Xcontainercommand $Xc_nomitshm" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; *) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerwayland" Xcontainercommand="$Xcontainercommand $Xc_gpu" Xcontainercommand="$Xcontainercommand $Xc_weston" Xcontainercommand="$Xcontainercommand $Xc_console" ;; esac ;; --kwin) case "$Newxvt" in "") Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand $Xc_nopriv" Xcontainercommand="$Xcontainercommand \\ --cap-add SYS_RESOURCE" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_hostx" Xcontainercommand="$Xcontainercommand $Xc_hostwayland" Xcontainercommand="$Xcontainercommand $Xc_containerwayland" Xcontainercommand="$Xcontainercommand $Xc_nomitshm" Xcontainercommand="$Xcontainercommand $Xc_gpu" ;; *) Xcontainercommand="$Xcontainercommand $Xc_capdrop" Xcontainercommand="$Xcontainercommand \\ --cap-add SYS_RESOURCE" Xcontainercommand="$Xcontainercommand \\ --env XDG_SESSION_ID=$XDG_SESSION_ID" Xcontainercommand="$Xcontainercommand $Xc_user" Xcontainercommand="$Xcontainercommand $Xc_containerwayland" Xcontainercommand="$Xcontainercommand $Xc_gpu" Xcontainercommand="$Xcontainercommand $Xc_console" ;; esac ;; *) error "create_xcontainercommand(): Unknown X server option $Xserver" return 1 ;; esac Xcontainercommand="$Xcontainercommand \\ $Xcontainerimage bash /xcontainerrc" # xcontainerrc echo "#! /bin/bash # script running in X server container (option --xc) exec >> $Xinitlogfile 2>&1 LD_PRELOAD= cp /XlibNoSHM.so $Sharefolder/XlibNoSHM.so $(declare -f rocknroll) Timetosaygoodbyefile=$Timetosaygoodbyefile mkdir -p -m 1777 /tmp/.X11-unix [ -S /X${Hostdisplaynumber} ] && ln -v -s /X$Hostdisplaynumber /tmp/.X11-unix/X$Hostdisplaynumber #[ -S /${Hostwaylandsocket} ] && ln -v -s /$Hostwaylandsocket \$XDG_RUNTIME_DIR/$Hostwaylandsocket ls -la /tmp/.X11-unix # --init cp /usr/bin/catatonit $Sharefolder/catatonit " >> "$Xcontainerrc" echo ' # set host cookie to ffff, just in case xauth failed on host in check_hostxenv() [ -n "$XAUTHORITY" ] && { Cookie="$(xauth -n -i -f "${XAUTHORITY:-}" nlist "$DISPLAY" 2>/dev/null | sed -e "s/^..../ffff/")" echo "$Cookie" | xauth -n -i -f "$HOME/Xauthority.host" nmerge - truncate -s0 "${XAUTHORITY:-}" cat "$HOME/Xauthority.host" >> "${XAUTHORITY:-}" } ' >> "$Xcontainerrc" [ "$Sharegpu" != "no" ] && echo " # --gpu=virgl virgl_test_server & " >> "$Xcontainerrc" echo " echo 'X server container is ready' # wait for the end case $Usemkfifo in yes) read -n1 goodbye <$Timetosaygoodbyefifo ;; no) while rocknroll; do sleep 1; done ;; esac # avoid freeze on console that happens if container stops before Xorg or weston is down killall Xorg weston sleep 1 exit 0 " >> "$Xcontainerrc" return 0 } create_xinitrc() { # create xinitrc: set up X environment, create cookies echo "#! /bin/sh" declare -f cookiebaker declare -f strlenhex declare -f pspid declare -f disable_xhost declare -f rocknroll declare -f storeinfo declare -f saygoodbye echo "$Messagefifofuncs" case "$Xcontainer" in yes) echo "storepid() { : ; }" echo "unpriv() {" echo ' eval "${1:-}"' echo "}" ;; no) declare -f storepid declare -f unpriv echo "Unpriv='$Unpriv'" ;; esac echo "xtool() {" echo ' [ "${1:-}" = "--check" ] && command -v "${2:-}" && return' echo ' eval ${1:-}' echo "}" echo "getscreensize() {" echo " CurrentXaxis=\"\$(xrandr | grep primary | cut -d' ' -f4 | cut -dx -f1 )\"" echo " CurrentYaxis=\"\$(xrandr | grep primary | cut -d' ' -f4 | cut -dx -f2 | cut -d+ -f1)\"" echo "}" echo "checkscreensize() {" echo " getscreensize" echo " [ \"\$Xaxis\" = \"\$CurrentXaxis\" ] || return 1" echo " [ \"\$Yaxis\" = \"\$CurrentYaxis\" ] || return 1" echo " return 0" echo "}" echo "getprimary() {" echo " xrandr | grep -q primary || xrandr --output \$(xrandr | grep ' connected' | head -n1 | cut -d' ' -f1) --primary" echo " echo \$(xrandr | grep primary | cut -d' ' -f1)" echo "}" echo "" echo "Messagefile='$Messagefifo'" echo "Output=\"\$(getprimary)\"" echo "Storeinfofile='$Storeinfofile'" echo "Storepidfile='$Storepidfile'" echo "Timetosaygoodbyefile='$Timetosaygoodbyefile'" echo "" echo "export PATH='${PATH:-}'" echo "" echo "Cookie=''" echo "Line=''" echo "Var=''" echo "" echo "debugnote 'Running xinitrc'" echo "" case "$Xserver" in --weston|--kwin|--hostwayland) echo "export $Newxenv" echo "unset DISPLAY XAUTHORITY" echo "export DISPLAY XAUTHORITY" ;; --tty) echo "unset DISPLAY XAUTHORITY WAYLAND_DISPLAY" echo "export DISPLAY XAUTHORITY WAYLAND_DISPLAY" ;; --runx) [ "$Xauthentication" != "no" ] && { echo "# cookie generated by runx" echo 'debugnote "xinitrc: Option --runx: Using cookie: $XAUTHORITY"' echo "cp -T \"\$XAUTHORITY\" '$Xclientcookie'" echo "cp -T \"\$XAUTHORITY\" '$Xservercookie'" } echo "export $Newxenv" ;; *) # here something for real X servers echo "export $Newxenv" case "$Xoverip" in socat) case "$Xcnetworkid" in "") echo "echo \"socat -d TCP-LISTEN:$((6000+Newdisplaynumber)),fork,bind=${Xcontainerip:-$Hostip} UNIX-CONNECT:$Newxsocket\"" echo "socat -d TCP-LISTEN:$((6000+Newdisplaynumber)),fork,bind=${Xcontainerip:-$Hostip} UNIX-CONNECT:$Newxsocket &" ;; *) echo "echo \"socat -d TCP-LISTEN:$((6000+Newdisplaynumber)),fork,bind=$Xcontainername UNIX-CONNECT:$Newxsocket\"" echo "socat -d TCP-LISTEN:$((6000+Newdisplaynumber)),fork,bind=$Xcontainername UNIX-CONNECT:$Newxsocket &" ;; esac echo "storepid \$! socat-tcp" ;; esac echo "" case "$Xserver" in --hostdisplay) echo "# --hostdisplay: need cookie from host" ### FIXME rather get a trusted one echo "Hostcookie=\"\$(xauth -n -i -f $Hostxauthority list | grep :$Hostdisplaynumber | awk '{print \$3}')\"" echo "xauth -n -i -f $Xclientcookie add $Newdisplay MIT-MAGIC-COOKIE-1 \$Hostcookie" echo "" ;; --nxagent) echo "sleep 1 && xsetroot -solid '#7F7F7F' 2>/dev/null &" ;; *) echo "xsetroot -solid '#7F7F7F' 2>/dev/null" ;; esac echo "" [ "$Xauthentication" != "no" ] && { echo "# create new XAUTHORITY cookies" case "$Xauthentication" in yes|trusted) echo "Trusted=trusted" ;; untrusted) echo "Trusted=untrusted" ;; esac echo "echo \"Requesting \$Trusted cookie from X server\"" echo "xauth -v -n -i -f $Xclientcookie generate $Newdisplay . \$Trusted timeout 3600" echo "[ '$Xserver' = '--hostdisplay' ] && sed -i /\$Hostcookie/d $Xclientcookie" echo "" echo "[ -s '$Xclientcookie' ] || { " echo " :" case "$Xauthentication" in trusted|untrusted) echo " error 'Failed to create $Xauthentication cookie. Maybe your X server misses extension SECURITY.'" ;; esac echo "}" echo "[ -s '$Xclientcookie' ] || { " echo " # still no cookie? try to create one without extension security" echo " echo 'Failed to retrieve trusted cookie from X server. Will bake one directly with xauth'" echo " xauth -v -n -i -f $Xclientcookie add :$Newdisplaynumber . $(makecookie)" echo "}" echo "[ -s '$Xclientcookie' ] && {" echo " # Prepare cookie with localhost identification disabled by ffff, needed if X socket is shared. ffff means 'familiy wild'" echo " Cookie=\"\$(xauth -n -i -f $Xclientcookie nlist | sed -e 's/^..../ffff/')\"" echo " truncate -s0 $Xclientcookie" echo " echo \"\$Cookie\" | xauth -v -n -i -f $Xclientcookie nmerge -" echo "}" echo "[ -s '$Xclientcookie' ] || {" echo " debugnote 'Failed to create cookie with xauth. Will try custom cookie baker script.'" echo " cookiebaker '$Newdisplay' >> $Xclientcookie" echo "}" echo "ls -l $Xclientcookie" echo "truncate -s0 $Xservercookie" echo "cat $Xclientcookie >> $Xservercookie" echo "chmod 644 $Xclientcookie" echo "" echo "[ -s '$Xclientcookie' ] || error 'xinitrc(): Option --xauth=$Xauthentication: Cookie creation failed.'" } echo "export XAUTHORITY=$Xclientcookie" echo "[ '$Xauthentication' = 'no' ] || [ ! -s '$Xclientcookie' ] && unset XAUTHORITY && warning 'Option --xauth=no: X server $Newdisplay runs without cookie authentication.'" echo "" case "$Xserver" in --hostdisplay) ;; # do not change host settings --xwin) ;; # xhost does not work over tcp *) case "$Xauthentication" in yes|trusted|untrusted) echo "# clean xhost" echo "verbose 'Disabling any possible access to new X server possibly granted by xhost'" echo "disable_xhost" ;; esac [ -n "$Xhost" ] && { [ "$Xhost" = "auto" ] && Xhost="+SI:localuser:$Containeruser" echo "warning \"Option --xhost: Running 'xhost $Xhost' on $Newdisplay\"" echo "xhost $Xhost" } echo "" ;; esac case "$Xserver" in --hostdisplay|--xwin|--nxagent) ;; --hostwayland|--weston|--kwin|--tty) ;; *) echo "# Keyboard layout" [ -n "$Hostdisplay" ] && { xtool --check setxkbmap && { case "$Xkblayout" in "") #setxkbmap -display "$Hostdisplay" -print >> "$Xkbkeymapfile" echo "env DISPLAY=$Hostdisplay XAUTHORITY=$Hostxauthority setxkbmap -display $Hostdisplay -print >> $Xkbkeymapfile" ;; *) echo "setxkbmap '$Xkblayout' -print >> $Xkbkeymapfile" ;; esac } [ -s "$Xkbkeymapfile" ] && { echo "# set keyboard layout on $Newdisplay" echo "verbose \"Keyboard layout:" echo "\$(cat $Xkbkeymapfile)\"" echo "xkbcomp $Xkbkeymapfile $Newdisplay" } echo "" } ;; esac ;; esac case "$Xserver" in --xpra|--xpra2|--xvfb) echo "# create set of different screen resolutions" echo "[ -e \"$Modelinefile\" ] && while read Line; do" echo " Line=\"\$(echo \"\$Line\" | sed 's/Modeline//g')\"" echo " Line=\"\$(echo \"\$Line\" | sed 's/\"//g')\"" echo " xrandr --newmode \$Line 2>/dev/null" echo " xrandr --addmode \"\$Output\" \$(echo \$Line | cut -d' ' -f1) 2>/dev/null" echo "done < \"$Modelinefile\"" [ -n "$Modeline" ] && { echo "xrandr --newmode $Modeline" echo "xrandr --addmode \$Output $(echo $Modeline | cut -d " " -f1)" [ "$Xserver" != "--xvfb" ] && [ "$Desktopmode" = "yes" ] && echo "xrandr --output \$Output --mode $(echo $Modeline | cut -d " " -f1)" } echo "" ;; --xorg) # --xorg: --scale, --size, --rotate echo "# determine screen size" echo '[ -n "$(xrandr | grep connected | grep -v disconnected)" ] && {' [ -z "$Screensize" ] && { echo " getscreensize" echo " Xaxis=\"\$CurrentXaxis\"" echo " Yaxis=\"\$CurrentYaxis\"" [ "$Scaling" ] && echo " Xaxis=\"\$(awk -v a=\"\$Xaxis\" -v b=\"$Scaling\" 'BEGIN {print (a / b)}')\"" echo " Xaxis=\"\${Xaxis%.*}\"" [ "$Scaling" ] && echo " Yaxis=\"\$(awk -v a=\"\$Yaxis\" -v b=\"$Scaling\" 'BEGIN {print (a / b)}')\"" echo " Yaxis=\"\${Yaxis%.*}\"" } || { echo " Xaxis='$Xaxis'" echo " Yaxis='$Yaxis'" } echo " Screensize=\"\${Xaxis}x\${Yaxis}\"" echo "" [ "$Screensize" ] && [ -z "$Scaling" ] && { echo " # Switch to desired screen size $Screensize" echo " [ -n \"\$(xrandr | grep \$Screensize)\" ] && { " echo " note \"Will try to set native resolution \$Screensize." echo " If that looks ugly, use --scale=1 to enforce a fake scaled resolution.\"" echo " xrandr --output \$Output --mode \$Screensize" echo " } || note \"Resolution \$Screensize not found in xrandr.\"" echo "" } [ "$Screensize" ] && [ -z "$Scaling" ] && { echo " checkscreensize || {" echo " note \"Panning \$Screensize. If virtual screen is greater than " echo " maximal screen size, you can move virtual screen with mouse at screen edges." echo " You can force the virtual screen to match your monitor with option --scale=1\"" echo " xrandr --output \$Output --panning \$Screensize+0+0/\$Screensize+0+0/100/100/100/100 --verbose" echo ' }' echo " checkscreensize || {" echo " note 'Panning failed, trying to scale instead.'" echo " xrandr --output \$Output --scale-from \$Screensize --panning \$Screensize+0+0/\$Screensize+0+0" echo " checkscreensize && note \"Successfully set screen size \$Screensize\"" echo ' }' echo " checkscreensize || {" echo " getscreensize" echo " note \"Setting desired resolution \$Screensize failed." echo " Fallback: Will use detected \${CurrentXaxis}x\${CurrentYaxis} instead.\"" echo ' }' echo "" } [ "$Scaling" ] && { echo " # --scale $Scaling" [ "$Screensize" ] && [ "$Scaling" != "1" ] && echo " note 'Cannot set panning and scaling at the same time. Desired screen size $Screensize will be scaled to your monitor size for arbitrary values you may provide with option --scale.'" echo " # Scaling $Scaling" echo " note \"Setting scaled resolution \$Screensize\" with scale factor $Scaling." # must use --scale-from and --panning because --scale causes mouse barriers/crtc-boundaries echo " xrandr --output \$Output --scale-from \$Screensize --panning \$Screensize+0+0/\$Screensize+0+0 --verbose" echo " checkscreensize || {" echo " getscreensize" echo " note \"Setting desired resolution \$Screensize failed." echo " Detected resolution \${CurrentXaxis}x\${CurrentYaxis} instead.\"" echo " }" echo "" } [ -n "$Rotation" ] && { echo " # --rotate $Rotation" echo " verbose 'Rotation $Rotation'" case "$Rotation" in 0|normal) Rotation="" ;; 90) Rotation="--rotate right";; 180) Rotation="--reflect xy" ;; 270) Rotation="--rotate left";; flipped) Rotation="--reflect y";; flipped-90) Rotation="--rotate right --reflect x";; flipped-180) Rotation="--reflect x";; flipped-270) Rotation="--rotate left --reflect x";; esac echo " bash -c 'while read Line ; do xrandr --output \$Line $Rotation ; done < <(xrandr | grep \" connected\" | cut -d \" \" -f1)'" echo "" } echo " :" echo "} || {" echo " Xaxis=${Xaxis:-1024}" echo " Yaxis=${Yaxis:-768}" echo " Screensize=\"\${Xaxis}x\${Yaxis}\"" echo " note \"Could not detect any connected monitor." echo " Running on a server? Is xrandr installed? Will try to set a framebuffer size" echo " with \"xrandr --fb \$Screensize\" that may serve as a virtual display.\"" echo " xrandr --fb \$Screensize" echo "}" echo "" ;; esac [ -n "$Newdisplay" ] && echo "verbose \"Output of xrandr on $Newdisplay \$(xrandr)\"" echo "" # --wm [ "$Windowmanagermode" = "host" ] && { echo "# window manager" echo "$Windowmanagercommand & storepid \$! windowmanager" echo "# only one desktop" echo "sleep 1 && wmctrl -n 1 &" } # --xfishtank [ "$Xfishtank" = "yes" ] && echo "xfishtank & storepid \$! xfishtank" echo "echo 'xinitrc: xinitrc is ready'" #echo "storeinfo xinitrc=ready" echo "" # --clipboard [ "$Shareclipboard" != "no" ] && { case "$Xserver" in # --xpra*|--nxagent|--xwin|--runx) ;; # have their own clipboard management --xwin|--runx) ;; # have their own clipboard management --hostdisplay) ;; # already same clipboard *) # synchronizing between different X servers echo "# option '-c, --clipboard': Run clipboard script " echo "bash $Clipboardrc &" echo "" ;; esac } # --checkwindow [ "$Checkforopenwindow" ] && { echo "sleep 5" echo "while rocknroll; do" echo " sleep 2" case "$Checkforopenwindow" in yes) echo " [ \"\$(xwininfo -root -children | grep -v '(has no name)' | awk '{print \$1}' | grep -c ^0x)\" = '0' ] && break" ;; *) echo " xwininfo -root -children | grep -q '$Checkforopenwindow' || break" ;; esac echo "done" echo "saygoodbye" } echo "# wait for the end" case "$Usemkfifo" in yes) echo "read Var <$Timetosaygoodbyefifo" ;; no) echo "while rocknroll; do sleep 1; done" ;; esac return 0 } disable_xhost() { # remove any access to X server granted by xhost local Line= Environment Environment="${1:-"DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY"}" xtool --check xhost || return 1 xtool "env $Environment xhost 2>&1 | tail -n +2 /dev/stdin" | while read -r Line ; do # read all but the first line (header) debugnote "xhost: Removing entry $Line" xtool "env $Environment xhost -'$Line'" # disable every entry done xtool "env $Environment xhost -" # enable access control [ "$(xtool "env $Environment xhost 2>&1 | wc -l")" -gt "1" ] && { warning "Remaining xhost permissions found on display ${DISPLAY:-} $(xtool "env $Environment xhost 2>&1" )" return 1 } xtool "env $Environment xhost 2>&1" | grep -q "access control disabled" && { warning "Failed to restrict xhost permissions. Access to display ${1:-} is allowed for everyone." return 1 } return 0 } setup_xcnetwork() { # --xoverip --xc: start internal network between X container and command container local Internal [ "$Backend" = "$Xcontainerbackend" ] && [ "$Xcrootless" = "$Backendrootless" ] || return 1 case "${1:-}" in "") [ "$Xcontainer" = "yes" ] && [ "$Hostxoverip" = "no" ] && { case "$Xoverip" in yes|listentcp|socat) case "$Network" in none) Internal="--internal" ;; ""|bridge) Internal="";; *) return 1 ;; ### FIXME esac Xcnetworkid="$(unpriv_backend "$Backendbin network create $Internal $Xcnetworkname")" 2>>$Xinitlogfile || return 1 debugnote "setup_xcnetwork(): Creating $Xcnetworkname $Xcnetworkid" ;; esac } ;; remove) [ -n "$Xcnetworkid" ] && { debugnote "setup_xcnetwork(): Removing $Xcnetworkname $Xcnetworkid" unpriv_backend "$Backendbin network remove $Xcnetworkid" >>$Xinitlogfile 2>&1 || return 1 } ;; esac return 0 } store_newxenv() { # store display variables Newdisplay="${Newdisplay//"XCONTAINERIP"/"$Xcontainerip"}" # create $Newxenv: collection of environment variables to access new X from host (e.g. in xinitrc) [ "$Newdisplay" ] && storeinfo "DISPLAY=$Newdisplay" && Newxenv="$Newxenv DISPLAY=$Newdisplay" [ "$Xauthentication" != "no" ] && storeinfo "XAUTHORITY=$Xclientcookie" && Newxenv="$Newxenv XAUTHORITY=$Xclientcookie" [ "$Newxsocket" ] && storeinfo "XSOCKET=$Newxsocket" && Newxenv="$Newxenv XSOCKET=$Newxsocket" [ "$Newwaylandsocket" ] && storeinfo "WAYLAND_DISPLAY=$Newwaylandsocket" && Newxenv="$Newxenv WAYLAND_DISPLAY=$Newwaylandsocket" [ "$Setupwayland" = "yes" ] && for Line in $Waylandtoolkitenv ; do Newxenv="$Newxenv $Line" ; done [ -n "$XDG_RUNTIME_DIR" ] && storeinfo "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" && Newxenv="$Newxenv XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" Newxenv_cr="$(grep . <<< "$Newxenv")" Newxenv="$(echo $Newxenv)" storeinfo "Xenv=$Newxenv" # X / Wayland environment variables for container case "$Xserver" in --xpra*|--xephyr|--weston-xwayland|--hostdisplay|--xorg|--xvfb|--xwayland|--kwin-xwayland|--nxagent|--xwin|--runx) store_runoption env "DISPLAY=$Newdisplay" [ "$Xauthentication" != "no" ] && store_runoption env "XAUTHORITY=$(convertpath share "$Xclientcookie")" ;; --weston|--kwin|--hostwayland|--tty) store_runoption env "WAYLAND_DISPLAY=$Newwaylandsocket" ;; esac [ "$Setupwayland" = "yes" ] && { for Line in $Waylandtoolkitenv; do store_runoption env "$Line" done } return 0 } xtool() { # run X tool command in X container if available, otherwise on host local Tool Toolinfo Usexcontainer Check= Nocheck= Message Message="note" case "${1:-}" in --check) Check="yes" shift ;; --nocheck) Check="no" Message=":" shift ;; esac case "${1:-}" in note|debugnote|verbose|warning|error) Message="${1:-}" shift ;; --quiet) Message=":" shift ;; esac # extract tool from "env a=b tool args" Tool="$(sed 's/\S*\(=\|env \)\S*//g ; s/^ *//' <<< "${1:-}" | cut -d' ' -f1)" case "$Tool" in cvt) Toolinfo="x11docker uses cvt to create a set of screen resolutions that can be seen and set by tools like xrandr." ;; setxkbmap|xkbcomp) Toolinfo="x11docker uses setxkbmap and xkbcomp to set the keyboard in new X server to same as on host or specified with --keymap." ;; socat) Toolinfo="socat allows to set up TCP access to X unix sockets." ;; vainfo) Toolinfo="vainfo helps to configure VAAPI video decoding support." ;; wl-copy|wl-paste) Toolinfo="x11docker uses wl-copy and wl-paste for Wayland clipboard support." ;; wmctrl) Toolinfo="x11docker uses wmctrl to configure window managers to provide one virtual desktop only." ;; xauth) Toolinfo="xauth creates and adjusts X authentication cookies." ;; xbindkeys) Toolinfo="xbindkeys intercepts SUPER+v or ALT+v for option --clipboard." ;; xclip) Toolinfo="xclip is needed for option --clipboard." ;; xdpyinfo) Toolinfo="xdpyinfo provides some information about the host X server. It tells x11docker about dpi and installed extensions like MIT-SHM." ;; xdotool) Toolinfo="x11docker uses xdotool to hide the weston window that is used in background for --xpra-xwayland." ;; xfishtank) Toolinfo="xfishtank shows a fish tank with --xfishtank." ;; xhost) Toolinfo="xhost controls network access to X. x11docker uses it to disable undesired network access, or to allow some with option --xhost=ARG." ;; xinit) Toolinfo="xinit serves to properly start and stop most X servers." ;; xrandr) Toolinfo="xrandr allows to check or set the screen size (option --size). with --xorg it also helps at options --rotate and --scale." ;; xwininfo) Toolinfo="x11docker needs xwininfo for option --checkwindow to get a list of currently open windows." ;; esac case "$Xcontainer" in yes) Usexcontainer="yes" echo "$Xcontainertools" | grep -w -q "$Tool" || { $Message "Option --xc: Please update image x11docker/xserver. Did not find command '$Tool' in image. Will try to run '$Tool' on host" Usexcontainer="no" } ;; no) Usexcontainer="no" ;; esac [ "$Check" = "no" ] && Usexcontainer="${Usexcontainer:-yes}" Usexcontainer="${Usexcontainer:-no}" # in case xinitrc already runs in X container. verbose "xtool(): (use xc $Usexcontainer): ${1:-}" case "$Usexcontainer" in no) command -v "$Tool" >/dev/null || { $Message "xtool(): Command '$Tool' not found on host. Please install $Tool. $Toolinfo $Wikipackagesimage" return 1 } ;; esac [ "$Check" = "yes" ] && return 0 case "$Usexcontainer" in yes) unpriv_xcbackend "$Xcontainerbackend exec $Xcontainername ${1:-}" ;; no) unpriv "${1:-}" ;; esac return $? } #### backend command setup check_backend() { # options --backend, --rootless local Checkrootfs= case "$Mobyvm" in no) Backendbin="$(command -v "$Backend")" ;; yes) case "$Backend" in docker|host) ;; *) error "Option --backend=$Backend is not supported on MS Windows yet. If you need this, ask for support at https://github.com/mviereck/x11docker" return 1 ;; esac Backendbin="docker.exe" command -v "$Backendbin" >/dev/null || { PATH="${PATH:-}:$(convertpath subsystem "C:/Program Files/docker"):$(convertpath subsystem "C:/Program Files/Docker/Docker/resources/bin")" export PATH } ;; esac case "$Backend" in docker|podman|nerdctl|proot) case "$Installermode" in "") [ -z "$Backendbin" ] && error "Option --backend=$Backend: No binary found for $Backend." ;; *) [ -z "$Backendbin" ] && note "Option --backend=$Backend: No binary found for $Backend." return 1 ;; esac ;; host) Backendbin="" ;; esac case "$Backend" in docker|podman|nerdctl) if [ -n "$Jqbin" ]; then debugnote "check_backend(): will parse json using $Jqbin" elif [ -n "$Pythonbin" ]; then debugnote "check_backend(): will parse json using $Pythonbin" else error "x11docker needs 'jq' or 'python' to parse output of '$Backend inspect'. Please install 'jq' or 'python' version 2.x or 3.x." fi ;; esac # rootful or rootless [ "$Backendrootless" = "no" ] && export DOCKER_HOST= [ -z "$Backendrootless" ] && case "$Backend" in docker) id -G -n | grep -q -w 'docker' && Backendrootless="no" grep -q "/run/user/" <<< "${DOCKER_HOST:-}" && Backendrootless="yes" [ "$(id -un)" = "root" ] && Backendrootless="no" ;; podman|nerdctl) [ "$(id -u)" = "0" ] && Backendrootless="no" || Backendrootless="yes" ;; proot) ;; host) ;; esac case "$Backendrootless" in yes) case "$Backend" in docker) DOCKER_HOST="${DOCKER_HOST:-/run/user/$Hostuseruid/docker.sock}" DOCKER_HOST="${DOCKER_HOST#unix://}" [ -e "${DOCKER_HOST:-}" ] || error "Option --rootless --backend=docker: docker user socket not found. Docker user daemon not running? Try to start it with: systemctl --user start docker" DOCKER_HOST="unix://$DOCKER_HOST" export DOCKER_HOST ;; esac ;; no) case "$Backend" in docker) [ "$(id -un)" = "root" ] || id -G -n | grep -q -w 'docker' || error "Option --rootless=no: Please run x11docker as root to allow rootful $Backend." ;; podman|nerdctl) [ "$(id -un)" != "root" ] && error "Option --rootless=no: Please run x11docker as root to allow rootful $Backend." ;; esac ;; esac case "$Backend" in docker|podman|nerdctl) # Check whether docker daemon is running, get backend info Backendinfo="$(unpriv_backend "env LC_ALL=C $Backendbin info --format='{{json .}}'" 2>&1)" [ "$?" != "0" ] || grep -q "permission denied" <<< "$Backendinfo" || [ -z "$Backendinfo" ] && error "Option --backend=$Backend: Error executing $Backend. If using docker: Is docker daemon running at all? Maybe you need root privileges to run docker? Try to start docker daemon with 'systemctl start docker'. Try to run x11docker as root. Output of '$Backendbin info': $Backendinfo" check_runtime ;; esac # check image name case "$Backend" in docker|podman|nerdctl|proot) [ "${Imagename:0:1}" = "/" ] && { Rootfs="$Imagename" Imagename="$(basename "$Rootfs")" } [ "$Podmanrootfs" = "yes" ] && Checkrootfs="yes" Imagebasename="$(echo "$Imagename" | tr / - | cut -d: -f1)" Codename="${Codename:-"$Imagename $Containercommand"}" verbose "Image name: $Imagename Container command: $Containercommand" ;; host) Containercommand="$Imagename $Containercommand" [ "$Customdockeroptions" ] && error "Option --backend=$Backend does not take CUSTOM_RUN_OPTIONS. If your command args contain ' -- ', run like x11docker --backend=$Backend [OPTIONS] -- -- COMMAND -- ARGS" Imagename="" Imagebasename="$(basename "$Containercommand" | cut -d' ' -f1)" Codename="${Codename:-"$Imagebasename"}" ;; esac Codename="$(unspecialstring "$Codename" | cut -c1-40)" Codename="${Codename:-noname}" Imagebasename="$(unspecialstring "$Imagebasename")" # must be - for backwards compatibility of --home Imagebasename="${Imagebasename:-noname}" case "$Backend" in docker) ;; podman) # /proc/sys/kernel/unprivileged_userns_clone might exist on debian only. # https://github.com/mviereck/x11docker/issues/255#issuecomment-758014962 [ "$(cat /proc/sys/kernel/unprivileged_userns_clone 2>/dev/null)" = "0" ] && error "Option --backend=podman: Linux kernel disallows unprivileged user namespace setup. Please run as root: sysctl -w kernel.unprivileged_userns_clone=1" store_runoption cap "CHOWN" ;; nerdctl) note "Option --backend=nerdctl: Support of nerdctl is experimental yet. In production rather use docker or podman for now." Switchcontaineruser="yes" [ "$Capdropall" = "yes" ] && note "Option --backend=nerdctl: If you encounter issues, please try with option --cap-default to remove some container restrictions." ;; proot) warning "Option --backend=$Backend: Your host system is NOT PROTECTED. There is close to no isolation like a real container could provide. Effectively applications in proot have the same privileges as if they would have been installed and started directly on the host system." check_optionset "--backend=$Backend" "--cap-default --hostipc --ipc --limit --network --newprivileges --no-setup --runtime --sharecgroup --user" ||: check_optionset "--backend=$Backend" "--user" || error "Option --backend=$Backend: Option --user is not allowed. Please try option --hostuser instead." Checkrootfs="yes" # set defaults. Some might look misleading just to avoid later messages, but not affecting the setup. Network="host" Shareipc="host" Createcontaineruser="yes" Containersetup="yes" Capdropall="yes" Allownewprivileges="no" Runtime="" Sharecgroup="no" Limitresources="" # needed Switchcontaineruser="yes" [ "$Sudouser" ] && note "Option --backend=$Backend --sudouser: sudo fails in $Backend. Workaround: starting with user root. You can switch to user $Containeruser with 'su $Containeruser'. Note that root in $Backend is a fake and does not have real root privileges." ;; host) warning "Option --backend=host provides no isolation at all. It only provides an X server for a host application." storeinfo "containerrootrc=ready" Sharefoldercontainer="$Sharefolder" # --dbus, --hostdbus, --init check_optionset "--backend=$Backend" "--alsa --cap-default --group-add --hostipc --ipc --limit --home --name --network --newprivileges --no-entrypoint --no-setup \ --printer --pulseaudio --runasroot --runtime --share --sharecgroup --sudouser --webcam" ||: check_optionset "--backend=$Backend" "--user" || error "Option --backend=$Backend: Option --user is not allowed. Please try option --hostuser instead." # set defaults. Some might look misleading just to avoid later messages, but not affecting the setup. Sharealsa="no" Capdropall="yes" Shareipc="host" [ "$Langwunsch" ] && store_runoption env "LANG=$Langwunsch" Limitresources="" Network="host" Allownewprivileges="no" Noentrypoint="" Containersetup="yes" Sharecupsmode="" Pulseaudiomode="" Runasroot="" Runtime="" Sharevolumes="" Sharecgroup="no" Sudouser="" Sharewebcam="" ;; esac [ "$Checkrootfs" = "yes" ] && { case "$Backend" in proot) ;; podman) note "Option --backend=podman with --rootfs is experimental yet." [ "$Containersetup" = "yes" ] && Switchcontaineruser="yes" ;; *) error "Option --rootfs is supported by backends proot and podman only." ;; esac [ -z "$Rootfs" ] && [ -d "$Hosthomebasefolder/ROOTFS/$Imagebasename" ] && Rootfs="$Hosthomebasefolder/ROOTFS/$Imagebasename" [ -z "$Rootfs" ] && error "Option --backend=$Backend: Did not find a rootfs for $Imagename in $Hosthomebasefolder/ROOTFS/$Imagebasename" [ -z "$Containercommand" ] && [ -e "$Rootfs/start" ] && Containercommand="/bin/sh /start" [ -z "$Containercommand" ] && { [ -e "$Rootfs/bin/bash" ] && Containercommand="/bin/bash" || Containercommand="/bin/sh" [ "$Containerusershell" != "auto" ] && Containercommand="$Containerusershell" note "Option --backend=$Backend: No command specified. Fallback: using command '$Containercommand' and enabling option --interactive." check_fallback Interactive="yes" } } debugnote "Backend: $Backend, Backendbin: $Backendbin, Rootless: $Backendrootless" return 0 } check_image() { # get some image information # Check if image $Imagename is available locally Imageinspect="$(unpriv_backend "$Backendbin inspect $Imagename" 2>> $Containerlogfile)" || { error "Image $Imagename not found locally. Please pull or build image first. (Backend $Backend, rootless $Backendrootless)" return 1 } # Check architecture Imagearchitecture=$(parse_inspect "$Imageinspect" "Architecture") debugnote "Image architecture: $Imagearchitecture" # Check CMD Imagecommand="$(parse_inspect "$Imageinspect" "Config" "Cmd")" debugnote "Image CMD: $Imagecommand" [ -z "$Containercommand" ] && { Containercommand="$Imagecommand" grep -q "$(convertpath share "$Containerrc")" <<< "$Imagecommand" && error "Recursion error: Found CMD $Imagecommand in image. Did you use 'docker commit' with an x11docker container? Please build new images with a Dockerfile instead of using docker commit, or provide a different container command in x11docker command." } # Check USER Imageuser="$(parse_inspect "$Imageinspect" "Config" "User")" debugnote "Image USER: $Imageuser" case "$Createcontaineruser" in yes) [ "$Imageuser" ] && note "Found 'USER $Imageuser' in image. If you want to run with user $Imageuser instead of host user $Containeruser, than run with option --user=RETAIN." ;; no) Containeruser="${Imageuser:-root}" ;; esac storeinfo "containeruser=$Containeruser" # Check ENTRYPOINT Imageentrypoint="$(parse_inspect "$Imageinspect" "Config" "Entrypoint")" debugnote "Image ENTRYPOINT: $Imageentrypoint" case "$Noentrypoint" in yes) Containerentrypoint="" ;; no) Containerentrypoint="$Imageentrypoint" case "$Initsystem" in systemd|sysvinit|runit|openrc|tini) grep -qE 'tini|init|systemd' <<< "$Containerentrypoint" && { note "There seems to be an init system in ENTRYPOINT of image: $Containerentrypoint Will disable it as x11docker already runs an init with option --$Initsystem. To allow this ENTRYPOINT, run x11docker with option --init=none." Containerentrypoint="" } ;; s6-overlay) [ "$Containerentrypoint" = '/init' ] && { Containerentrypoint="" [ "$Containercommand" ] || Containercommand="sh -c 'while :; do sleep 10; done'" } ;; none) grep -qE 'tini|init|systemd' <<< "$Containerentrypoint" && { note "There seems to be an init system in ENTRYPOINT of image: $Containerentrypoint Returning correct exit code of container command will likely fail." } ;; esac ;; esac # Check WORKDIR Imageworkdir="$(parse_inspect "$Imageinspect" "Config" "Workdir")" # It seems Config.Workdir is Config.WorkingDir in more recent versions? [ -z "$Imageworkdir" ] && { Imageworkdir="$(parse_inspect "$Imageinspect" "Config" "WorkingDir")" } debugnote "Image WORKDIR: $Imageworkdir" [ -z "$Workdir" ] && [ -n "$Imageworkdir" ] && { note "Found 'WORKDIR $Imageworkdir' in image. You can change it with option --workdir=DIR." Workdir="$Imageworkdir" } [ -z "$Containercommand$Containerentrypoint" ] && error 'No container command specified and no CMD or ENTRYPOINT found in image.' return 0 } check_runtime() { # option --runtime local Defaultruntime case "$Backend" in docker) Defaultruntime="$(parse_inspect "$Backendinfo" "DefaultRuntime" | tr -d '"')" ;; podman) Defaultruntime="$(parse_inspect "$Backendinfo" "host" "ociRuntime" "name" | tr -d '"')" ;; nerdctl) Defaultruntime="" ;; esac debugnote "Default runtime: $Defaultruntime" [ -z "$Runtime" ] && Runtime="$Defaultruntime" case "$Runtime" in ""|runc|crun|oci|nvidia|kata-runtime) ;; sysbox-runc) store_runoption env "SYSBOX_HONOR_CAPS=TRUE" check_optionset "--runtime=sysbox-runc" "--alsa --webcam" ||: verlt "$(uname -r)" "5.12" && error "Option --runtime=sysbox-runc needs at least Linux kernel version >=5.12" verlt "$(sysbox-runc -v | grep version | tr -d ' \t'| cut -d: -f2)" "0.5" && error "Option --runtime=sysbox-runc needs at least Sysbox version >=0.5.0" ;; *) note "Option --runtime: x11docker does not know runtime: '$Runtime' Will try to use it anyway. If that fails, you can try options --snap or --no-setup." ;; esac return 0 } check_smallinit() { # find container init binary on host [ "$Xcontainer" = "yes" ] && grep -q "catatonit" <<< "$Xcontainertools" && Initbinaryfile="$Sharefolder/catatonit" && return 0 [ -x "$Initbinaryfile" ] || Initbinaryfile="$(command -v catatonit ||:)" [ -x "$Initbinaryfile" ] || Initbinaryfile="/usr/libexec/catatonit/catatonit" [ -x "$Initbinaryfile" ] || Initbinaryfile="$(command -v docker-init ||:)" [ -x "$Initbinaryfile" ] || Initbinaryfile="/usr/local/share/x11docker/tini-static" [ -x "$Initbinaryfile" ] || Initbinaryfile="/usr/bin/tini-static" [ -x "$Initbinaryfile" ] || Initbinaryfile="$Localsharedir/tini-static" [ -x "$Initbinaryfile" ] || Initbinaryfile="/snap/docker/current/bin/docker-init" [ -x "$Initbinaryfile" ] || Initbinaryfile="/snap/docker/current/usr/bin/docker-init" Initbinaryfile="$(myrealpath "$Initbinaryfile" 2>/dev/null ||:)" [ -x "$Initbinaryfile" ] || Initbinaryfile="" [ -z "$Initbinaryfile" ] && { note "Option --init=$Initsystem: Did not find a tini container init system. Please install catatonit or tini-static. $Wikipackagesimage" return 1 } return 0 } check_cgroup() { # check [and create] cgroup mountpoint for systemd or elogind local Remounted Needcgroup [ "$Initsystem" = "systemd" ] && Needcgroup="systemd" || Needcgroup="elogind" [ "$Backend" = "docker" ] && { case "$Cgroupversion" in v1) Sharecgroup="yes" ;; v2) case "$Initsystem" in systemd) case "$Runtime" in sysbox-runc) ;; *) Remountcgroup="yes" warning "Option --init=systemd: To support systemd in docker container with cgroupv2 on host, x11docker shortly runs a privileged container of '$Imagename' with 'nsenter' to remount the container's /sys/fs/cgroup. Evil images might be set up to abuse these privileges." ;; esac ;; esac ;; esac case "$Initsystem" in sysvinit|openrc|runit) Sharecgroup="yes" ;; esac } [ "$Sharecgroup" = "yes" ] && warning "Option --init=$Initsystem: Sharing /sys/fs/cgroup from host." findmnt "/sys/fs/cgroup/$Needcgroup" >/dev/null && return 0 case "$Initsystem" in systemd) case "$Cgroupversion" in v1) [ "$Startuser" != "root" ] && note "Option --init=$Initsystem: Did not find /sys/fs/cgroup/$Needcgroup Startup of container is likely to fail." ;; v2) Needcgroup="" ;; esac ;; *) note "Option --init=$Initsystem: Did not find /sys/fs/cgroup/$Needcgroup A possible elogind service in container is likely to fail." ;; esac [ -z "$Needcgroup" ] && return 0 [ "$Startuser" != "root" ] && { note "Option --init=$Initsystem: To create and mount a cgroup for $Needcgroup, please run x11docker as root. Or create cgroup mountpoint on host yourself with: mount -o remount,rw cgroup /sys/fs/cgroup mkdir -p /sys/fs/cgroup/$Needcgroup mount -t cgroup cgroup /sys/fs/cgroup/$Needcgroup -o none,name=$Needcgroup" return 1 } [ "$Sharecgroup" = "yes" ] && [ "$Startuser" = "root" ] && { note "Option --init=$Initsystem: Creating cgroup mountpoint on host for '$Needcgroup'." findmnt /sys/fs/cgroup -O ro >/dev/null && { mount -o remount,rw cgroup /sys/fs/cgroup >> "$Containerlogfile" 2>&1 Remounted=yes } mkdir -p /sys/fs/cgroup/$Needcgroup >> "$Containerlogfile" 2>&1 mount -t cgroup cgroup /sys/fs/cgroup/$Needcgroup -o none,name=$Needcgroup >> "$Containerlogfile" 2>&1 [ "${Remounted:-}" = "yes" ] && { mount -o remount,ro cgroup /sys/fs/cgroup >> "$Containerlogfile" 2>&1 } } return 0 } check_containerhomebasedir() { # options --homebasedir, --home ## option '--homebasedir': Specify base folder here to store container home folders for --home case "$Backend" in proot) Hosthomebasefolder="$Localsharedir" ;; docker|podman|nerdctl) [ "$Hosthomebasefolder" ] || case "$Mobyvm" in no) Hosthomebasefolder="$Containeruserhosthome/.local/share/x11docker" ;; yes) Hosthomebasefolder="$(convertpath subsystem "$(wincmd 'echo %userprofile%') ")/x11docker/home" ;; esac ;; esac } check_containerhome() { # option --home: check HOME of container user. ## option '--home': Share folder ~/.local/share/x11docker/imagename with created container as its home directory ## option '--home=DIR': Share custom host folder as home # rootless with userns-remap [ "$Backendrootless" = "yes" ] && { case "$Backend" in docker|nerdctl) [ "$Sharehome" != "no" ] && { note "Option --home is not supported in $Backend rootless mode. In rootless mode only option --backend=podman supports option --home. Alternatively run one of docker, podman or nerdctl in rootful mode. Fallback: Disabling option --home" check_fallback Sharehome="no" } ;; esac } case "$Sharehome" in yes|host) [ -z "$Persistanthomevolume" ] && Persistanthomevolume="$Hosthomebasefolder/$Imagebasename" Persistanthomevolume="${Persistanthomevolume//"~"/"$Hostuserhome"}" [ "${Persistanthomevolume:0:1}" = "/" ] && Sharehome="host" || Sharehome="volume" ;; esac case "$Sharehome" in host) case "$Createcontaineruser" in no) note "Option --home or --home=DIR is not supported with option --user=RETAIN. Alternatively, specify a docker volume with --home=VOLUME. Also you can use option --share to share host directories. Fallback: Disabling option --home." check_fallback Sharehome="no" ;; yes) grep -q "unknown" <<< "$Containeruser" && { note "Option --home: Sharing a host folder is allowed only for container users that also exist on host. You can use a docker volume with --home=VOLUME instead. Fallback: Disabling option --home." check_fallback Sharehome="no" } ;; esac ;; esac case "$Sharehome" in host) Containeruserhomebasefolder="/home" [ "$Containeruser" = "root" ] && Containeruserhomebasefolder="/" # A change can break existing configs, e.g. playonlinux # Containeruserhomebasefolder="/home.x11docker" [ "$Persistanthomevolume" = "$Containeruserhosthome" ] && { # --home=$HOME must be same as on host #243 Containeruserhomebasefolder="$(dirname "$Containeruserhosthome")" Containeruserhome="$Containeruserhosthome" } ;; no) # Containeruserhomebasefolder="/home.tmp" Containeruserhomebasefolder="/home" [ "$Containeruser" = "root" ] && Containeruserhomebasefolder="/" ;; volume) Containeruserhomebasefolder="/home.volume/$Persistanthomevolume" grep -q "/" <<< "$Persistanthomevolume" && error "Option --home: Invalid argument: '$Persistanthomevolume' Please either specify an absolute path beginning with '/' or specify a docker volume without any '/'." ;; esac [ "$Createcontaineruser" = "yes" ] && Containeruserhome="${Containeruserhome:-$Containeruserhomebasefolder/$Containeruser}" [ "$Sharehome" != "no" ] && store_runoption env "HOME=$Containeruserhome" # case "$Createcontaineruser" in # no) store_runoption env "HOME=/tmp" ;; # esac case "$Sharehome" in host) # if no home folder on host is specified (--home=DIR), create a standard one in ~/.local/share/x11docker [ -d "$Persistanthomevolume" ] || { #[ "$Startuser" = "root" ] && su "$Containeruser" -c "mkdir -p '$Persistanthomevolume'" [ "$Containeruser" = "$Hostuser" ] && mkfolder "$Persistanthomevolume" && { # create symbolic link to ~/x11docker echo "$Persistanthomevolume" | grep -q .local/share/x11docker && [ ! -e "$Hostuserhome/x11docker" ] && unpriv "ln -s '$Hosthomebasefolder' '$Hostuserhome/x11docker'" ||: } } [ -d "$Persistanthomevolume" ] || error "Option --home: Could not create persistent home folder for user '$Containeruser' on host. Can e.g. happen with option --user. Four possibilities to solve issue: 1.) Run x11docker one time as user '$Containeruser'. 2.) Run x11docker one time as user 'root'. 3.) Use option --home=DIR with DIR pointing to a writeable folder. 4.) Use option --home=VOLUME to use a docker volume." writeaccess "$Containeruseruid" "$Persistanthomevolume" || warning "User '$Containeruser' might have no write access to $Persistanthomevolume." verbose "Sharing directory $Persistanthomevolume with container as its home directory $Containeruserhome" ;; volume) debugnote "Option --home: Using docker volume $Persistanthomevolume" ;; esac return 0 } check_containeruser() { # check container user (also option --user) ## check container user [ "$Containeruser" = "RETAIN" ] && { Createcontaineruser="no" Containeruser="" return 0 } [ -z "$Containeruser" ] && Containeruser="$Hostuser" # default: containeruser = hostuser. can be changed with --user [ -n "$Containeruser" ] && echo "$Containeruser" | grep -q ':' && { # option --user can specify a group/gid after : Containerusergid="$(echo "$Containeruser" | cut -d: -f2)" Containeruser="$(echo "$Containeruser" | cut -d: -f1)" } [ "$Containeruser" = "root" ] && Containeruser="0" [ -n "$(mygetent passwd "$Containeruser")" ] && { # user exists on host Containeruser=$(mygetent passwd "$Containeruser" | cut -d: -f1) # can be name or uid -> now name Containeruseruid=$(mygetent passwd "$Containeruser" | cut -d: -f3) [ -z "$Containerusergid" ] && Containerusergid="$(mygetent passwd "$Containeruser" | cut -d: -f4)" [ "$Containeruser" = "$Hostuser" ] && Containeruserhosthome="$Hostuserhome" [ -z "$Containeruserhosthome" ] && Containeruserhosthome="$(mygetent passwd "$Containeruser" | cut -d: -f6)" : } || { # user does not exist on host [[ "$Containeruser" =~ ^[0-9]+$ ]] || error "Option --user: Unknown host user or invalid user number '$Containeruser'. Non-host users can be specified with an UID only, not with a name." Containeruseruid="$Containeruser" Containeruser="unknown$Containeruseruid" [ -z "$Containerusergid" ] && Containerusergid=100 Containeruserhosthome="" } Containerusergroup="$(mygetent group "$Containerusergid" | cut -d: -f1 || echo group_"$Containeruser")" [ "$Containeruseruid" = "0" ] && { Containeruser="root" Containerusergid="0" Containerusergroup="root" Containeruserhosthome="/root" [ "$Sudouser" = "yes" ] || [ "$Capdropall" = "no" ] || { note "Option --user=root: Please set option --sudouser or --cap-default if you want root privileges for container user root." } } [ -f "$Passwordfile" ] && { verbose "Found password file $Passwordfile" Containeruserpassword="$(cat "$Passwordfile")" case "$(stat -c '%a' "$Passwordfile")" in 600|400) ;; *) warning "File $Passwordfile should be readable by current user only. Please set access permissions to 600 or 400." ;; esac } [ -z "$Containeruserpassword" ] && Containeruserpassword='$6$Mj$WtcqpZ7JFegW4.0Br1WM0NJcNSxVxUQXgVLxGEV5uD3ib3jWGvIM3FNg2Gcj8e//mI06yhgfZ79WoNHGVtaYw1' # password: x11docker storeinfo containeruser="$Containeruser" store_runoption env "USER=$Containeruser" debugnote "container user: $Containeruser $Containeruseruid:$Containerusergid $Containeruserhosthome" Containerusergroups="$Containerusergroups $Containerusergroup" case "$Backend" in docker|podman|nerdctl|proot) case "$Containersetup" in no) store_runoption env "XDG_RUNTIME_DIR=/tmp" ;; esac ;; host) ;; esac return 0 } create_backendcommand() { ### create command to run docker|podman|nerdctl ### local Line= Memory Initcommand= Grouplist= case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendbin run" unpriv_backend "$Backendbin run --help" 2>/dev/null | grep -q -- '--pull' && Backendcommand="$Backendcommand \\ --pull never" ;; proot) Backendcommand="proot \\ --rootfs='$Rootfs' \\ --bind=/etc/host.conf \\ --bind=/etc/nsswitch.conf \\ --bind=/etc/resolv.conf \\ --bind=/proc \\ --bind=/sys \\ --bind=/dev \\ --bind=/run/shm" ;; esac # --keepcache case "$Backend" in docker|podman) [ "$Preservecachefiles" = "no" ] && Backendcommand="$Backendcommand \\ --rm" ;; esac # --sudouser [ "$Sudouser" = "yes" ] && { case "$Backend" in proot) Backendcommand="$Backendcommand \\ --root-id" ;; esac } # --interactive case "$Interactive" in yes) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --interactive \\ --tty" ;; esac ;; no) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --detach" ;; esac case "$Backend" in docker|podman) Backendcommand="$Backendcommand \\ --tty" ;; esac ;; esac # --name [ -z "$Containername" ] && Containername="x11docker_X${Newdisplaynumber}_${Codename}_${Cachenumber}" case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --name $Containername" storeinfo "containername=$Containername" ;; esac # --limit [ "$Limitresources" ] && { case "$Backend" in docker|podman|nerdctl) Memory="$(awk "BEGIN {print int($(LC_ALL=C free -b | grep "Mem:" | awk ' {print $4 + $6}') * $Limitresources)}")" Backendcommand="$Backendcommand \\ --cpus=$(awk "BEGIN {print $(nproc) * $Limitresources}") \\ --memory=$Memory \\ --kernel-memory=$Memory" ;; esac } # --user case "$Initsystem" in none|tini|dockerinit) case "$Switchcontaineruser" in no) case "$Backend" in docker|podman|nerdctl) [ "$Createcontaineruser" = "yes" ] && Backendcommand="$Backendcommand \\ --user $Containeruseruid:$Containerusergid" ;; esac ;; yes) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --user 0:0" ;; esac ;; esac ;; systemd|runit|openrc|sysvinit|s6-overlay) # init systems switch later from root to user. case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --user root" ;; esac ;; esac # userns [ "$Createcontaineruser" = "yes" ] && { # Disable user namespacing to avoid file permission issues with --home or --share. Files need same UID/GID. case "$Backend" in podman) [ "$Backendrootless" = "yes" ] && Backendcommand="$Backendcommand \\ --userns=keep-id" ;; docker) case "$Runtime" in sysbox-runc) ;; *) [ "$Backendrootless" = "yes" ] || Backendcommand="$Backendcommand \\ --userns=host" ### FIXME option deprecated? ;; esac ;; esac } # --group-add [ "$Switchcontaineruser" = "no" ] && { case "$Backend" in docker|podman) for Line in $Containerusergroups; do mygetent group "${Line:-nonsense}" >/dev/null && Backendcommand="$Backendcommand \\ --group-add $(mygetent group "$Line" | cut -d: -f3)" done ;; esac } # --runtime [ "$Runtime" ] && { case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --runtime='$Runtime'" ;; esac } # --ipc [ "$Shareipc" != "no" ] && { case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --ipc $Shareipc" ;; esac } # --network case "$Backend" in docker|podman|nerdctl) case "$Xcnetworkid" in "") [ -n "$Network" ] && Backendcommand="$Backendcommand \\ --network $Network" ;; *) Backendcommand="$Backendcommand \\ --network=$Xcnetworkname" ;; esac ;; esac # capabilities case "$Backend" in docker|podman|nerdctl) [ "$Capdropall" = "yes" ] && Backendcommand="$Backendcommand \\ --cap-drop ALL" while read Line ; do Backendcommand="$Backendcommand \\ --cap-add $Line" done < <(store_runoption dump cap) ;; esac # --newprivileges case "$Backend" in docker|podman|nerdctl) [ "$Allownewprivileges" = "no" ] && Backendcommand="$Backendcommand \\ --security-opt no-new-privileges" ;; esac # SELinux restrictions for containers must be disabled to allow access to X socket. Flags z or Z do not help. ### FIXME check for possible change meanwhile case "$Backend" in docker|podman) Backendcommand="$Backendcommand \\ --security-opt label=type:container_runtime_t" ;; esac # --init case "$Initsystem" in dockerinit) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --init" ;; esac ;; tini) Initcommand="$Initcontainerpath -g --" case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Initbinaryfile:ro" "$Initcontainerpath")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Initbinaryfile:$Initcontainerpath" ;; esac ;; systemd) Backendcommand="$Backendcommand \\ --tmpfs /var/lib/journal" esac case "$Initsystem" in systemd|sysvinit|openrc|runit) case "$Backend" in podman) Backendcommand="$Backendcommand \\ --systemd=always" ;; esac [ "$Remountcgroup" = "yes" ] && { unpriv_backend "$Backendbin run --help" | grep -q -- '--cgroupns' && Backendcommand="$Backendcommand \\ --cgroupns private" } ;; esac # stop signal for some init systems [ "$Stopsignal" ] && { case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --stop-signal $Stopsignal" ;; esac } # --sharecgroup [ "$Sharecgroup" = "yes" ] && { case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount type=bind,source=/sys/fs/cgroup,target=/sys/fs/cgroup,readonly" ;; esac } # Needed especially for --init=systemd and --dbus=system case "$Backend" in docker|podman) Backendcommand="$Backendcommand \\ --tmpfs /run:exec \\ --tmpfs /run/lock \\ --tmpfs /tmp" ;; esac # shared x11docker cache folder case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Sharefolder:rw" $Sharefoldercontainer)" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Sharefolder:$Sharefoldercontainer" ;; esac # --home case "$Sharehome" in host) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Persistanthomevolume:rw" "$Containeruserhome")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Persistanthomevolume:$Containeruserhome" ;; esac ;; volume) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --volume $(convertpath volume "$Persistanthomevolume" "$Containeruserhomebasefolder")" ### FIXME $Containeruserhomebasefolder ?? mount/volume? ;; *) error "Option --backend=$Backend: Sharing a docker volume is not supported. Failing option: --home=$Persistanthomevolume" ;; esac ;; esac # --share while read -r Line; do case "$Backend" in docker|podman|nerdctl) case "$(cut -c1-5 <<< "$Line")" in "/dev/") Backendcommand="$Backendcommand \\ --device $(convertpath volume "$Line")" warning "Sharing device file: $Line" ;; *) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Line")" ;; esac ;; proot) grep -q ':ro' <<< "$Line" && { warning "Option --backend=proot cannot mount read-only: $Line Fallback: mounting with r/w" check_fallback } Line="${Line//":ro"/""}" Backendcommand="$Backendcommand \\ --bind=$Line:$Line" ;; esac done < <(store_runoption dump volume) # --gpu: share NVIDIA driver installer [ -f "$Nvidiainstallerfile" ] && { case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Nvidiainstallerfile:ro" "$Nvidiacontainerfile")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Nvidiainstallerfile:$Nvidiacontainerfile" ;; esac } # --gpu=virgl [ "$Sharegpu" = "virgl" ] && Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Cachefolder/tmp/.virgl_test" "/tmp/.virgl_test")" # share X socket [ "$Xoverip" = "no" ] && case "$Xcontainer" in yes) [ "$Newxsocket" ] && { case "$Backend" in docker|podman|nerdctl) case "$Xserver" in --hostdisplay) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Newxsocket:ro" "$Newxsocket")" ;; *) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Cachefolder/tmp/.X11-unix/X$Newdisplaynumber:ro" "$Newxsocket")" ;; esac [ "$Shareipc" = "no" ] && [ "$Containeruser" = "$Hostuser" ] && [ "$Xcontainerbackend" = "$Backend" ] && { case "$Runtime" in kata-runtime|sysbox-runc) ;; *) case "$Backend" in docker) [ "$Xcrootless" = "$Backendrootless" ] && Backendcommand="$Backendcommand \\ --ipc=container:$Xcontainername" ;; podman) case "$Runtime" in crun) [ "$Xcrootless" = "$Backendrootless" ] && Backendcommand="$Backendcommand \\ --ipc=container:$Xcontainername" ;; *) debugnote "--xc --backend=podman: Not sharing ipc namespace using runtime $Runtime" ;; esac ;; esac ;; esac } ;; proot) case "$Xserver" in --hostdisplay) Backendcommand="$Backendcommand \\ --bind=$Newxsocket:$Newxsocket" ;; *) Backendcommand="$Backendcommand \\ --bind=$Cachefolder/tmp/.X11-unix/X$Newdisplaynumber:$Newxsocket" ;; esac ;; esac } ;; no) [ "$Newxsocket" ] && { case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Newxsocket:ro")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Newxsocket:$Newxsocket" ;; esac } ;; esac # Wayland socket will be softlinked to XDG_RUNTIME_DIR in containerrc [ "$Setupwayland" = "yes" ] && { case "$Containersetup" in yes) case "$Xcontainer" in yes) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Cachefolder/XDG_RUNTIME_DIR/$Newwaylandsocket" "/$Newwaylandsocket")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Cachefolder/XDG_RUNTIME_DIR/$Newwaylandsocket:/$Newwaylandsocket" ;; esac ;; no) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$XDG_RUNTIME_DIR/$Newwaylandsocket" "/$Newwaylandsocket")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$XDG_RUNTIME_DIR/$Newwaylandsocket:/$Newwaylandsocket" ;; esac ;; esac ;; no) case "$Xcontainer" in yes) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Cachefolder/XDG_RUNTIME_DIR/$Newwaylandsocket")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Cachefolder/XDG_RUNTIME_DIR/$Newwaylandsocket" ### FIXME looks wrong ;; esac ;; no) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$XDG_RUNTIME_DIR/$Newwaylandsocket")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$XDG_RUNTIME_DIR/$Newwaylandsocket" ;; esac ;; esac ;; esac } # --pulseaudio case "$Pulseaudiomode" in socket) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Pulseaudioconf:ro" "/etc/pulse/client.conf")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Pulseaudioconf:/etc/pulse/client.conf" ;; esac ;; host) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --mount $(convertpath mount "$Pulseaudiosocket:ro" "/tmp/pulseaudio.socket.host") \\ --mount $(convertpath mount "$Pulseaudioconf:ro" "/etc/pulse/client.conf")" ;; proot) Backendcommand="$Backendcommand \\ --bind=$Pulseaudiosocket:/tmp/pulseaudio.socket.host \\ --bind=$Pulseaudioconf:/etc/pulse/client.conf" ;; esac ;; esac # --workdir case "$Containersetup" in yes) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --workdir '${Workdir:-/tmp}'" ;; proot) Backendcommand="$Backendcommand \\ --cwd=${Workdir:-/tmp}" ;; esac ;; no) [ "$Workdir" ] && { case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --workdir '$Workdir'" ;; proot) Backendcommand="$Backendcommand \\ --cwd=${Workdir}" ;; esac } ;; esac # --no-setup, --no-entrypoint case "$Containersetup" in yes) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --entrypoint env" ;; esac ;; esac # --env: add environment variables. Only needed here for possible 'docker exec'. Also set in containerrc while read Line; do case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --env '$(escapestring "$Line")'" ;; esac done < <(store_runoption dump env) # add custom docker arguments, imagename and imagecommand [ "$Customdockeroptions" ] && { case "$Backend" in docker|podman|nerdctl|proot) Backendcommand="$Backendcommand \\ $Customdockeroptions" ;; esac } # --rootfs case "$Backend" in podman) [ "$Podmanrootfs" = "yes" ] && Backendcommand="$Backendcommand \\ --rootfs" ;; esac case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand \\ --" ;; proot) Backendcommand="$Backendcommand \\ " ;; esac case "$Containersetup" in yes) case "$Switchcontaineruser" in no) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand ${Rootfs:-$Imagename} $Initcommand /bin/sh - $(convertpath share "$Containerrc")" # start_container runs containerrootrc with 'docker exec' ;; proot) Backendcommand="$Backendcommand /bin/sh - $(convertpath share "$Containerrc")" ;; esac ;; yes) case "$Backend" in docker|podman|nerdctl) Backendcommand="$Backendcommand ${Rootfs:-$Imagename} $Initcommand /bin/sh - $(convertpath share "$Containerrootrc")" # containerrootrc runs containerrc ;; proot) Backendcommand="$Backendcommand /bin/sh - $(convertpath share "$Containerrootrc")" ;; esac ;; esac ;; no) case "$Backend" in docker|podman|nerdctl) case "$Initsystem" in tini) Backendcommand="$Backendcommand ${Rootfs:-$Imagename} $Initcommand $Containercommand" ;; *) Backendcommand="$Backendcommand ${Rootfs:-$Imagename} $Containercommand" ;; esac ;; proot) Backendcommand="$Backendcommand $Containercommand" ;; esac ;; esac return 0 } setup_capabilities() { # check linux capabilities needed by container # compare: man capabilities [ "$Sudouser" ] && Adminusercaps="yes" [ "$Capdropall" = "no" ] && [ "$Allownewprivileges" = "auto" ] && { note "Option --cap-default: Enabling option --newprivileges=yes. You can avoid this with --newprivileges=no" Allownewprivileges="yes" } # --sudouser [ "$Sudouser" ] && warning "Option --sudouser severely 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. Default password: x11docker" # enable dbus [ "$Dbussystem" = "yes" ] && { Dbusrunsession="yes" store_runoption cap "CHOWN FOWNER" ### FIXME: CHOWN needed indeed here? Switchcontaineruser="yes" } #case "$Initsystem" in # systemd) Dbusrunsession="no" ;; #esac case "$Initsystem" in none|tini|dockerinit) ;; systemd) Switchcontaineruser="yes" store_runoption cap "FSETID FOWNER SETPCAP SYS_BOOT" ;; runit|openrc|sysvinit) Switchcontaineruser="yes" store_runoption cap "SYS_BOOT KILL" ;; s6-overlay) Switchcontaineruser="yes" store_runoption cap "CHOWN KILL" ;; esac [ "$Sharecgroup" = "yes" ] && Switchcontaineruser="yes" # needed for elogind [ "$Switchcontaineruser" = "yes" ] && Switchcontainerusercaps="yes" [ "$Adminusercaps" = "yes" ] && { Switchcontainerusercaps="yes" store_runoption cap "CHOWN KILL FSETID FOWNER SETPCAP" [ "$Allownewprivileges" = "auto" ] && { note "Option --sudouser: Enabling option --newprivileges=yes. You can avoid this with --newprivileges=no" Allownewprivileges="yes" } } [ "$Switchcontainerusercaps" = "yes" ] && { warning "setup_capabilities(): Adding capabilities for user switching from root to unprivileged user" store_runoption cap "SETUID SETGID DAC_OVERRIDE AUDIT_WRITE" } # Automated NVIDIA driver installation [ "$Sharegpu" = "direct" ] && [ "$Nvidiainstallerfile" ] && [ "$Switchcontaineruser" = "yes" ] && store_runoption cap "CHOWN FOWNER" [ "$Allownewprivileges" = "auto" ] && Allownewprivileges="no" [ "$Allownewprivileges" = "yes" ] && warning "Option --newprivileges=yes: x11docker does not set docker run option --security-opt=no-new-privileges. That degrades container security. However, this is still within a default docker setup." # Issues with hidepid=2 seen on NixOS (issue #83) { [ "$Switchcontaineruser" = "yes" ] || [ "$Containeruser" != "$Hostuser" ] ; } && { [ "$Hostcanwatchroot" = "no" ] && { [ "$Hosthidepid" = "yes" ] && Message="/proc is mounted with hidepid=2." || Message="Cannot watch processes of other users for unknown reasons." Message="$Message x11docker cannot watch processes of root or other users different from $Hostuser." [ "$Hostuser" != "$Containeruser" ] && Message="$Message Container user $Containeruser is different from host user $Hostuser." [ "$Switchcontaineruser" = "yes" ] && Message="$Message Container PID 1 will run as root." Message="$Message Therefore x11docker cannot watch container processes for a clean termination of X and x11docker itself. Four possible solutions: 1. Run x11docker as root. 2. Don't use options like --user or --init=systemd that change container user. 3. Add user $Hostuser to group 'proc'. 4. Change /proc mount option hidepid=2 to hidepid=1." error "$Message" } } return 0 } setup_initsystem() { # option init: set up capabilities, check or create files # some init system setup also in containerrootrc local Message= # --init in Mobyvm. /usr/bin/docker-init is not available in MSYS2/Cygwin/WSL1 case "$Mobyvm" in yes) [ "$Initsystem" = "tini" ] && Initsystem="dockerinit" ;; esac case "$Backend" in proot|host) case "$Initsystem" in tini|none) ;; *) note "Option --init: Only --init[=tini] or --init=none are supported with option --backend=$Backend. Fallback: Setting option --init=tini" check_fallback Initsystem="tini" ;; esac ;; esac case "$Backend" in docker|podman|nerdctl) store_runoption env "container=$Backend" # At least OpenRC and systemd regard this hint ;; esac case "$Initsystem" in none|dockerinit) ;; tini) check_smallinit && { case "$Runtime" in kata-runtime) # avoid sharing same file that might be shared with runc already. cp -u "$Initbinaryfile" "$Localsharedir/init-kata" Initbinaryfile="$Localsharedir/init-kata" ;; esac } || { Initsystem="none" } [ "$Initsystem" = "none" ] && { ### FIXME --backend=host,proot note "--init=$Initsystem: Did not find container init system 'tini' or 'catatonit'. It should be provided by docker or podman package. Please install catatonit to provide a container init system. $Wikipackagesimage" } verbose "--init: Found init binary: ${Initbinaryfile:-(none)}" [ "$Initbinaryfile" ] && storeinfo "tini=$Initbinaryfile" ;; systemd) Stopsignal="SIGRTMIN+3" Containerusergroups="$Containerusergroups systemd-journal" ;; runit) Stopsignal="HUP" store_runoption env "VIRTUALIZATION=docker" ;; openrc) ;; sysvinit) Stopsignal="INT" ;; s6-overlay) ;; esac case "$Initsystem" in systemd) warning "Option --init=systemd slightly degrades container isolation. It adds some capabilities x11docker would drop otherwise. However, they are still within default docker capabilities. Not within default docker capabilities it adds capability SYS_BOOT. Some processes in container will run as root." ;; runit|openrc|sysvinit) warning "Option --init=$Initsystem slightly degrades container isolation. It adds some user switching capabilities x11docker would drop otherwise. However, they are still within default docker capabilities. Not within default docker capabilities it adds capability SYS_BOOT. Some processes in container will run as root." ;; s6-overlay) warning "Option --init=$Initsystem slightly degrades container isolation. It adds some user switching capabilities x11docker would drop otherwise. However, they are still within default docker capabilities. Some processes in container will run as root." ;; tini|none|dockerinit) [ "$Dbussystem" = "yes" ] && { [ "$Capdropall" = "yes" ] && warning "Option --dbus=system slightly degrades container isolation. It adds some user switching capabilities x11docker would drop otherwise. However, they are still within default docker capabilities. Some processes in container will run as root. --dbus=system might need further capabilities or --cap-default to work as expected. If in doubt, one of --init=systemd|openrc|runit|sysvinit might be a better choice." note "Option --dbus=system with init system '$Initsystem' can have a quite long timeout delay until startup. Use one of --init=systemd|openrc|runit|sysvinit in that case." } ;; esac case "$Backend" in host) [ "$Initsystem" = "tini" ] && { Containerentrypoint="$Initbinaryfile --" store_runoption env "TINI_SUBREAPER=1" } ;; esac case "$Initsystem" in systemd|sysvinit|openrc|runit) check_cgroup ||: ;; esac return 0 } store_runoption() { # store env, cap or volume/device for docker command # $1 env store environment variable $2 # volume store volume or device path $2 # cap store capability $2 # dump dump all entries of $2 local Count Line Path Readwritemode case "${1:-}" in env) Containerenvironmentcount="$((Containerenvironmentcount + 1))" Containerenvironment[$Containerenvironmentcount]="${2:-}" ;; volume) Path="$(convertpath subsystem "${2:-}")" Readwritemode="$(echo "${2:-}" | rev | cut -c1-3 | rev)" [ "$Readwritemode" = ":ro" ] || Readwritemode="" Path="$(convertpath subsystem "${2:-}")" Readwritemode="$(echo "${2:-}" | rev | cut -c1-3 | rev)" [ "$Readwritemode" = ":ro" ] || Readwritemode="" case "${Path:0:1}" in "/") # path on host Sharevolumescount="$((Sharevolumescount + 1))" Sharevolumes[$Sharevolumescount]="${2:-}" [ -h "$Path" ] && myrealpath "$Path" >/dev/null && { note "Option --share: Shared file is a symbolic link. Sharing target, too. Symlink: $Path Target: $(myrealpath "$Path")" store_runoption volume "$(myrealpath "$Path")$Readwritemode" } ;; *) # Docker volume Sharevolumescount="$((Sharevolumescount + 1))" Sharevolumes[$Sharevolumescount]="${2:-}" ;; esac ;; cap) for Line in ${2:-} ; do Capabilities="$Capabilities $Line" done ;; dump) case "${2:-}" in env) for ((Count=$Containerenvironmentcount ; Count>=1 ; Count --)) ; do echo "${Containerenvironment[$Count]}" ; done ;; volume) for ((Count=1 ; Count<=$Sharevolumescount ; Count ++)) ; do echo "${Sharevolumes[$Count]}" ; done ;; cap) while read Line; do [ "$Line" ] && case "$Capdropall" in yes) echo "$Line" ;; no) grep -w -q "$Line" <<< "SETPCAP MKNOD AUDIT_WRITE CHOWN NET_RAW DAC_OVERRIDE FOWNER FSETID KILL SETGID SETUID NET_BIND_SERVICE SYS_CHROOT SETFCAP" || echo "$Line" ;; esac done < <(echo "$Capabilities" | sort -u) ;; esac ;; esac return 0 } #### scripts running in container create_containerrootrc() { # This script runs as root in container echo "#! /bin/sh # containerrootrc # This Script is executed as root in container. # - Create container user # - Set time zone # - Create locale # - Install NVIDIA driver if requested # - Set up init system services and DBus for --init=systemd|openrc|runit|sysvinit # redirect output to have it available before 'docker logs' starts. --init=runit (void) would eat up the output at all for unknown reasons. exec 5>&1 6>&2 exec 1>>$(convertpath share "$Containerlogfile") 2>&1 " declare -f storeinfo declare -f rocknroll echo "$Messagefifofuncs" echo " Messagefile=$(convertpath share "$Messagefifo") Storeinfofile='$(convertpath share "$Storeinfofile")' Timetosaygoodbyefile=$(convertpath share "$Timetosaygoodbyefile") Containeruser=\"\$(storeinfo dump containeruser)\" Containeruserhome='$Containeruserhome' debugnote 'Running containerrootrc: Setup as root in container' Error='' for Line in cat chmod chown cut cd cp date echo env export grep id ln ls mkdir mv printf rm sed sh sleep tail touch; do command -v \$Line || { warning \"ERROR: Command not found in image: \$Line\" Error=1 } done [ \"\$Error\" ] && error 'Commands for container setup missing in image. You can try with option --no-setup to avoid this error.' # /etc/profile.d " case "$Initsystem" in systemd|openrc|sysvinit|runit) echo " install -m 666 /dev/null /etc/profile.d/90-x11docker-containerrc.sh echo ' echo > /etc/profile.d/90-x11docker-containerrc.sh exec /bin/sh $(convertpath share "$Containerrc") ' >> /etc/profile.d/90-x11docker-containerrc.sh " ;; esac while read Line; do echo "echo 'export $Line' >> /etc/profile.d/10-x11docker-env.sh" done <<< $(store_runoption dump env) echo " # Container system Containersystem=\"\$(grep '^ID=' /etc/os-release 2>/dev/null | cut -d= -f2 || echo 'unknown')\" verbose \"Container system ID: \$Containersystem\" # Check type of libc ldd --version 2>&1 | grep -q 'musl libc' && Containerlibc='musl' ldd --version 2>&1 | grep -q -E 'GLIBC|GNU libc' && Containerlibc='glibc' debugnote \"containerrootrc: Container libc: \$Containerlibc\" # Create some system dirs with needed permissions mkdir -v -p /var/lib/dbus /var/run/dbus mkdir -v -p -m 1777 /tmp/.ICE-unix /tmp/.X11-unix /tmp/.font-unix chmod -c 1777 /tmp/.ICE-unix /tmp/.X11-unix /tmp/.font-unix " [ "$Screensize" ] && rootrc_xrandr [ "$Hostlocaltimefile" ] && rootrc_timezone [ "$Langwunsch" ] && rootrc_create_locale [ "$Nvidiainstallerfile" ] && rootrc_nvidia_installer echo " rocknroll || exit 64 " [ "$Createcontaineruser" = "yes" ] && rootrc_setup_user echo " debugnote \"containerrootrc: Container user: \$(id \$Containeruser) \$(cat /etc/passwd | grep '^\$Containeruser:')\" # Create HOME Containeruserhome=\"\$(cat /etc/passwd | grep \"\$Containeruser:\" | cut -d: -f6)\" Containeruserhome=\"\${Containeruserhome:-/tmp/\$Containeruser}\" [ -e \"\$Containeruserhome\" ] || { mkdir -v -p -m 777 \"\$Containeruserhome\" chown -v \"\$Containeruser\":\"\$Containerusergroup\" \"\$Containeruserhome\" && chmod -v 755 \"\$Containeruserhome\" # can fail depending on capabilities } ls -la \"\$Containeruserhome\" export HOME=\"\$Containeruserhome\" " [ "$Switchcontaineruser" = "yes" ] && rootrc_create_helperscripts [ "$Dbussystem" = "yes" ] && rootrc_prepare_dbus case "$Initsystem" in openrc) rootrc_prepare_init_openrc ;; runit) rootrc_prepare_init_runit ;; systemd) rootrc_prepare_init_systemd ;; sysvinit) rootrc_prepare_init_sysvinit ;; esac echo " # disable getty in inittab [ -e /etc/inittab ] && sed -i 's/.*getty/##getty disabled by x11docker## \0/' /etc/inittab " [ "$Dbussystem" = "yes" ] && { echo " command -v dbus-daemon || note 'DBus not found. Cannot run DBus system daemon. Please install dbus in image.' " case "$Initsystem" in tini|none|dockerinit) echo " dbus-daemon --system --fork " ;; esac } [ "$Remountcgroup" = "yes" ] && echo " # wait for nsenter to remount /sys/fs/cgroup to :rw for i in 1 2 3 4 5 6 7 8 9 10; do test -e /nsenter_is_ready && break sleep 1 debugnote 'containerrootrc: Waiting for nsenter/mount to be ready' done rm /nsenter_is_ready " [ "$Runasroot" ] && { echo " # Custom setup root command added with option --runasroot $Runasroot " } echo " rocknroll || exit 64 storeinfo containerrootrc=ready # signal for containerrc #exec 1>&5 2>&6 " [ "$Interactive" = "yes" ] && echo " exec 1>&5 2>&6 " [ "$Switchcontaineruser" = "yes" ] && { # if "no", containerrc is executed in command line $Backendcommand, and containerrootrc is started with 'docker exec' echo "debugnote 'containerrootrc(): --init=$Initsystem'" case "$Initsystem" in none|dockerinit) case "$Backend" in proot) echo "exec /bin/sh $(convertpath share "$Containerrc")" ;; docker|podman|nerdctl) echo "exec /usr/local/bin/x11docker-agetty" ;; esac ;; tini) case "$Backend" in proot) echo "exec env TINI_SUBREAPER=1 '$Initcontainerpath' -- /bin/sh $(convertpath share "$Containerrc")" ;; docker|podman|nerdctl) echo "exec '$Initcontainerpath' -- /usr/local/bin/x11docker-agetty" ;; esac ;; sysvinit) echo "/usr/local/bin/x11docker-watch &" echo "exec /sbin/init" ;; openrc) echo "/usr/local/bin/x11docker-watch &" echo "command -v openrc-init && exec openrc-init || exec /sbin/init" ;; runit) echo "/usr/local/bin/x11docker-watch &" echo "[ -e /sbin/runit-init ] && exec runit-init || exec /sbin/init" ;; s6-overlay) echo "exec /init /usr/local/bin/x11docker-agetty" ;; systemd) echo 'Systemd=/lib/systemd/systemd' echo '[ -x "$Systemd" ] || Systemd=/bin/systemd' echo '[ -x "$Systemd" ] || Systemd=/sbin/systemd' echo '[ -x "$Systemd" ] || Systemd=/sbin/init' echo 'command -v systemctl >/dev/null || {' echo ' error "Option --init=systemd: systemd not found."' echo ' exit 64' echo '}' echo 'export SYSTEMD_LOG_LEVEL=info' echo 'export SYSTEMD_LOG_TARGET=console' echo 'exec $Systemd --show-status=yes' ;; esac } return 0 } create_containerrc() { # This script runs as unprivileged user in container local Ungrep= Path= Line= { echo "#! /bin/sh # containerrc # Created startscript for docker run used as container command. # Runs as unprivileged user in container. [ '$Interactive' = 'no' ] && exec >> $(convertpath share "$Containerlogfile") 2>&1 $(declare -f mysleep) $(declare -f pspid) $(declare -f rocknroll) $(declare -f saygoodbye) $(declare -f storeinfo) $(declare -f storepid) $(declare -f waitforlogentry) $Messagefifofuncs Messagefile=$(convertpath share "$Messagefifo") Storeinfofile=$(convertpath share "$Storeinfofile") Storepidfile=$Sharefoldercontainer/store.pids Timetosaygoodbyefile=$(convertpath share "$Timetosaygoodbyefile") waitforlogentry containerrc \$Storeinfofile containerrootrc=ready '' infinity debugnote 'Running containerrc: Unprivileged user commands in container' verbose \"containerrc: Container system: \$(cat /etc/os-release 2>&1 ||:)\" " case "$Backend" in proot) # set another hostname ### FIXME fails sed "s/$(hostname)/$Backend-$Imagebasename/g" "/etc/hosts" > "$Rootfs/etc/hosts" echo "$Backend-$Imagebasename" > "$Rootfs/etc/hostname" # clean environment variables while read -r Line; do Line="$(cut -d= -f1 <<< "$Line")" [ -n "$Line" ] && { echo "unset $Line" echo "export $Line" } done < <(unset -f rmcr ; env) echo "export PATH='/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin:/usr/sbin'" echo 'export PS1="\u@'"$Backend"'-'"$Imagebasename"':\w\$ "' ### FIXME fails ;; docker|podman|nerdctl) [ "$Switchcontaineruser" = "yes" ] && { echo '# Environment variables found in image:' IFS=$'\n' while read -r Line; do echo "export $(escapestring "$Line")" done < <(unpriv_backend "$Backendbin run --rm --entrypoint env $Imagename env" 2>> "$Containerlogfile" | rmcr | grep -v 'HOSTNAME=' ||: ) IFS=$' \t\n' } ;; esac [ "$Initsystem" = 'systemd' ] && echo "systemctl --user start dbus" ### FIXME echo " # USER and HOME Containeruser='$(storeinfo dump containeruser)' export USER=\"\$Containeruser\" " case "$Createcontaineruser" in yes) echo "Containeruserhome='$Containeruserhome'" ;; no) case "$Sharehome" in no) echo "Containeruserhome=\"\$(cat /etc/passwd | grep \"\$Containeruser:.:\" | cut -d: -f6)\"" echo "Containeruserhome=\"\${Containeruserhome:-/tmp/\$Containeruser}\"" echo "mkdir -p \"\$Containeruserhome\"" ;; volume) echo "Containeruserhome='$Containeruserhome'" ;; esac ;; esac echo " [ \"\$Containeruserhome\" ] && export HOME=\"\$Containeruserhome\" # XDG_RUNTIME_DIR Containeruseruid=\$(id -u \$Containeruser) export XDG_RUNTIME_DIR=/tmp/XDG_RUNTIME_DIR [ -e /run/user/\$Containeruseruid ] && { ln -s /run/user/\$Containeruseruid \$XDG_RUNTIME_DIR export XDG_RUNTIME_DIR=/run/user/\$Containeruseruid } || { mkdir -p -m700 \$XDG_RUNTIME_DIR } " # softlinks from shared folders to HOME [ "$Persistanthomevolume" != "$Containeruserhosthome" ] && { # not for --home=$HOME while read -r Line; do Path="$(convertpath container "$Line")" [ "$(cut -c1-5 <<< "$Line")" != "/dev/" ] && { [ "$Line" != "$Path" ] && { # different for paths in HOME without --home case "$Line" in "$Containeruserhosthome") # --share=$HOME echo "ln -s '$Path' -T '$Containeruserhome/home.host.$Containeruser'" Ungrep="$Ungrep|home.host.$Containeruser" ;; *) echo "mkdir -p $(dirname "$Line")" echo "ln -s '$Path' -T '$Line'" Ungrep="$Ungrep|$(basename "$Line")" ;; esac } } done < <(store_runoption dump volume) } echo " # Copy files from /etc/skel into empty HOME [ -d \"\$HOME\" ] && { [ -d /etc/skel ] && [ -z \"\$(ls -A \"\$Containeruserhome\" 2>/dev/null | grep -v -E \"\.bashrc|\.profile|gnupg${Ungrep}\")\" ] && { debugnote \"containerrc: HOME is empty. Copying from /etc/skel\" cp -n -R /etc/skel/. \$Containeruserhome : } || { debugnote \"containerrc: HOME is not empty. Not copying from /etc/skel\" } } " echo "export DISPLAY='$Newdisplay' XAUTHORITY=$(convertpath share "$Xclientcookie")" case "$Xserver" in --tty) echo "unset DISPLAY WAYLAND_DISPLAY XAUTHORITY" ;; --weston|--kwin|--hostwayland) echo "unset DISPLAY XAUTHORITY" ;; *) echo "unset WAYLAND_DISPLAY" ;; esac [ "$Setupwayland" = "yes" ] && { echo "export WAYLAND_DISPLAY='$Newwaylandsocket'" echo "ln -s /$Newwaylandsocket \$XDG_RUNTIME_DIR/$Newwaylandsocket" } || { echo "export XDG_SESSION_TYPE=x11" } [ "$Dbusrunsession" = "yes" ] && { echo " # Check for dbus user daemon command [ -z \"\$DBUS_SESSION_BUS_ADDRESS\" ] && [ -d \"\$XDG_RUNTIME_DIR/bus\" ] && DBUS_SESSION_BUS_ADDRESS=\"unix:path=\$XDG_RUNTIME_DIR/bus;autolaunch:\" [ -z \"\$DBUS_SESSION_BUS_ADDRESS\" ] && { command -v dbus-run-session >/dev/null && Dbus=dbus-run-session command -v dbus-launch >/dev/null && Dbus=dbus-launch [ -z \"\$Dbus\" ] && note \"Option --dbus: Neither dbus-run-session nor dbus-launch found. Cannot run a DBus user session. Please install package dbus or even dbus-x11 in image.\" } " } echo " export TERM=xterm storeinfo test locale && export LANG=\"\$(storeinfo dump locale)\" [ -e \"$Hostlocaltimefile\" ] || export TZ=$Hostutctime [ \"\$(date -Ihours)\" != \"$(date -Ihours)\" ] && export TZ=$Hostutctime [ \"\$DEBIAN_FRONTEND\" = noninteractive ] && unset DEBIAN_FRONTEND && export DEBIAN_FRONTEND [ \"\$DEBIAN_FRONTEND\" = newt ] && unset DEBIAN_FRONTEND && export DEBIAN_FRONTEND # container environment (--env) " case "$Containerusershell" in auto) echo "command -v /bin/bash >/dev/null && export SHELL=/bin/bash || export SHELL=/bin/sh" ;; *) echo "export $(escapestring "SHELL=$Containerusershell")" ;; esac while read -r Line ; do echo "export $(escapestring "$Line")" done < <(store_runoption dump env) [ "$Xauthentication" = "no" ] && echo "unset XAUTHORITY && export XAUTHORITY" echo " [ -d \"\$HOME\" ] && cd \"\$HOME\" [ -n '$Workdir' ] && [ -d '$Workdir' ] && cd '$Workdir' # WORKDIR in image or option --workdir unset -f rmcr env >> $(convertpath share "$Containerenvironmentfile") verbose \"Container environment: \$(env | sort)\" " case "$Interactive" in no) echo "env LD_PRELOAD= tail -f $(convertpath share "$Cmdstdoutlogfile") 2>/dev/null & Stdoutpid=\$!" echo "env LD_PRELOAD= tail -f $(convertpath share "$Cmdstderrlogfile") >&2 2>/dev/null & Stderrpid=\$!" case "$Backend" in proot) echo 'storepid $Stdoutpid tail_stdout' echo 'storepid $Stderrpid tail_stderr' ;; esac echo "exec \$Dbus sh $(convertpath share "$Cmdrc") >> $(convertpath share "$Cmdstdoutlogfile") 2>>$(convertpath share "$Cmdstderrlogfile")" ;; yes) echo "exec \$Dbus sh $(convertpath share "$Cmdrc") <&0" ;; esac } >> "$Containerrc" return 0 } create_cmdrc() { # This script runs as unprivileged user in container and starts the final command local Line { echo "#! /bin/sh # Created startscript for cmdrc containing final container command $(declare -f storeinfo) $(declare -f saygoodbye) $(declare -f waitfortheend) $(declare -f rocknroll) $Messagefifofuncs Messagefile=$(convertpath share "$Messagefifo") Storeinfofile=\"$(convertpath share "$Storeinfofile")\" Timetosaygoodbyefile=$(convertpath share "$Timetosaygoodbyefile") [ -n \"\$DBUS_SESSION_BUS_ADDRESS\" ] && dbus-update-activation-environment --verbose --systemd DBUS_SESSION_BUS_ADDRESS DISPLAY XAUTHORITY WAYLAND_DISPLAY XDG_RUNTIME_DIR >$(convertpath share "$Containerlogfile") 2>&1 " echo " while rocknroll; do [ -e '$(convertpath share $Sharefolder/xhostready)' ] && break sleep 0.1 verbose 'cmdrc(): Waiting for /x11docker/xhostready' done " # --runasuser commands added here [ -n "$Runasuser" ] && { while read Line; do # The echoed string containing the debugnote command should not contain a newline character # to avoid problems with the generated cmdrc. echo "debugnote \"cmdrc: running --runsasuser command: $Line\"" echo " $Line " done <<< "$(grep . <<< "$Runasuser")" } # The echoed string containing the debugnote command should not contain a newline character # to avoid problems with the generated cmdrc. echo "debugnote \"cmdrc: Running container command: $Containerentrypoint $Containercommand\"" case "$Forwardstdin" in yes) echo "$Containerentrypoint $Containercommand <$(convertpath share "$Cmdstdinfifo")" ;; no) echo "$Containerentrypoint $Containercommand" ;; esac echo " storeinfo cmdexitcode=\$? " [ "$Checkforopenwindow" = "yes" ] && echo "waitfortheend" echo " export LD_PRELOAD= [ -h \"\$Homesoftlink\" ] && rm \$Homesoftlink saygoodbye cmdrc " } >> "$Cmdrc" return 0 } # code pieces for containerrootrc rootrc_create_helperscripts() { echo " # Create some helper scripts mkdir -p /usr/local/bin ### x11docker-message echo \"#! /bin/sh # Send messages to x11docker on host. # To be sourced by other scripts. $Messagefifofuncs_escaped Messagefile=$(convertpath share "$Messagefifo") \" >/usr/local/bin/x11docker-message ### x11docker-su echo \"#! /bin/sh # User switch from root in containerrootrc to unprivileged user in containerrc. # Called e.g. by x11docker-agetty. . /usr/local/bin/x11docker-message debugnote 'Running x11docker-su' chmod +x $(convertpath share "$Containerrc") \" >/usr/local/bin/x11docker-su " case "$Backend" in proot) echo "echo 'exec /bin/sh - $(convertpath share "$Containerrc")' >>/usr/local/bin/x11docker-su" ;; docker|podman|nerdctl) echo "echo \"exec su - -s /bin/sh \$Containeruser $(convertpath share "$Containerrc")\" >>/usr/local/bin/x11docker-su" ;; esac echo " chmod +x /usr/local/bin/x11docker-su ### x11docker-agetty echo \"#! /bin/sh # Run agetty to get a valid console. # Needed at least for --interactive. # Runs x11docker-su or agetty with login # Called at different places depending on init system. . /usr/local/bin/x11docker-message debugnote 'Running x11docker-agetty' " case "$Initsystem" in systemd|openrc|sysvinit|runit) echo " [ -e /sbin/agetty ] && exec agetty --autologin \$Containeruser console " ;; *) echo " [ -e /sbin/agetty ] && exec agetty --autologin \$Containeruser -l /usr/local/bin/x11docker-su console " ;; esac echo " debugnote 'x11docker-agetty: agetty not found.' note '/sbin/agetty not found. Startup can fail, --interactive can misbehave. Please install package util-linux in image.' exec /usr/local/bin/x11docker-su \" >/usr/local/bin/x11docker-agetty chmod +x /usr/local/bin/x11docker-agetty ### x11docker-watch echo \"#! /bin/sh # Wait for end of x11docker and shut down container. # Started in background by x11docker for sysvinit|runit|openrc. . /usr/local/bin/x11docker-message debugnote 'Running x11docker-watch' read Dummy <$(convertpath share "$Timetosaygoodbyefifo") echo timetosaygoodbye >>$(convertpath share "$Timetosaygoodbyefifo") debugnote 'x11docker-watch: $Initsystem shutdown now' shutdown 0 systemctl poweroff openrc-shutdown --poweroff 0 halt halt -f poweroff \" >/usr/local/bin/x11docker-watch chmod +x /usr/local/bin/x11docker-watch ### " return 0 } rootrc_create_locale() { local Line while read Line; do echo " # --lang: Language locale $Line verbose \"Searching for language locale matching $Line\" Locales=\"\$(locale -a)\" Langall=\"\$(cat /usr/share/i18n/SUPPORTED | grep -E 'UTF-8|utf8' | cut -d' ' -f1 | cut -d. -f1 | cut -d@ -f1 | sort | uniq)\" Langland=\"\$(echo $Line | cut -d. -f1)\" Langcontainer='' echo \"\$Langland\" | grep -q '_' || { Langland=\"\$(echo \$Langland | tr '[:upper:]' '[:lower:]')_\$(echo \$Langland | tr '[:lower:]' '[:upper:]')\" echo \"\$Langall\" | grep -q \"\$Langland\" || { echo \"\$Langall\" | grep -i -q \"$Line\" && { Langland=\"\$(echo \"\$Langall\" | grep -i -m1 \"$Line\")\" } } } Langland=\"\$(echo \"\$Langland\" | cut -d_ -f1 | tr '[:upper:]' '[:lower:]')_\$(echo \"\$Langland\" | cut -d_ -f2 | tr '[:lower:]' '[:upper:]')\" echo \"\$Locales\" | grep -q \"\$Langland.UTF-8\" && Langcontainer=\"\$Langland.UTF-8\" echo \"\$Locales\" | grep -q \"\$Langland.utf8\" && Langcontainer=\"\$Langland.utf8\" [ -z \"\$Langcontainer\" ] && { [ -e /usr/share/i18n/SUPPORTED ] || note \"Option --lang: /usr/share/i18n/SUPPORTED not found. Please install package 'locales' in image (belongs to glibc). Look here to find a package for your image system: https://github.com/mviereck/x11docker/wiki/dependencies#dependencies-in-image\" Langcontainer=\"\$Langland.utf8\" note \"Option --lang: Generating language locale \$Langcontainer\". command -v localedef >/dev/null || note 'Option --lang: Command localedef not found in image. Need it for language locale creation. Look here to find a package for your image system: https://github.com/mviereck/x11docker/wiki/dependencies#dependencies-in-image' localedef --verbose --force -i \"\$Langland\" -f UTF-8 \$Langcontainer || verbose \"localedef exit code: \$?\" locale -a | grep -q \"\$Langcontainer\" || { note \"Option --lang: Generation of locale \$Langcontainer failed.\" Langcontainer='' } } || { debugnote \"Option --lang: Found locale in image: \$Langcontainer\" } [ \"\$Langcontainer\" ] && { storeinfo locale=\"\$Langcontainer\" echo \"LANG=\$Langcontainer\" > /etc/default/locale } || { note 'Option --lang: Desired locale for '$Line' not found and not generated.' } " done < <(tac <<< "$Langwunsch" | grep . ||:) echo " debugnote \"Option --lang: Output of locale -a: \$(locale -a)\" " return 0 } rootrc_nvidia_installer() { # print code to install NVIDIA driver in container # $Nvidiainstallerfile must be shared with container as $Nvidiacontainerfile ### FIXME $Containerlibc echo " # Install NVIDIA driver Nvidiaversion=\"\$(nvidia-settings -v 2>/dev/null | grep version | rev | cut -d' ' -f1 | rev)\" [ \"\$Nvidiaversion\" ] && note \"Found NVIDIA driver \$Nvidiaversion in image.\" case \"\$Nvidiaversion\" in $Nvidiaversion) note 'NVIDIA driver version in image matches version on host. Skipping installation.' ;; *) Installationwillsucceed=maybe case \"\$Containerlibc\" in musl) note 'Installing NVIDIA driver in container systems based on musl libc like Alpine is not possible due to proprietary closed source policy of NVIDIA corporation.' Installationwillsucceed=no ;; esac [ \"\$Installationwillsucceed\" = \"maybe\" ] && { note 'Installing NVIDIA driver $Nvidiaversion in container.' mkdir -m 1777 /tmp2 # provide fake tools to fool installer dependency check ln -s /bin/true /tmp2/modprobe ln -s /bin/true /tmp2/depmod ln -s /bin/true /tmp2/lsmod ln -s /bin/true /tmp2/rmmod ln -s /bin/true /tmp2/ld ln -s /bin/true /tmp2/objcopy ln -s /bin/true /tmp2/insmod Nvidiaoptions='--accept-license --no-runlevel-check --no-questions --no-backup --ui=none --no-kernel-module --no-nouveau-check' env TMPDIR=/tmp2 PATH=\"/tmp2:\$PATH\" sh $Nvidiacontainerfile -A | grep -q -- '--install-libglvnd' && Nvidiaoptions=\"\$Nvidiaoptions --install-libglvnd\" env TMPDIR=/tmp2 PATH=\"/tmp2:\$PATH\" sh $Nvidiacontainerfile -A | grep -q -- '--no-nvidia-modprobe' && Nvidiaoptions=\"\$Nvidiaoptions --no-nvidia-modprobe\" env TMPDIR=/tmp2 PATH=\"/tmp2:\$PATH\" sh $Nvidiacontainerfile -A | grep -q -- '--no-kernel-module-source' && Nvidiaoptions=\"\$Nvidiaoptions --no-kernel-module-source\" env TMPDIR=/tmp2 PATH=\"/tmp2:\$PATH\" sh $Nvidiacontainerfile --tmpdir /tmp2 \$Nvidiaoptions || note 'ERROR: Installation of NVIDIA driver failed. Run with option --verbose to see installer output.' rm -R /tmp2 && unset TMPDIR } || note 'Skipping installation of $Nvidiacontainerfile' ;; esac" return 0 } rootrc_prepare_dbus() { echo " # check /etc/machine-id [ -s /etc/machine-id ] || dbus-uuidgen >/etc/machine-id # Prepare DBus services Unservicelist=' org.bluez org.bluez.obex org.freedesktop.hostname1 org.freedesktop.network1 org.freedesktop.resolve1 org.freedesktop.secrets org.freedesktop.timedate1 org.freedesktop.Tracker1 org.freedesktop.Tracker1.Miner.Extract org.freedesktop.UDisks2 org.freedesktop.UPower org.gtk.vfs.UDisks2VolumeMonitor org.opensuse.CupsPkHelper.Mechanism com.deepin.daemon.Bluetooth com.deepin.daemon.Grub2 com.deepin.daemon.Power com.deepin.lastore com.deepin.lastore.Smartmirror com.deepin.sync.Daemon com.deepin.sync.Helper com.deepin.userexperience.Daemon ' # ### FIXME test # Service=/etc/init.d/elogind # echo 'output_log=$(convertpath share $Containerlogfile)' >> \$Service # echo 'error_log=$(convertpath share $Containerlogfile)' >> \$Service # cat \$Service for Service in /usr/share/dbus-1/system-services/* /usr/share/dbus-1/services/*; do # find is not available on fedora Name=\"\$(cat \$Service | grep Name= | cut -d= -f2)\" Command=\"\$(cat \"\$Service\" | grep Exec= | cut -d= -f2)\" echo \"\$Unservicelist\" | grep -q -w \"\$Name\" && { debugnote \"DBus: Removing \$Name: \$Service\" rm \"\$Service\" } case \"\$Name\" in " [ "$Initsystem" != "systemd" ] && { echo " org.freedesktop.systemd1|org.freedesktop.hostname1|org.freedesktop.locale1) debugnote \"DBus: Removing \$Name: \$Service\" rm \"\$Service\" ;; " } echo " org.freedesktop.login1) debugnote \"DBus: Found login service \$Name: \$Command\" " [ "$Sharecgroup" = "no" ] && [ "$Cgroupversion" = "v1" ] && { echo " debugnote \"DBus: \$Name: Removing \$Service\" rm \"\$Service\" echo \"\$Command\" | grep -q elogind && { note 'Found login service elogind in container and cgroup v1 on host. If you want to use elogind in container, enable option --sharecgroup.' } " } echo " ;; esac done " return 0 } rootrc_prepare_init_openrc() { echo " # --init=openrc # Tell openrc that it runs in a container sed -e 's/#rc_sys=\"\"/rc_sys=\"$Backend\"/g' -i /etc/rc.conf # Create and enable x11docker service containing container command printf \"#!/sbin/openrc-run name=x11docker depend() { after * } start() { ebegin 'Starting x11docker-agetty' /usr/local/bin/x11docker-agetty openrc-shutdown --poweroff 0 shutdown 0 halt halt -f eend \$? } \" > /etc/init.d/x11docker.service chmod +x /etc/init.d/x11docker.service rc-update add x11docker.service default # DBus service [ -e /etc/init.d/dbus ] || echo '#!/sbin/openrc-run start() { ebegin \"Starting D-BUS system messagebus\" /usr/bin/dbus-uuidgen --ensure=/etc/machine-id mkdir -p /var/run/dbus start-stop-daemon --start --pidfile /var/run/dbus.pid --exec /usr/bin/dbus-daemon -- --system eend \$? } stop() { ebegin \"Stopping D-BUS system messagebus\" start-stop-daemon --stop --pidfile /var/run/dbus.pid retval=\$? eend \${retval} [ -S /var/run/dbus/system_bus_socket ] && rm -f /var/run/dbus/system_bus_socket return \${retval} } reload() { ebegin \"Reloading D-BUS messagebus config\" /usr/bin/dbus-send --print-reply --system --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig > /dev/null retval=\$? eend \${retval} return \${retval} } ' >/etc/init.d/dbus && chmod +x /etc/init.d/dbus verbose 'DBus: enabling dbus service' rc-update add dbus default " return 0 } rootrc_prepare_init_runit() { echo " # --init=runit # create and enable x11docker service containing container command mkdir -p /etc/sv/x11docker mkdir -p /etc/runit/runsvdir/default mkdir -p /etc/runit/1.d mkdir -p /service echo \"#! /bin/sh $(declare -f mysleep) waitforservice() { Service=\\\$1 [ \\\"\\\$(sv check \\\$Service | cut -d: -f1)\\\" = 'ok' ] && { echo \"x11docker: waiting for service \\\$Service ...\" for Count in $(seq -s' ' 20); do [ \\\"\\\$(sv status \\\$Service | cut -d: -f1)\\\" = 'down' ] && mysleep 0.2 || break done } } # make stderr visible exec 2>&1 # wait for all other services echo 'Content of /etc/runit/runsvdir/default:' ls -la /etc/runit/runsvdir/default/* for Service in /etc/runit/runsvdir/default/* ; do waitforservice \\\$Service ;done echo 'Current status of runit services:' for Service in /etc/runit/runsvdir/default/* ; do sv status \\\$Service ;done /usr/local/bin/x11docker-agetty \" > /etc/sv/x11docker/run chmod +x /etc/sv/x11docker/run echo \"#! /bin/sh sv down x11docker runit-init 0 init 0 shutdown -h 0 halt \" > /etc/sv/x11docker/finish chmod +x /etc/sv/x11docker/finish ln -s /etc/sv/x11docker /etc/runit/runsvdir/default #void ln -s /etc/sv/x11docker /service #alpine [ -e /etc/runit/1 ] || echo '#!/usr/bin/env sh set -eu chmod 100 /etc/runit/stopit /bin/run-parts --exit-on-error /etc/runit/1.d || exit 100 ' >/etc/runit/1 chmod +x /etc/runit/1 [ -e /etc/runit/2 ] || echo '#!/usr/bin/env sh set -eu runsvdir -P /service \"log: ..................................................................\" ' >/etc/runit/2 chmod +x /etc/runit/2 [ -e /etc/runit/3 ] || echo \"#!/usr/bin/env sh set -eu exec 2>&1 echo \"Waiting for services to stop...\" sv -w196 force-stop /service/* sv exit /service/* # kill any other processes still running in the container for ORPHAN_PID in \$(ps --no-headers -eo \"%p,\" -o stat | tr -d \" \" | grep \"Z\" | cut -d, -f1); do timeout 5 /bin/sh -c \"kill \$ORPHAN_PID && wait \$ORPHAN_PID || kill -9 \$ORPHAN_PID\" done \" >/etc/runit/3 chmod +x /etc/runit/3 [ -e /etc/sv/dbus ] || { mkdir -p /etc/sv/dbus echo '#!/bin/sh [ ! -d /run/dbus ] && install -m755 -g 22 -o 22 -d /run/dbus exec dbus-daemon --system --nofork --nopidfile ' >/etc/sv/dbus/run echo '#!/bin/sh exec dbus-send --system / org.freedesktop.DBus.Peer.Ping > /dev/null 2> /dev/null ' >/etc/sv/dbus/check chmod +x /etc/sv/dbus/run /etc/sv/dbus/check } verbose 'DBus: enabling dbus service' ln -s /etc/sv/dbus /etc/runit/runsvdir/default # void ln -s /etc/sv/dbus /service # alpine touch /etc/runit/stopit " return 0 } rootrc_prepare_init_systemd() { echo " # --init=systemd # remove failing and annoying services Unservicelist=' apt-daily.service apt-daily.timer apt-daily-upgrade.service apt-daily-upgrade.timer bluetooth.service cgproxy.service deepin-anything-monitor.service deepin-sync-daemon.service display-manager.service fprintd.service gdm3.service gvfs-udisks2-volume-monitor.service hwclock_stop.service lastore-daemon.service lastore-update-metadata-info.service lightdm.service NetworkManager.service plymouth-quit.service plymouth-quit-wait.service plymouth-read-write.service plymouth-start.service rtkit-daemon.service sddm.service systemd-localed.service systemd-hostnamed.service tracker-extract.service tracker-miner-fs.service tracker-store.service tracker-writeback.service udisks2.service upower.service ' for Service in \$(find /lib/systemd/system/* /usr/lib/systemd/user/* /etc/systemd/system/* /etc/systemd/user/*) ; do echo \"\$Unservicelist\" | grep -q \"\$(basename \$Service)\" && { debugnote \"--init=systemd: Removing \$Service\" rm \$Service } done # Fix for Gnome 3 sed -i 's/ProtectHostname=yes/ProtectHostname=no/' /lib/systemd/system/systemd-logind.service # create systemd units for x11docker mkdir -p /etc/systemd/system.conf.d echo \"[Manager] DefaultEnvironment=$(while read -r Line; do echo -n "$Line " ; done < <(store_runoption dump env)) \" > /etc/systemd/system.conf.d/x11docker.environment.conf echo \"[Unit] Description=x11docker target Wants=multi-user.target After=multi-user.target [Install] Also=console-getty.service Also=x11docker-watch.service Also=x11docker-journal.service \" > /etc/systemd/system/x11docker.target echo \"[Unit] Description=x11docker agetty service # initiate console # runs x11docker-agetty->x11docker-su or login->containerrc->cmdrc Wants=multi-user.target Wants=x11docker-watch.service Wants=x11docker-journal.service Wants=dbus.service After=systemd-user-sessions.service After=rc-local.service getty-pre.target Before=getty.target [Service] ExecStart=/usr/local/bin/x11docker-agetty StandardInput=tty StandardOutput=tty Type=idle UtmpIdentifier=cons TTYPath=/dev/console TTYReset=yes TTYVHangup=yes KillMode=process IgnoreSIGPIPE=no SendSIGHUP=yes [Install] WantedBy=x11docker.target WantedBy=getty.target WantedBy=multi-user.target \" > /lib/systemd/system/console-getty.service echo \"[Unit] Description=x11docker watch service # Watches for end of containerrc and initiates shutdown [Service] Type=simple ExecStart=/bin/sh -c 'while sleep 1; do systemctl is-active console-getty >/dev/null || { echo timetosaygoodbye >>$(convertpath share "$Timetosaygoodbyefile") ; systemctl halt ; } ; [ -s $(convertpath share "$Timetosaygoodbyefile") ] && systemctl halt ; done' [Install] WantedBy=x11docker.target \" > /etc/systemd/system/x11docker-watch.service echo \"[Unit] Description=x11docker containerrc service [Service] Type=simple ExecStart=sh $(convertpath share "$Containerrc") [Install] WantedBy=default.target \" > /etc/systemd/user/x11docker-containerrc.service echo \"[Unit] Description=x11docker journal log service # get systemd log to transfer it into x11docker.log [Service] Type=simple ExecStart=/bin/sh -c '/bin/journalctl --follow --no-tail >> $(convertpath share "$Systemdjournallogfile") 2>&1' [Install] WantedBy=x11docker.target \" > /etc/systemd/system/x11docker-journal.service # enable x11docker CMD service systemctl unmask console-getty.service systemctl enable console-getty.service systemctl enable x11docker-journal.service # enable logind service systemctl unmask systemd-logind systemctl enable systemd-logind # enable DBus service systemctl unmask dbus systemctl enable dbus " return 0 } rootrc_prepare_init_sysvinit() { echo " # --init=sysvinit # Adding x11docker start command to rc.local sed -i '/exit 0/d' /etc/rc.local echo \" /usr/local/bin/x11docker-agetty || echo \\\"x11docker: Exit code of x11docker-agetty: \\\$?\\\" echo 'x11docker: rc.local sends shutdown -h now' shutdown -h now exit 0 \" >> /etc/rc.local chmod +x /etc/rc.local # DBus service echo '#!/bin/sh ### BEGIN INIT INFO # Provides: dbus # Required-Start: \$remote_fs \$syslog # Required-Stop: \$remote_fs \$syslog # Default-Start: 2 3 4 5 # Default-Stop: # Short-Description: D-Bus systemwide message bus # Description: D-Bus is a simple interprocess messaging system, used # for sending messages between applications. ### END INIT INFO # -*- coding: utf-8 -*- # Debian init.d script for D-BUS # Copyright © 2003 Colin Walters # Copyright © 2005 Sjoerd Simons # DAEMON=/usr/bin/dbus-daemon UUIDGEN=/usr/bin/dbus-uuidgen UUIDGEN_OPTS=--ensure NAME=dbus DAEMONUSER=messagebus PIDDIR=/var/run/dbus PIDFILE=\"\$PIDDIR/pid\" DESC=\"system message bus\" # test -x \$DAEMON || exit 1 . /lib/lsb/init-functions # Source defaults file; edit that file to configure this script. PARAMS="" if [ -e /etc/default/dbus ]; then . /etc/default/dbus fi create_machineid() { # Create machine-id file if [ -x \$UUIDGEN ]; then \$UUIDGEN \$UUIDGEN_OPTS fi } start_it_up() { [ -d \$PIDDIR ] || { mkdir -p \$PIDDIR chown \$DAEMONUSER \$PIDDIR chgrp \$DAEMONUSER \$PIDDIR } mountpoint -q /proc/ || { log_failure_msg \"Cannot start \$DESC - /proc is not mounted\" return 1 } [ -e \$PIDFILE ] && { \$0 status > /dev/null && { log_success_msg \"\$DESC already started; not starting.\" return 0 } log_success_msg \"Removing stale PID file \$PIDFILE.\" rm -f \$PIDFILE } create_machineid log_daemon_msg \"Starting \$DESC\" \"\$NAME\" start-stop-daemon --start --quiet --pidfile \$PIDFILE --exec \$DAEMON -- --system \$PARAMS log_end_msg \$? } shut_it_down() { log_daemon_msg \"Stopping \$DESC\" \"\$NAME\" start-stop-daemon --stop --retry 5 --quiet --oknodo --pidfile \$PIDFILE --user \$DAEMONUSER log_end_msg \$? rm -f \$PIDFILE } reload_it() { create_machineid log_action_begin_msg \"Reloading \$DESC config\" dbus-send --print-reply --system --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig > /dev/null log_action_end_msg \$? } case \$1 in start) start_it_up ;; stop) shut_it_down ;; reload|force-reload) reload_it ;; restart) shut_it_down start_it_up ;; status) status_of_proc -p \$PIDFILE \$DAEMON \$NAME && exit 0 || exit \$? ;; esac ' > /etc/init.d/dbus chmod +x /etc/init.d/dbus " return 0 } rootrc_setup_user() { local Line echo " # Set up container user " case "$Containerusershell" in auto) echo "bash --version >/dev/null 2>&1 && Containerusershell=/bin/bash || Containerusershell=/bin/sh" ;; *) echo "Containerusershell='$Containerusershell'" ;; esac echo " # /etc/passwd Containeruserentry=\"\$Containeruser:x:$Containeruseruid:$Containerusergid:\$Containeruser,,,:\$Containeruserhome:\$Containerusershell\" debugnote \"containerrootrc: \$Containeruserentry\" # Disable possible /etc/shadow passwords for other users # Delete root user # Delete possibly existing user with same uid sed -i 's%:x:%:-:% ; /:0:0:/d ; /:$Containeruseruid:/d' /etc/passwd echo \"\$Containeruserentry\" >> /etc/passwd echo \"root:-:0:0:root:/root:\$Containerusershell\" >> /etc/passwd # Create password entry for container user in /etc/shadow rm -f -v /etc/shadow || warning 'Cannot change /etc/shadow. That may be a security risk.' echo \"\$Containeruser:\"'$Containeruserpassword'\":17293:0:99999:7:::\" > /etc/shadow chown root:shadow /etc/shadow " case "$Sudouser" in "") echo "echo 'root:*:17219:0:99999:7:::' >> /etc/shadow" ;; *) 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" ;; esac echo " chmod 640 /etc/shadow # can fail depending on available capabilities # sudo configuration # Create /etc/sudoers, delete /etc/sudoers.d. Overwrite possible sudo setups in image. [ -e /etc/sudoers.d ] && rm -f -v -R /etc/sudoers.d [ -e /etc/sudoers ] && rm -f -v /etc/sudoers echo '# /etc/sudoers created by x11docker' > /etc/sudoers echo 'Defaults env_reset' >> /etc/sudoers echo 'root ALL=(ALL) ALL' >> /etc/sudoers " case "$Sudouser" in yes) echo "echo '$Containeruser ALL=(ALL) ALL' >> /etc/sudoers" ;; nopasswd) echo "echo '$Containeruser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers" ;; esac # try to disable possible custom PAM setups that could allow a switch to root in container ### FIXME maybe not foolproof [ -z "$Sudouser" ] && { echo "# /etc/pam.d # Restrict PAM configuration of su and sudo mkdir -p /etc/pam.d [ -e /etc/pam.d/sudo ] && rm -f -v /etc/pam.d/sudo case \"\$Containersystem\" in fedora) echo '#%PAM-1.0' > /etc/pam.d/su echo 'auth sufficient pam_rootok.so' >> /etc/pam.d/su # echo 'auth substack system-auth' >> /etc/pam.d/su # echo 'auth include postlogin' >> /etc/pam.d/su echo 'account sufficient pam_succeed_if.so uid = 0 use_uid quiet' >> /etc/pam.d/su # echo 'account include system-auth' >> /etc/pam.d/su # echo 'password include system-auth' >> /etc/pam.d/su echo 'session include system-auth' >> /etc/pam.d/su echo 'session include postlogin' >> /etc/pam.d/su echo 'session optional pam_xauth.so' >> /etc/pam.d/su ;; *) echo '#%PAM-1.0' > /etc/pam.d/su echo 'auth sufficient pam_rootok.so' >> /etc/pam.d/su # allow root to switch user without a password echo '@include common-auth' >> /etc/pam.d/su echo '@include common-account' >> /etc/pam.d/su echo '@include common-session' >> /etc/pam.d/su ;; esac " } echo "# /etc/group /etc/gshadow sed -i \"s/\$Containeruser//g ; s/:,/:/g ; s/,\$//g\" /etc/group # remove existing entries for user in /etc/group sed -i \"s/\$Containeruser//g ; s/:,/:/g ; s/,\$//g\" /etc/gshadow # remove existing entries for user in /etc/gshadow " for Line in $Containerusergroups ; do echo " Groupname=\"$(cat /etc/group 2>/dev/null | grep "^$Line" | cut -d: -f1)\" Groupid=\"$(cat /etc/group 2>/dev/null | grep "^$Line" | cut -d: -f3)\" [ \"\$Groupname\" ] || Groupname=\"\$(cat /etc/group | grep \"$Line\" | cut -d: -f1)\" [ \"\$Groupid\" ] || Groupid=\"\$(cat /etc/group | grep \"$Line\" | cut -d: -f3)\" [ \"\$Groupname\" ] && { # /etc/group Entry=\"\$(grep \$Groupname /etc/group)\" Entry=\"\$(echo \"\$Entry\" | cut -d: -f3-)\" [ -z \"\$Entry\" ] && Entry=\"\$Groupid:\" Entry=\"\$Groupname:x:\$Entry,\$Containeruser\" Entry=\"\$(echo \"\$Entry\" | sed 's/:,/:/g')\" sed -i \"/^\$Groupname:/d\" /etc/group echo \"\$Entry\" >> /etc/group # /etc/gshadow Entry=\"\$(grep \$Groupname /etc/gshadow)\" Entry=\"\$(echo \"\$Entry\" | cut -d: -f4)\" Entry=\"\$Groupname:!::\$Entry,\$Containeruser\" Entry=\"\$(echo \"\$Entry\" | sed 's/:,/:/g')\" sed -i \"/^\$Groupname:/d\" /etc/gshadow echo \"\$Entry\" >> /etc/gshadow } || { note \"Failed to add container user to group '$Line'.\" } # Create user group entry (and delete possibly existing same gid) sed -i '/:$Containerusergid:/d' /etc/group echo \"$Containerusergroup:x:$Containerusergid:\" >> /etc/group sed -i '/:$Containerusergid:/d' /etc/gshadow echo \"$Containerusergroup:!::\" >> /etc/gshadow sed -i -e 's/\(:\).*\(:\)/\1!:\2/' /etc/gshadow # remove possible passwords " done return 0 } rootrc_timezone() { echo " # Time zone [ ! -d /usr/share/zoneinfo ] && [ \"\$Containerlibc\" = \"$Hostlibc\" ] && { mkdir -p $(dirname "$Hostlocaltimefile") cp '$(convertpath share "$Containerlocaltimefile")' '$Hostlocaltimefile' } [ -e '$Hostlocaltimefile' ] && ln -f -s '$Hostlocaltimefile' /etc/localtime " return 0 } rootrc_xrandr() { echo " # workaround: autostart of xrandr for some desktops like deepin, cinnamon and gnome to fix wrong autoresize echo '#! /bin/sh Output=\$(xrandr | grep \" connected\" | cut -d\" \" -f1) Mode=$Screensize xrandr --output \$Output --mode \$Mode ' > /usr/local/bin/x11docker-xrandr chmod +x /usr/local/bin/x11docker-xrandr mkdir -p /etc/xdg/autostart echo '[Desktop Entry] Encoding=UTF-8 Version=0.9.4 Type=Application Name=x11docker-xrandr Comment= Exec=/usr/local/bin/x11docker-xrandr ' > /etc/xdg/autostart/x11docker-xrandr.desktop " return 0 } #### final startup routines start_container() { # docker run # create containerrc -> runs as unprivileged user in container # check and set up cgroup on host for systemd or elogind # run docker local Containerid= Containerip= Containerinspect= local Failure= Pid1pid= # [ "$Winsubsystem" = "MSYS2" ] && { ### FIXME check if needed # # avoid path conversion in MSYS2 commands # export MSYS2_ARG_CONV_EXCL='*' # } debugnote "start_container(): Running image" case "$Interactive" in no) case "$Backend" in host) unpriv "bash '$Containerrc'" & Pid1pid=$! ;; proot) unpriv "$Backendcommand" & Pid1pid=$! ;; docker|podman|nerdctl) unpriv_backend "$Backendcommand" >> $Containerlogfile 2>&1 ||: & ;; esac ;; yes) case "$Backend" in host) unpriv "bash '$Containerrc'" <&0 & Pid1pid=$! ;; proot) unpriv "$Backendcommand" <&0 & Pid1pid=$! ;; docker|podman|nerdctl) [ "$Winpty" ] && { $Winpty bash "$(oneline "$Backendcommand")" <&0 & } || { unpriv_backend "$Backendcommand" <&0 & } ;; esac ;; esac case "$Backend" in proot|host) ;; docker|podman|nerdctl) # Wait for container to be ready debugnote "start_container(): Running repeated checks if container is ready." for ((Count=1 ; Count<=40 ; Count++)); do unpriv_backend "$Backendbin exec $Containername sh -c ':'" 2>&1 | rmcr >> $Containerlogfile && { debugnote 'start_container(): Container is up and running.' break } || { debugnote "start_container(): Container not ready on ${Count}. attempt, trying again." } rocknroll || break mysleep 0.1 done # Wait for pid 1 in container for ((Count=1 ; Count<=40 ; Count++)); do debugnote "start_container(): $Count. check for PID 1" Containerinspect="$(unpriv_backend "$Backendbin inspect $Containername" 2>> $Containerlogfile | rmcr)" ||: [ -n "$Containerinspect" ] && [ "$Containerinspect" != "[]" ] && Pid1pid="$(parse_inspect "$Containerinspect" "State" "Pid")" case "$Mobyvm" in no) checkpid "$Pid1pid" && break ;; yes) [ "$Pid1pid" ] && [ "$Pid1pid" != "0" ] && break esac rocknroll || break mysleep 0.1 done [ "$Pid1pid" = "0" ] && Pid1pid="" debugnote "start_container(): PID1=$Pid1pid" [ -z "$Pid1pid" ] && error "start_container(): Did not receive PID of PID1 in container. Maybe the container immediately stopped for unknown reasons. Just in case, check if host and image architecture are compatible: Host architecture: $Hostarchitecture, image architecture: $Imagearchitecture. Content of container log: $(rmcr < "$Containerlogfile" | uniq )" # store stdout and stderr [ "$Containersetup" = "no" ] && { # Store container output separated for stdout and stderr unpriv_backend "$Backendbin logs -f $Containername" 1>> "$Cmdstdoutlogfile" 2>> "$Cmdstderrlogfile" & ### FIXME rootless, unpriv storepid $! dockerlogs } # Get IP of container case "$Xcnetworkid" in "") Containerip="$(parse_inspect "$Containerinspect" "NetworkSettings" "IPAddress")" ;; *) Containerip="$(parse_inspect "$Containerinspect" "NetworkSettings" "Networks" "$Xcnetworkname" "IPAddress")" ;; esac storeinfo "containerip=$Containerip" # Get ID of container Containerid="$(parse_inspect "$Containerinspect" "Id")" storeinfo "containerid=$Containerid" # --init=systemd cgroupv2 [ "$Remountcgroup" = "yes" ] && { # Compare https://github.com/mviereck/x11docker/issues/349 Remountcommand="$Backendbin run --rm --user root" Remountcommand="$Remountcommand --cap-add SYS_ADMIN --cap-add=SYS_PTRACE --security-opt apparmor=unconfined" Remountcommand="$Remountcommand --pid=container:$Containerid" Remountcommand="$Remountcommand --entrypoint /usr/bin/env" [ "$Runtime" ] && Remountcommand="$Remountcommand --runtime $Runtime" Remountcommand="$Remountcommand -- $Imagename" Remountcommand="$Remountcommand nsenter -t 1 -m -C -p /bin/sh -c '" Remountcommand="$Remountcommand set -x ; " Remountcommand="$Remountcommand findmnt /sys/fs/cgroup -O rw || mount -v -o remount,rw /sys/fs/cgroup/ ; " Remountcommand="$Remountcommand findmnt /sys/fs/cgroup -O rw || echo \"ERROR: cgroup mount failed.:NOTE\" >>/x11docker/message.fifo ; " Remountcommand="$Remountcommand touch /nsenter_is_ready'" debugnote "start_container(): Remounting /sys/fs/cgroup to :rw with nsenter: $Remountcommand" echo "cgroup mount with nsenter:" >> "$Containerlogfile" unpriv_backend "$Remountcommand" >> "$Containerlogfile" 2>&1 || { note "Option --init=$Initsystem: Failed to remount /sys/fs/cgroup in container. Startup of systemd might fail. nsenter command: $Remountcommand Last lines of log: $(tail "$Containerlogfile")" } } ;; esac storeinfo "pid1pid=$Pid1pid" # Check log for startup failure Failure="$(rmcr < "$Containerlogfile" | grep -v grep | grep -E 'OCI runtime exec' ||:)" [ "$Failure" ] && { echo "$Failure" >> "$Containerlogfile" error "start_container(): Got error message from backend $Backend: $Failure Last lines of logfile: $(tail "$Containerlogfile")" } # start containerrootrc [ "$Switchcontaineruser" = "no" ] && [ "$Containersetup" = "yes" ] && { debugnote "start_container(): Starting containerrootrc with $Backend exec" # copy containerrootrc inside of container to avoid possible noexec of host home. case "$Backend" in proot) ### FIXME currently with Switchcontaineruser=yes only ;; host) ;; # no containerrootrc docker|podman|nerdctl) # copy to /tmp because host HOME might be nosuid or noexec # run container root setup. containerrc will wait until setup script is ready. unpriv_backend "$Backendbin exec -u root $Containername sh -c 'cp $(convertpath share "$Containerrootrc") /tmp/containerrootrc ; chmod 644 /tmp/containerrootrc ; exec /bin/sh /tmp/containerrootrc'" 2>&1 | rmcr >> $Containerlogfile ;; esac } return 0 } start_compositor() { # start Wayland compositor Weston or KWin local Compositorkeyword case "$Xserver" in --weston|--weston-xwayland|--xpra-xwayland|--xpra2-xwayland) Compositorkeyword="weston-desktop-shell" ;; --kwin|--kwin-xwayland) Compositorkeyword="XKEYBOARD" ;; esac case "$Xcontainer" in yes) unpriv_xcbackend "$Xcontainerbackend exec --detach $Xcontainername sh -c '$Compositorcommand >> $Compositorlogfile 2>&1'" 2>> $Compositorlogfile Compositorpid="$(ps aux | grep weston | grep "$Newwaylandsocket" | head -n1 | awk '{print $2}')" ;; no) unpriv "$Compositorcommand >> '$Compositorlogfile' 2>&1 & echo compositorpid=\$! >> '$Storeinfofile'" Compositorpid="$(storeinfo dump compositorpid)" ;; esac storeinfo "compositorpid=$Compositorpid" waitforlogentry "start_compositor()" "$Compositorlogfile" "$Compositorkeyword" "$Compositorerrorcodes" setonwatchpidlist "$(storeinfo dump compositorpid)" compositor case "$Xserver" in --xpra-xwayland|--xpra2-xwayland) # hide weston window xtool "xdotool windowunmap 0x$(printf '%x\n' $(grep 'window id' "$Compositorlogfile" | rev | cut -d' ' -f1 | rev))" ;; esac return 0 } start_xcontainer() { # --xc local Containerinspect Xpid1pid read Xcontainerid< <(unpriv_xcbackend "$Xcontainercommand" 2>> $Xinitlogfile) storeinfo "Xcontainerid=$Xcontainerid" Containerinspect="$(unpriv_xcbackend "$Xcontainerbackend inspect $Xcontainername" 2>> $Xinitlogfile | rmcr)" [ -n "$Containerinspect" ] && [ "$Containerinspect" != "[]" ] && { Xcontainerip="$(parse_inspect "$Containerinspect" "NetworkSettings" "IPAddress")" Xcontainerip="${Xcontainerip:-$Xcontainername}" storeinfo "Xcontainerip=$Xcontainerip" Xpid1pid="$(parse_inspect "$Containerinspect" "State" "Pid")" setonwatchpidlist "$Xpid1pid" "Xcontainerpid1" } || { error "Option --xc: Startup of x11docker/xserver failed. Last lines of xinit log: $(tail "$Xinitlogfile")" return 1 } [ "$Nvidiainstallerfile" ] && { note "Option --gpu: Installing NVIDIA driver in X container." unpriv_xcbackend "$Xcontainerbackend exec --tty --privileged --user=root $Xcontainername bash $Sharefolder/nvidia_installer" >> "$Xinitlogfile" 2>&1 || note 'ERROR: NVIDIA driver installation in X container failed.' } waitforlogentry "start_xcontainer()" "$Xinitlogfile" "X server container is ready" || return 1 return 0 } start_xserver() { # start X server local Exitcode= Xcontainerexitcode= Command case "$Xserver" in --xpra*|--xephyr|--xvfb|--nxagent|--xwayland|--weston-xwayland|--kwin-xwayland|--xwin|--xorg) Command="env WAYLAND_DISPLAY=$Newwaylandsocket xinit $Xinitrc -- $Xcommand" ;; --hostdisplay|--hostwayland|--weston|--kwin|--tty) Command="sh $Xinitrc" ;; --runx) Command="$Xcommand -- sh $Xinitrc" ;; esac case "$Xcontainer" in yes) unpriv_xcbackend "$Xcontainerbackend exec $Xcontainername $Command" >> $Xinitlogfile 2>&1 Xcontainerexitcode=$? case "$Xcontainerexitcode" in 137) Exitcode="0" ;; # docker error indicating that xinit did not shut down fast enough on SIGTERM. Ignore it. *) Exitcode="$Xcontainerexitcode" ;; esac ;; no) case "$Xlegacywrapper" in yes|"") unpriv "$Command" >> $Xinitlogfile 2>&1 ;; no) eval "$Command" >> $Xinitlogfile 2>&1 ;; esac ;; esac Exitcode="${Exitcode:-$?}" [ "$Exitcode" != 0 ] && rocknroll && note "X server $Xserver returned error code $Exitcode. Last lines of xinit logfile: $(tail -n15 "$Xinitlogfile") $( [ -s "$Compositorlogfile" ] && echo " Last lines of compositor log: $(tail "$Compositorlogfile")")" return "${Exitcode:-0}" } start_xpra() { # options --xpra / --xpra-xwayland: start and watch xpra server and xpra client local Xpraserverpid Xpraclientpid Xpraenv case "$Hostwaylandsocket" in "") Xpraenv=" GDK_BACKEND=x11" ;; *) Xpraenv=" GDK_BACKEND=wayland" ;; esac [ -n "$Xprashm" ] && Xpraenv="$Xpraenv \\ $Xprashm" Xpraenv="$Xpraenv \\ NO_AT_BRIDGE=1 \\ XPRA_EXPORT_ICON_DATA=0 \\ XPRA_EXPORT_XDG_MENU_DATA=0 \\ XPRA_ICON_OVERLAY=0 \\ XPRA_MENU_ICONS=0 \\ XPRA_UINPUT=0 \\ XPRA_VAAPI=0 \\ XPRA_XDG_EXPORT_ICONS=0 \\ XPRA_XDG_LOAD_GLOB=0" # xpra server Xpraservercommand="env XAUTHORITY='$Xclientcookie' \\ $Xpraenv \\ GDK_BACKEND=x11 \\ $Xpraservercommand" debugnote "Running xpra server: $Xpraservercommand" echo "x11docker [$(timestamp)]: Starting Xpra server" >> "$Xpraserverlogfile" case "$Xcontainer" in no) unpriv "$Xpraservercommand ||:" >> "$Xpraserverlogfile" 2>&1 & #Xpraserverpid=$! ;; yes) unpriv_xcbackend "$Xcontainerbackend exec $Xcontainername sh -c '$Xpraservercommand'" >> "$Xpraserverlogfile" 2>&1 & #Xpraserverpid="$(ps aux | grep "/usr/bin/xpra start $Newdisplay" | grep -v grep | awk '{print $2}')" ;; esac # xpra client Xpraclientcommand="env $Hostxenv \\ $Xpraenv $Xpraclientcommand" debugnote "Running xpra client: $Xpraclientcommand" echo "x11docker [$(timestamp)]: Starting Xpra client" >> "$Xpraclientlogfile" [ "$Xcontainer" = "yes" ] && [ "$Xserver" != "--xpra2" ] && [ "$Xserver" != "--xpra2-xwayland" ] && { unpriv_xcbackend "$Xcontainerbackend exec $Xcontainername sh -c '$Xpraclientcommand'" >> "$Xpraclientlogfile" 2>&1 & : } || { unpriv "$Xpraclientcommand ||:" >> "$Xpraclientlogfile" 2>&1 & } # get pids sleep 3 # avoid race condition not finding the process Xpraserverpid="$(ps aux | grep -E "/usr/bin/xpra start :$Newdisplaynumber|/usr/bin/xpra start-desktop :$Newdisplaynumber" | grep -v grep | awk '{print $2}')" storepid "$Xpraserverpid" xpraserver Xpraclientpid="$(ps aux | grep "/usr/bin/xpra attach :$Newdisplaynumber" | grep -v grep | awk '{print $2}')" storepid "$Xpraclientpid" xpraclient # catch possible xpra crashes while rocknroll; do ps -p "$Xpraserverpid" >/dev/null || { debugnote "xpra server terminated" ; break ; } ps -p "$Xpraclientpid" >/dev/null || { debugnote "xpra client terminated" ; break ; } sleep 1 done sleep 3 && rocknroll && note "Option $Xserver: xpra terminated unexpectedly. Last lines of xpra server log: $(pspid "$Xpraserverpid") $(tail "$Xpraserverlogfile") --------------------------------- Last lines of xpra client log: $(pspid "$Xpraclientpid") $(tail "$Xpraclientlogfile")" saygoodbye xpra return 0 } start_pulseaudiotcp() { # option --pulseaudio=tcp: load Pulseaudio TCP module authenticated with container IP local Containerip Containerip="$(storeinfo dump containerip)" Pulseaudiomoduleid="$(unpriv "pactl load-module module-native-protocol-tcp port=$Pulseaudioport auth-ip-acl=${Containerip:-"127.0.0.1"}" )" [ "$Pulseaudiomoduleid" ] && { storeinfo "pulseaudiomoduleid=$Pulseaudiomoduleid" } || note "Option --pulseaudio: command pactl failed. Is pulseaudio running at all on your host? You can try option --alsa instead." return 0 } #### main init routines check_console() { # check whether x11docker runs on console ### FIXME not reliable. If in doubt, sets "no" env LANG=C tty 2>&1 | grep -q '/dev/tty' && Runsonconsole="yes" || Runsonconsole="no" id "$Hostuser" | grep -q -e "(tty)" -e "(root)" || { unpriv "fgconsole" >/dev/null 2>&1 && Runsonconsole="yes" } debugnote "check_console(): Guess if running on console: $Runsonconsole" } check_fallback() { # --fallback # Option --fallback case "$Fallback" in no) error "Option --fallback=no: Fallbacks are disabled. x11docker cannot fulfill an option you have chosen, see message above." ;; esac } check_host() { # check host environment local Drive [ "${0:-}" = "${BASH_SOURCE:-}" ] && Runssourced="no" || Runssourced="yes" Hostsystem="$(grep '^ID=' /etc/os-release 2>/dev/null | cut -d= -f2 || echo 'unknown')" Hostarchitecture="$(uname -m)" case "$Hostarchitecture" in x86_64|x86-64|amd64|AMD64) Hostarchitecture="amd64 ($Hostarchitecture)" ;; aarch64|armv8|ARMv8|arm64v8) Hostarchitecture="arm64v8 ($Hostarchitecture)" ;; aarch32|armv8l|armv7|armv7l|ARMv7|arm32v7|armhf|armv7hl) Hostarchitecture="arm32v7 ($Hostarchitecture)" ;; arm32v6|ARMv6|armel) Hostarchitecture="arm32v6 ($Hostarchitecture)" ;; arm32v5|ARMv5) Hostarchitecture="arm32v5 ($Hostarchitecture)" ;; i686|i386|x86) Hostarchitecture="i386 ($Hostarchitecture)" ;; ppc64*|POWER8) Hostarchitecture="ppc64le ($Hostarchitecture)" ;; s390x) Hostarchitecture="s390x ($Hostarchitecture)" ;; mips|mipsel) Hostarchitecture="mipsel ($Hostarchitecture)" ;; mips64*) Hostarchitecture="mips64el ($Hostarchitecture)" ;; *) Hostarchitecture="unknown ($Hostarchitecture)" ;; esac # Check libc from host. If same as in container, it is possible to share timezone file Hostlibc="unknown" ldd --version 2>&1 | grep -q 'musl libc' && Hostlibc='musl' ldd --version 2>&1 | grep -q -E 'GLIBC|GNU libc' && Hostlibc='glibc' # cgroup version case "$(stat -c"%T" -f /sys/fs/cgroup)" in tmpfs) Cgroupversion="v1" ;; cgroup2fs) Cgroupversion="v2" ;; *) Cgroupversion="UNKNOWN" ;; esac debugnote "Detected cgroup $Cgroupversion" # Check host time zone Hostlocaltimefile="$(myrealpath /etc/localtime)" # Find time zone file in /usr/share/zoneinfo [ -e "$Hostlocaltimefile" ] || Hostlocaltimefile="" Hostutctime=$(date +%:::z) # Offset of UTC. Used if time zone file cannot be provided [ "$(cut -c1 <<< "$Hostutctime")" = "+" ] && { Hostutctime="UTC-$(cut -c2- <<< "$Hostutctime")" } || { Hostutctime="UTC+$(cut -c2- <<< "$Hostutctime")" } # Check for MS Windows subsystem command -v cygcheck.exe >/dev/null && { cygcheck.exe -V | rmcr | grep -q "(cygwin)" && Winsubsystem="CYGWIN" cygcheck.exe -V | rmcr | grep -q "(msys)" && Winsubsystem="MSYS2" } uname -r | grep -q "Microsoft" && Winsubsystem="WSL1" uname -r | grep -q "microsoft" && Winsubsystem="WSL2" case "$Winsubsystem" in MSYS2|CYGWIN) Winsubmount="$(cygpath.exe -u "c:/" | rmcr | sed s%/c/%%)" Winsubpath="$(convertpath unix "$(cygpath.exe -w "/" | rmcr)" )" Mobyvm="yes" ;; WSL1|WSL2) command -v "/mnt/c/Windows/System32/cmd.exe" >/dev/null && Winsubmount="/mnt" command -v "/c/Windows/System32/cmd.exe" >/dev/null && Winsubmount="" grep -q "Windows" <<< "${PATH:-}" || export PATH="${PATH:-}:$Winsubmount/c/Windows/System32:$Winsubmount/c/Windows/System32/WindowsPowerShell/v1.0" # can miss after sudo in WSL command -v "$Winsubmount/c/Windows/System32/cmd.exe" >/dev/null || error "$Winsubsystem: Could not find cmd.exe in /mnt/c/Windows/System32 or /c/Windows/System32. Do you have a different path to your Windows system partition?" Winsubpath="$(convertpath unix "$(getwslpath)")" [ "$Winsubsystem" = "WSL1" ] && Mobyvm="yes" ;; esac Winsubmount="${Winsubmount%/}" Winsubpath="${Winsubpath%/}" [ "$Winsubsystem" ] && Hostsystem="MSWindows-$Winsubsystem" [ -z "$Mobyvm" ] && Mobyvm="no" # Check host IP. Needed for --pulseaudio=tcp, --printer=tcp, --xoverip and --xwin case "$Winsubsystem" in "") case "$Network" in host) Hostip="127.0.0.1" ;; *) #Hostip="$(hostname -I | cut -d' ' -f1)" [ "$Hostip" ] || Hostip="$(ip -4 -o a | awk '{print $4}' | cut -d/ -f1 | grep "^192\.168\.*" | head -n1)" [ "$Hostip" ] || Hostip="$(ip -4 -o a | grep 'docker0' | awk '{print $4}' | cut -d/ -f1 | grep "172.17.0.1" ||: )" [ "$Hostip" ] || Hostip="$(ip -4 -o a | grep 'docker0' | awk '{print $4}' | cut -d/ -f1 | head -n1)" [ "$Hostip" ] || Hostip="$(ip -4 -o a | awk '{print $4}' | cut -d/ -f1 | grep -v "127.0.0.1" | head -n1)" ;; esac ;; *) Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '192\.168\.[0-9]*\.[0-9]*' | head -n1 )" [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '10\.0\.[0-9]*\.[0-9]*' | head -n1 )" [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*' | head -n1 )" ;; esac # Provide dos->unix newline converter to unpriv() commands [ "$Winsubsystem" ] || rmcr() { cat; } export -f rmcr # Check whether x11docker runs over SSH pstree -ps $$ >/dev/null 2>&1 && { pstree -ps $$ | grep -q sshd && Runsoverssh="yes" || Runsoverssh="no" } || { check_parent_sshd "$$" && Runsoverssh="yes" || Runsoverssh="no" } # Check whether x11docker runs in a terminal tty -s && Runsinterminal="yes" || Runsinterminal="no" # Check whether x11docker runs in interactive bash mode (--enforce-i) case $- in *i*) Runsinteractive="yes" ;; *) Runsinteractive="no" ;; esac # Check whether ps can watch processes of other users mount | grep "^proc" | grep -q "hidepid=2" && { Hosthidepid="yes" debugnote "check_host(): /proc is mounted with hidepid=2." } || { Hosthidepid="no" } ps aux | cut -d' ' -f1 | grep -q root && { Hostcanwatchroot="yes" } || { Hostcanwatchroot="no" case "$Winsubsystem" in MSYS2|CYGWIN) Hostcanwatchroot="yes" ;; esac } debugnote "check_host(): ps can watch root processes: $Hostcanwatchroot" # Check if host uses proprietary NVIDIA driver Nvidiaversion=$(head -n1 2>/dev/null /dev/null 2>&1 || { [ -f /etc/passwd ] || warning "Your system misses /etc/passwd" error "Could not find user '$Hostuser' in /etc/passwd." } Hostuser="$(id -un "$Hostuser")" Hostuseruid="$(id -u "$Hostuser")" Hostusergid="$(id -g "$Hostuser")" [ "$Hostuser" = "$Startuser" ] && Hostuserhome="$HOME" # How to run as unprivileged user in unpriv() case "$Hostuser" in "$Startuser") Unpriv="eval" ;; # alternatively: bash -c *) Unpriv="su $Hostuser -c" ;; esac [ -z "$Hostuserhome" ] && Hostuserhome=$(mygetent passwd "$Hostuser" 2>/dev/null | cut -d: -f6) [ -z "$Hostuserhome" ] && { Hostuserhome="/tmp/home/$Hostuser" mkfolder "$Hostuserhome" warning "Could not read your home directory from /etc/passwd for user '$Hostuser'. Please set \$HOME with a valid path. Fallback: setting HOME=$Hostuserhome" check_fallback } debugnote "host user: $Hostuser $Hostuseruid:$Hostusergid $Hostuserhome" [ "$Hostuser" = "root" ] && warning "Running as user root. Maybe \$(logname) did not provide an unprivileged user. Please use option --hostuser=USER to specify an unprivileged user. Otherwise, new X server runs as root, and container user will be root." id | grep -q "(docker)" && warning "User $Hostuser is member of group docker. That allows unprivileged processes on host to gain root privileges." Localsharedir="$Hostuserhome/.local/share/x11docker" mkfolder "$Localsharedir" 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 [ -n "$(cut -d: -f1 -s <<< "$Hostdisplay")" ] && Hostxoverip="yes" || Hostxoverip="no" #Hostdisplay="$(sed "s/localhost/$Hostip/" <<< "$Hostdisplay")" # get cookie from host display XAUTHORITY=${XAUTHORITY:-} [ -z "$XAUTHORITY" ] && [ -e "$Hostuserhome/.Xauthority" ] && XAUTHORITY="$Hostuserhome/.Xauthority" #[ -z "$XAUTHORITY" ] && command -v systemctl >/dev/null && XAUTHORITY="$(systemctl --user show-environment | grep XAUTHORITY= | cut -d= -f2)" [ "${XAUTHORITY:-}" ] && { command -v xauth >/dev/null && { xauth -n -i -f "${XAUTHORITY:-}" nlist "$Hostdisplay" 2>/dev/null | sed -e 's/^..../ffff/' | unpriv "xauth -n -i -f '$Hostxauthority' nmerge - 2>/dev/null" } || { unpriv "cp '${XAUTHORITY:-}' '$Hostxauthority'" debugnote "check_hostxenv(): xauth not found or failed. Host cookie not set to ffff." } chown "$Hostuser" "$Hostxauthority" chmod 600 "$Hostxauthority" export XAUTHORITY } || { Hostxauthority="" XAUTHORITY="" } [ "$Hostdisplay" ] || { Hostxsocket="" Hostxauthority="" DISPLAY="" XAUTHORITY="" } [ -s "${XAUTHORITY:-}" ] && [ ! -s "$Hostxauthority" ] && cp "${XAUTHORITY:-}" "$Hostxauthority" # create $Hostxenv Hostxenv="DISPLAY=$Hostdisplay" [ -s "$Hostxauthority" ] && { Hostxenv="$Hostxenv XAUTHORITY=$Hostxauthority" } || { Hostxauthority= XAUTHORITY="" } [ -n "$Hostxsocket" ] && Hostxenv="$Hostxenv XSOCKET=$Hostxsocket" [ -n "$Hostwaylandsocket" ] && Hostxenv="$Hostxenv WAYLAND_DISPLAY=$Hostwaylandsocket" Hostxenv="$Hostxenv XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" [ -n "$Hostdisplay" ] && [ -z "$Hostxauthority" ] && warning "Your host X server runs without cookie authentication." [ -z "$GDK_BACKEND" ] && { [ -n "$Hostwaylandsocket" ] && export GDK_BACKEND="wayland" [ -n "$Hostdisplay" ] && export GDK_BACKEND="x11" [ -z "$Hostdisplay$Hostwaylandsocket" ] && unset GDK_BACKEND } return 0 } check_snap() { # check if docker is installed in snap. Causes restrictions. # Check if docker is installed with snap/snappy (notable Ubuntu Server) myrealpath "$(command -v "${Backendbin:-docker_not_found}")" | grep -q snap && Runsinsnap="yes" || Runsinsnap="no" [ "$Runsinsnap" = "yes" ] && [ -z "$Snapsupport" ] && { note "It seems docker runs in snap. This limits possibilities to use docker and x11docker. Fallback: Enabling option --snap" Snapsupport="yes" check_fallback } [ -d "/snap/docker" ] && [ "$Snapsupport" = "no" ] && note "Detected /snap/docker. If you run Docker in snap, you might need option --snap to support this setup." [ "$Snapsupport" = "yes" ] && { note "Option --snap to support a docker-in-snap setup causes some restrictions: Option --newprivileges=yes is enabled, snap needs it for unknown reasons. Option --xoverip is enabled because snap cannot access X unix sockets in /tmp. Option --network is enabled because --xoverip is not possible otherwise. Option --hostdisplay is not available because it would need a shared unix socket. Option --gpu only works with --xorg and with indirect rendering (--gpu=iglx). Options --printer and --pulseaudio only work in TCP mode. Option --xc to run X servers in container with x11docker/xserver is not possible. Recommendation: Purge the Docker snap installation and install Docker natively." [ "$Allownewprivileges" = "auto" ] && Allownewprivileges="yes" Xoverip="${Xoverip:-yes}" Xcontainer="no" [ "$Network" = "none" ] && Network="" case "$Sharegpu" in yes|virgl|iglx|direct) Sharegpu="iglx" ;; esac } return 0 } check_xdg_runtime_dir() { # set up XDG_RUNTIME_DIR if needed [ -z "$XDG_RUNTIME_DIR" ] && [ -e "/run/user/${Hostuseruid:-unknownuid}" ] && export XDG_RUNTIME_DIR="/run/user/$Hostuseruid" case "$Xserver" in --weston|--kwin|--weston-xwayland|--kwin-xwayland) [ -z "$XDG_RUNTIME_DIR" ] && { export XDG_RUNTIME_DIR="$Cachefolder/XDG_RUNTIME_DIR" mkfolder "$XDG_RUNTIME_DIR" 0700 } ;; esac return 0 } create_cachefiles() { # create empty cache files owned by unprivileged user local Line # create base cache folder [ "$Cachebasefolder" ] || { #Cachebasefolder="$Hostuserhome/.cache/x11docker" ### FIXME really a good idea for MS Windows? WSL cache provides performance, but maybe must not be shared with container to avoid file access errors. case "$Winsubsystem" in ""|MSYS2|CYGWIN) Cachebasefolder="$Hostuserhome/.cache/x11docker" ;; WSL1|WSL2) case "$Mobyvm" in yes) Cachebasefolder="$(convertpath subsystem "$(wincmd "echo %userprofile%")")/x11docker/cache" mkfolder "$Hostuserhome/.cache/x11docker/symlink" [ -e "$Hostuserhome/.cache/x11docker/symlink" ] || ln -s -T "$Cachebasefolder" "$Hostuserhome/.cache/x11docker/symlink" mkfile "$Hostuserhome/.cache/x11docker/symlink/symlink.txt" echo "x11docker: With MobyVM x11docker cache in WSL is stored in $Cachebasefolder to allow file sharing with containers. A symbolic link is created in WSL at $Hostuserhome/.cache/x11docker/symlink " >> "$Hostuserhome/.cache/x11docker/symlink/symlink.txt" ;; no) Cachebasefolder="$Hostuserhome/.cache/x11docker" ;; esac ;; esac } [ "$Cachebasefolder" = "/x11docker/cache" ] && error "Failed to find a valid path for cache directory. Please report at https://github.com/mviereck/x11docker As a workaround you can specify a cache folder with --cachebasedir='DIR'" Cachebasefolder="$(convertpath subsystem "$Cachebasefolder")" [ "$Cachebasefolder" != "${Cachebasefolder//" "/""}" ] && error "Cache root folder must not contain whitespaces. $Cachebasefolder" mkfolder "$Cachebasefolder" || error "Could not create cache folder $Cachebasefolder" writeaccess "$Hostuseruid" "$Cachebasefolder" || error "User $Hostuser does not have write access to cache folder $Cachebasefolder" # Create cache subfolders Cachefolder="$Cachebasefolder/$Cachenumber-$(unspecialstring "$(basename "$Imagename")")" [ -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" mkfolder "$Sharefolder" # Files in $Cachefolder: host only access Backendcommandfile="$Cachefolder/$Backendcommandfile" && mkfile $Backendcommandfile Hostxauthority="$Cachefolder/$Hostxauthority" && mkfile $Hostxauthority Messagelogfile="$Cachefolder/$Messagelogfile" && mkfile $Messagelogfile Pulseaudioconf="$Cachefolder/$Pulseaudioconf" && mkfile $Pulseaudioconf Storepidfile="$Cachefolder/$Storepidfile" && mkfile $Storepidfile Watchpidfifo="$Cachefolder/$Watchpidfifo" Xservercookie="$Cachefolder/$Xservercookie" && mkfile $Xservercookie Xtermrc="$Cachefolder/$Xtermrc" && mkfile $Xtermrc mkfolder "$Cachefolder/tmp/.X11-unix" mkfolder "$Cachefolder/XDG_RUNTIME_DIR" 0700 # Files in $Sharefolder: shared to /x11docker in container Clipboardrc="$Sharefolder/$Clipboardrc" && mkfile $Clipboardrc Cmdrc="$Sharefolder/$Cmdrc" && mkfile $Cmdrc Cmdstderrlogfile="$Sharefolder/$Cmdstderrlogfile" && mkfile $Cmdstderrlogfile 666 Cmdstdinfifo="$Sharefolder/$Cmdstdinfifo" Cmdstdoutlogfile="$Sharefolder/$Cmdstdoutlogfile" && mkfile $Cmdstdoutlogfile 666 Compositorlogfile="$Sharefolder/$Compositorlogfile" && mkfile $Compositorlogfile Containerrc="$Sharefolder/$Containerrc" && mkfile $Containerrc Containerenvironmentfile="$Sharefolder/$Containerenvironmentfile" && mkfile $Containerenvironmentfile 666 Containerlocaltimefile="$Sharefolder/$Containerlocaltimefile" Containerlogfile="$Sharefolder/$Containerlogfile" && mkfile $Containerlogfile 666 Containerrootrc="$Sharefolder/$Containerrootrc" && mkfile $Containerrootrc Logfile="$Sharefolder/x11docker.log" && mkfile $Logfile 666 Messagefifo="$Sharefolder/$Messagefifo" Nxagentclientrc="$Sharefolder/$Nxagentclientrc" && mkfile $Nxagentclientrc Nxagentkeysfile="$Sharefolder/$Nxagentkeysfile" && mkfile $Nxagentkeysfile Nxagentoptionsfile="$Sharefolder/$Nxagentoptionsfile" && mkfile $Nxagentoptionsfile Pulseaudiocookie="$Sharefolder/$Pulseaudiocookie" Pulseaudiosocket="$Sharefolder/$Pulseaudiosocket" Storeinfofile="$Sharefolder/$Storeinfofile" && mkfile $Storeinfofile 666 Systemdjournallogfile="$Sharefolder/$Systemdjournallogfile" && mkfile $Systemdjournallogfile Timetosaygoodbyefile="$Sharefolder/$Timetosaygoodbyefile" && mkfile $Timetosaygoodbyefile 666 Timetosaygoodbyefifo="$Sharefolder/$Timetosaygoodbyefifo" Westonini="$Sharefolder/$Westonini" && mkfile $Westonini Xclientcookie="$Sharefolder/$Xclientcookie" && mkfile $Xclientcookie Xkbkeymapfile="$Sharefolder/$Xkbkeymapfile" && mkfile $Xkbkeymapfile Xinitlogfile="$Sharefolder/$Xinitlogfile" && mkfile $Xinitlogfile 666 Xinitrc="$Sharefolder/$Xinitrc" && mkfile $Xinitrc Xpraclientlogfile="$Sharefolder/$Xpraclientlogfile" && mkfile $Xpraclientlogfile Xpraserverlogfile="$Sharefolder/$Xpraserverlogfile" && mkfile $Xpraserverlogfile case "$Backend" in proot) ln -s "$Storepidfile" "$Sharefolder/store.pids" ;; esac # Files in $Cachebasefolder Logfilebackup="$Cachebasefolder/x11docker.log" Modelinefilebasepath="$Cachebasefolder/$Modelinefilebasepath" && mkfolder "$Modelinefilebasepath" # file to store display numbers in use today Numbersinusefile="$Cachebasefolder/$Numbersinusefile" for Line in $(find $Cachebasefolder/displaynumbers.* 2>/dev/null ||:) ; do [ "$Line" != "$Numbersinusefile" ] && rm "$Line" done [ -e "$Numbersinusefile" ] || mkfile "$Numbersinusefile" # libc timezone file [ -e "$Hostlocaltimefile" ] && cp "$Hostlocaltimefile" "$Containerlocaltimefile" storeinfo "cache=$Cachefolder" storeinfo "stdout=$Cmdstdoutlogfile" storeinfo "stderr=$Cmdstderrlogfile" return 0 } setup_fifo() { # set up fifo channels (also option --stdin) # setup fifos to allow messages from within container and xinitrc # and to send pids to watch to watchpidlist() thread # file descriptors in use: # FDstderr stderr for warnings and notes redirected to &2, with --silent redirected to /dev/null # FDmessage $Messagefifo for messages from other threads to watchmessagefifo() # FDcmdstdin stdin>>$Cmdstinfile --stdin with catstdin, redirection of &0 # FDtimetosaygoodbye $Timetosaygoodbyefifo for saygoodbye() and waitfortheend() # FDwatchpid $Watchpidfifo for watchpidlist() case "$Mobyvm" in yes) Usemkfifo="no" ;; no) Usemkfifo="yes" ;; esac [ "$Runtime" = "kata-runtime" ] && Usemkfifo="no" #[ "$Runtime" = "sysbox-runc" ] && Usemkfifo="no" # redirect stdin to named pipe. Named pipe is shared with container and used as stdin of container command in containerrc [ "$Forwardstdin" = "yes" ] && { case "$Usemkfifo" in yes) unpriv "mkfifo '$Cmdstdinfifo'" ;; no) mkfile "$Cmdstdinfifo" ;; esac #exec {FDcmdstdin}<>$Cmdstdinfifo #cat <&0 >>${FDcmdstdin} & storepid $! catstdin cat <&0 >>"$Cmdstdinfifo" & storepid $! catstdin storeinfo "stdin=$Cmdstdinfifo" } case "$Usemkfifo" in yes) unpriv "mkfifo '$Watchpidfifo'" unpriv "mkfifo '$Messagefifo' && chmod 666 '$Messagefifo'" unpriv "mkfifo '$Timetosaygoodbyefifo'" ;; no) # Windows, kata mkfile "$Watchpidfifo" mkfile "$Messagefifo" 666 mkfile "$Timetosaygoodbyefifo" 666 ;; esac # used by waitfortheend() exec {FDtimetosaygoodbye}<>"$Timetosaygoodbyefifo" # start watching important pids, e.g. xinit, container. exec {FDwatchpid}<>"$Watchpidfifo" watchpidlist & storepid $! watchpidlist # start watching for messages out of container exec {FDmessage}<>"$Messagefifo" watchmessagefifo & storepid $! watchmessagefifo return 0 } setup_verbosity() { # options --verbose, --stdout, --stderr local Line Logfiles # create summary logfile Logfiles=" $Cmdstderrlogfile $Cmdstdoutlogfile $Compositorlogfile $Containerlogfile $Systemdjournallogfile $Messagelogfile $Xinitlogfile $Xpraclientlogfile $Xpraserverlogfile " [ -n "$Verbose" ] && [ "$Verbose" != "yes" ] && Logfiles="$(grep -E "$Verbose" <<< "$Logfiles")" # unofficial hack to optionally reduce log output for Line in $Logfiles; do [ -e "$Line" ] && grep -q "/" <<< "$Line" && Logfiles="$Logfiles $Line" done { trap '' SIGINT tail --pid="$$" --retry -n +1 -F $Logfiles 2>/dev/null >>"$Logfile" ||: } & # option --verbose [ "$Verbose" ] && { trap '' SIGINT case "$Verbosecolors" in no) tail --pid="$$" --retry -n +1 -F "$Logfile" 2>/dev/null >&${FDstderr} ;; yes) tail --pid="$$" --retry -n +1 -F "$Logfile" 2>/dev/null | sed " /\(Failed to add fd to store\|Failed to set invocation ID\|Failed to reset devices.list\)/d; s/\(ERROR\|Error\|error\|FAILURE\|FATAL\|Fatal\|fatal\)/${Colredbg}\1${Colnorm}/g; s/\(Failed\|failed\|Failure\|failure\)/${Colred}\1${Colnorm}/g; s/\(WARNING\|Warning\|warning\)/${Colyellow}\1${Colnorm}/g; s/\(DEBUGNOTE\)/${Colblue}\1${Colnorm}/g; s/^==>.*/${Coluline}\0${Colnorm}/; s/\(Starting\|Activating\)/${Colgreen}\0${Colnorm}/; s/\(Started\|Reached target\|activated\)/${Colgreenbg}\0${Colnorm}/; s/^\(+\|++\|+++\)/${Colgreenbg}\0${Colnorm}/ ; s/^x11docker/${Colgreen}\0${Colnorm}/ " >&${FDstderr} ;; esac } & [ "$Showcontaineroutput" = "yes" ] && { { waitforlogentry tailstdout "$Storeinfofile" "x11docker=ready" "" infinity ||: trap '' SIGINT tail --pid="$$" -n +1 -f "$Cmdstdoutlogfile" 2>/dev/null ||: } & { waitforlogentry tailstderr "$Storeinfofile" "x11docker=ready" "" infinity ||: trap '' SIGINT tail --pid="$$" -n +1 -f "$Cmdstderrlogfile" >&2 2>/dev/null ||: } & } return 0 } #### options parse_options() { # parse cli options local Shortoptions Longoptions Parsedoptions Presetoptions Presetfile Shortoptions="aAcdDfFghHiIKlmnpPqtTvVwWxXyY" Shortoptions="${Shortoptions}e" # deprecated Longoptions="$Longoptions,auto,desktop,tty,wayland,wm::,xc::,xonly" # Influencing auto-setup of X/Wayland/x11docker Longoptions="$Longoptions,hostdisplay,nxagent,runx,xephyr,xpra,xpra2,xorg,xvfb,xwin" # X servers Longoptions="$Longoptions,weston-xwayland,xpra-xwayland,xpra2-xwayland,xwayland" # X servers depending on a Wayland compositor Longoptions="$Longoptions,hostwayland,kwin,weston" # Wayland compositors without X Longoptions="$Longoptions,border::,dpi:,fullscreen,output-count:,rotate:,scale:,size:,xfishtank" # X/Wayland appearance options Longoptions="$Longoptions,clean-xhost,composite::,display:,keymap:,vt::,westonini:,xauth::,xhost::,xoverip::,xtest::" # X/Wayland config Longoptions="$Longoptions,checkwindow::,fallback::,preset:,pull::" # x11docker config Longoptions="$Longoptions,cachebasedir:,home::,homebasedir:,share:" # Host folders Longoptions="$Longoptions,alsa::,clipboard::,gpu::,lang::,printer::,pulseaudio::,webcam" # Host integration features Longoptions="$Longoptions,backend:,env:,mobyvm,name:,no-entrypoint,no-setup,rootfs,rootless::,runtime:,snap,workdir:" # Container config Longoptions="$Longoptions,cap-default,ipc::,limit::,newprivileges::,network::" # Container capabilities Longoptions="$Longoptions,group-add:,hostuser:,password::,sudouser::,user:,shell:" # Container user Longoptions="$Longoptions,dbus::,init::,hostdbus,sharecgroup" # Container init and DBus Longoptions="$Longoptions,stdin,interactive" # Container interaction Longoptions="$Longoptions,runasuser:,runfromhost:,runasroot:" # Additional commands to execute Longoptions="$Longoptions,printenv::,printid::,printinfofile::,printpid1::" # Output of vars on stdout Longoptions="$Longoptions,debug,printcheck,quiet,verbose::" # Verbose options Longoptions="$Longoptions,build,cleanup,help,launcher,licence,license,version" # Special options without starting X or container Longoptions="$Longoptions,install,remove,remove-oldprefix,update::,update-master::" # Installation # Longoptions="$Longoptions,experimental,keepcache,remountcgroup,xopt:,xorgconf:" # Experimental Longoptions="$Longoptions,dbus-system,enforce-i,exe,homedir:,hostipc,hostnet,iglx,kwin-xwayland" # Deprecated Longoptions="$Longoptions,no-auth,no-internet,no-xhost" # Deprecated Longoptions="$Longoptions,sharedir:,sharessh,systemd,showenv,showid,showinfofile,showpid1" # Deprecated Longoptions="$Longoptions,cachedir:,no-init,nothing,no-xtest,openrc,podman,pull::,ps,pw::,runit,silent,starter" # Removed Longoptions="$Longoptions,stderr,stdout,sys-admin,sysvinit,tini,trusted,untrusted,vcxsrv,xdummy" # Removed # default preset files parse_preset "default" ||: # options from cli Parsedoptions="$(getopt --options "$Shortoptions" --longoptions "$Longoptions" --name "$0" -- "$@" )" || error "Failed to parse options." eval set -- "$Parsedoptions" Parsedoptions_global="$Parsedoptions_global () $Parsedoptions" [ "$*" = "-h --" ] && usage && exit 0 # Catch single -h for usage info, otherwise it means --hostdisplay [ "$*" = "--" ] && usage && exit 0 # x11docker without options while [ $# -gt 0 ]; do Optionset="$Optionset ${1:-}" 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 --xonly) Backend="host" # Only create X server Containercommand="sleep infinity" Showdisplayenvironment="${Showdisplayenvironment:-yes}" ;; --preset) # Predefined option sets parse_preset "${2:-}" || error "Option --preset: File not found: ${2:-} Searching in: $Presetdirlocal $Presetdirsystem" shift ;; #### Choice of X servers and Wayland compositors --auto) Autochooseserver="yes" ;; # Default: auto-choose X server or Wayland compositor -h|--hostdisplay) Xserver="--hostdisplay" ;; # Host display :0 with shared X socket -H|--hostwayland) Xserver="--hostwayland" ;; # Host wayland. Allows coexistence with option -K|--kwin) Xserver="--kwin" ;; # KWin, Wayland only -n|--nxagent) Xserver="--nxagent" ;; # nxagent --runx) Xserver="--runx" ;; # MS Windows: Will be Xwin or VcXsrv -t|--tty) Xserver="--tty" ;; # Do not provide any X nor Wayland -T|--weston) Xserver="--weston" ;; # Weston, Wayland only -Y|--weston-xwayland) Xserver="--weston-xwayland" ;; # Weston + Xwayland -y|--xephyr) Xserver="--xephyr" ;; # Xephyr -a|--xpra) Xserver="--xpra" ;; # xpra --xpra2) Xserver="--xpra2" ;; # xpra server in container and client on host -A|--xpra-xwayland) Xserver="--xpra-xwayland" ;; # Xpra with vfb Xwayland --xpra2-xwayland) Xserver="--xpra2-xwayland" ;; # Xpra with vfb Xwayland -x|--xorg) Xserver="--xorg" ;; # Xorg --xvfb) Xserver="--xvfb" ;; # Xvfb. Invisible on host. -X|--xwayland) Xserver="--xwayland" ;; # Xwayland on already running Wayland --xwin) Xserver="--xwin" ;; # XWin, MS Windows only #### Influencing automatic choice of X server or Wayland compositor -d|--desktop) Desktopmode="yes" ;; # image contains a desktop environment. -g) Sharegpu="yes" ;; # GPU access --gpu) Sharegpu="${2:-yes}" ; shift ;; # GPU access -W|--wayland) Setupwayland="yes" ;; # set up wayland environment, regards --desktop -w) Windowmanagermode="auto" ; Desktopmode="yes" ;; --wm) case "${2:-}" in # choose window manager "n"|"none"|"no") Windowmanagermode="none" ;; "host") Windowmanagermode="host" ;; ""|"auto"|"m") Windowmanagermode="auto" ;; *) Windowmanagermode="auto"; Windowmanagercommand="${2:-}" ;; esac shift ; Desktopmode="yes" ;; --xc) Xcontainer="${2:-yes}" ; shift ;; # Run X server in container #### X and Wayland appearance --border) Xpraborder="${2:-"blue,1"}"; shift ;; # Colored border for xpra clients --dpi) Dpi=${2:-} ; shift ;; # Dots per inch. Influences font size -f|--fullscreen) Fullscreen="yes" ;; # Fullscreen mode for Xephyr and Weston --output-count) Outputcount="${2:-}" ; shift ;; # Number of virtual outputs --rotate) Rotation=${2:-} ; shift ;; # Rotation and mirroring --scale) Scaling=${2:-} ; shift ;; # Zoom --size) Screensize="${2:-}" ; shift ;; # Screen size -F|--xfishtank) Xfishtank="yes" ;; # Run xfishtank on new X server #### X and Wayland configuration --composite) Xcomposite="${2:-yes}" ; shift ;; # Enable or disable X extension COMPOSITE --display) Newdisplaynumber="${2:-}" # Display number to use for new X server or Wayland compositor [ "$(cut -c1 <<< "$Newdisplaynumber")" = ":" ] && Newdisplaynumber="$(cut -c2- <<< "$Newdisplaynumber")" shift ;; --keymap) Xkblayout="${2:-}" ; shift ;; # Keymap layout for xkbcomp. Compare /usr/share/X11/xkb/symbols --vt) Newxvt="${2:-auto}" ; shift ;; # Virtual console to use for --xorg --xoverip) Xoverip="${2:-yes}" ; shift ;; # Use X over TCP/IP instead of sharing X socket --xtest) Xtest="${2:-yes}" ; shift ;; # X extension XTEST --westonini) Customwestonini="${2:-}" ; shift ;; # Custom weston.ini #### X Authentication --clean-xhost|--no-xhost) Cleanxhost="yes" # Disable xhost credentials on host X [ "${1:-}" = "--no-xhost" ] && note "Option --no-xhost is deprecated. Please use --clean-xhost instead." ;; --xauth) Xauthentication="${2:-yes}" ; shift ;; # X cookie settings --xhost) Xhost="${2:-auto}" ; shift ;; # Custom xhost setting on new X server #### Host integration options --alsa) Sharealsa="yes" # ALSA sound (shares /dev/snd) Alsacard="${2:-$Alsacard}" ; shift ;; -c) Shareclipboard="yes" ;; # Clipboard sharing --clipboard) Shareclipboard="${2:-yes}" ; shift ;; # Clipboard sharing -l) Langwunsch="$Langwunsch ${LANG:-}" # Locale/language setting Langwunsch="${Langwunsch:-$LC_ALL}" [ "$Langwunsch" ] || note "Option --lang: Environment variable \$LANG is empty. Please specify desired language locale with e.g. --lang=en_US or --lang=zh_CN." ;; --lang) Langwunsch="$Langwunsch ${2:-${LANG:-}}" ; shift # Locale/language setting Langwunsch="${Langwunsch:-$LC_ALL}" [ "$Langwunsch" ] || note "Option --lang: Environment variable \$LANG is empty. Please specify desired language locale with e.g. --lang=en_US or --lang=zh_CN." ;; -P|--printer) Sharecupsmode="${2:-auto}" ; shift ;; # Printer sharing with CUPS -p) Pulseaudiomode="auto" ;; # Pulseaudio sound --pulseaudio) Pulseaudiomode="${2:-auto}"; shift ;; # Pulseaudio sound --webcam) Sharewebcam="yes" ;; # Webcam sharing #### Special options --checkwindow) Checkforopenwindow="${2:-yes}" ; shift ;; # Run container until all windows are closed --fallback) Fallback="${2:-yes}" ; shift ;; # Allow/deny fallbacks for impossible options -i|--interactive) Interactive="yes" ;; # Interactive terminal --runasroot) Runasroot="$Runasroot ${2:-}" ; shift ;; # Add custom root command in container setup script --runasuser) Runasuser="$Runasuser ${2:-}" shift ;; # Add custom user command in cmdrc --runfromhost) Runfromhost="$Runfromhost ${2:-}" ; shift ;; # Add custom host command in xinitrc #### User settings --group-add) Containerusergroups="$Containerusergroups ${2:-}" ; shift ;; # Additional groups for container user --hostuser) Hostuser="${2:-}" ; shift ;; # Set host user different from logged in user --password) Containeruserpassword="${2:-INTERACTIVE}" ; shift ;; # Change encrypted password in ~/.config/x11docker/passwd --shell) Containerusershell="${2:-}" ; shift ;; # Set preferred user shell --sudouser) Sudouser="${2:-yes}" ; shift ;; # su and sudo for container user with password x11docker --user) Containeruser="${2:-}" ; shift ;; # Set container user other than host user #### Init system and DBus --dbus) Dbusrunsession="${2:-yes}" ; shift ;; # DBus in container, Default: user session, =system: with system daemon --hostdbus) Sharehostdbus="yes" ;; # Connect to host DBus --init) Initsystem="${2:-tini}" ; shift ;; # init in container --sharecgroup) Sharecgroup="yes" ;; # Share /sys/fs/cgroup. Default for --init=systemd, possible use with --init=openrc or elogind. --systemd) Initsystem="systemd" ; note "Option --systemd is deprecated. Please use: --init=systemd" ;; #### Container configuration --backend) Backend="${2:-}" ; shift ;; # container backend to use: docker, podman, nerdctl, others --cap-default) Capdropall="no" ;; # Don't use --cap-drop=ALL --security-opt=no-new-privileges --env) store_runoption env "${2:-}" # Set container environment variables shift ;; --ipc) Shareipc="${2:-host}" ; shift ;; --limit) Limitresources="${2:-0.5}" ; shift ;; # Limited CPU and RAM access --mobyvm) Mobyvm="yes" ;; # Use MobyVM in WSL2 --name) Containername="${2:-}" ; shift ;; # Set container name -I) Network="" ;; --network) Network="${2:-}" ; shift ;; # Enable internet access --newprivileges) Allownewprivileges="${2:-yes}" ; shift ;; # [Don't] set --security-opt=no-new-privileges --no-entrypoint) Noentrypoint="yes" ;; # Disable ENTRYPOINT of image --no-setup) Containersetup="no" ;; # No setup of x11docker inside of container (notable disables containerrootrc() ) --rootfs) Podmanrootfs="yes" ;; # run a rootfs in posman instead of image --rootless) Backendrootless="${2:-yes}"; shift ;; --runtime) Runtime="${2:-}" ; shift # Runtime=runc|nvidia|kata-runtime|crun [ "$Runtime" = "kata" ] && Runtime="kata-runtime" [ "$Runtime" = "sysbox" ] && Runtime="sysbox-runc" ;; --snap) Snapsupport="yes" ;; # snap fallback mode --stdin) Forwardstdin="yes" ;; # Forward stdin to container command --workdir) Workdir="${2:-}" ; shift ;; # Set working directory #### host folders and docker volumes -m) Sharehome="host" ;; --home|--homedir) Sharehome="yes" # Share host folder as HOME in container, ~/x11docker/imagename or $2 [ "${1:-}" = "--homedir" ] && note "Option --homedir is deprecated. Please use --home=DIR instead." Persistanthomevolume="${2:-}" ; shift ;; --share|--sharedir) store_runoption volume "${2:-}" # Share host file, device or directory [ "${1:-}" = "--sharedir" ] && note "Option --sharedir is deprecated. Please use option --share=PATH instead." shift ;; --homebasedir) Hosthomebasefolder="${2:-}" ; shift ;; # Set base folder for --home instead of ~/.local/share/x11docker --cachebasedir) Cachebasefolder="${2:-}" ; shift ;; # Set base folder for cache instead of ~/.cache/x11docker #### Verbosity options -D|--debug) Debugmode="yes" ;; # Debugging mode -v) Verbose="yes" ;; # Be verbose --verbose) Verbose="${2:-yes}" ; shift ;; # Be verbose -V) Verbose="${Verbose:-yes}"; Verbosecolors="yes";; # Be verbose with colored output -q|--quiet) Silent="yes" ;; # Do not show warnings or errors --printcheck) Printcheck="yes" ;; # Show dependency check messages --printenv) Showdisplayenvironment="${2:-yes}" ; shift ;; # Output of display number and cookie file on stdout. Catch with: read xenv < <(x11docker --printenv) --printid) Showcontainerid="${2:-yes}" ; shift ;; # Output of container id on stdout --printinfofile) Showinfofile="${2:-yes}" ; shift ;; # Show path to $Storeinfofile --printpid1) Showcontainerpid1pid="${2:-yes}" ; shift ;; # Output of host PID of container PID 1 #### Special options not starting X or docker --build) Buildimage="yes" ;; # Build an image from x11docker repository --cleanup) Cleanup="yes" ;; # Remove orphaned containers and cache files --install|--remove|--remove-oldprefix) Installermode="${1:-}" ;; # Installer --update|--update-master) Installermode="${1:-}" ; Installerarg="${2:-}" ; shift ;; # Installer --launcher) Createlauncher="yes" ;; # Create application launcher on desktop and exit #### Experimental options --experimental) Experimental="yes" ;; # Allow some experimental code that might be changed at any time --keepcache) Preservecachefiles="yes" ; note "Option --keepcache: experimental option." ;; --remountcgroup) Remountcgroup="yes" ; note "Option --remountcgroup: experimental option." ;; --xopt) Xserveroptions="${2:-}" ; shift ; note "Option --xopt: experimental option." ;; # Custom X server options --xorgconf) Xorgconf="${2:-}" ; shift ; note "Option --xorgconf: experimental option." ;; # Custom xorg.conf #### Deprecated options --dbus-system) note "Option --dbus-system is deprecated. Please use one of --init=systemd|openrc|runit|sysvinit instead. Possible but discouraged is --dbus=system. Fallback: Enabling options --dbus=system --cap-default" check_fallback Dbusrunsession="system" Capdropall="no" ;; -e|--exe) Backend="host" note "Option -e, --exe is deprecated. Please use --backend=host instead." ;; --enforce-i) note "Option --enforce-i is deprecated. Rather create a group weston-launch and add your user to it." ;; --hostipc) Shareipc="host" note "Option --hostipc is deprecated. Please use --ipc=host instead." ;; --hostnet) Network="host" note "Option --hostnet is deprecated. Please use --network=host instead." ;; --iglx) Sharegpu="iglx" ; note "Option --iglx is deprecated. Please use option --gpu=iglx instead." ;; --kwin-xwayland) Xserver="--kwin-xwayland" ; note "Option --kwin-xwayland is deprecated. Please use option --weston-xwayland instead." ;; --no-auth) Xauthentication="no" ; note "Option --no-auth is deprecated. Please use option --xauth=no instead." ;; --no-internet) Network="none" note "Option --no-internet is deprecated. Option --network=none is default now." ;; --sharessh) [ -e "${SSH_AUTH_SOCK:-}" ] && { # SSH socket sharing store_runoption volume "$(dirname "$SSH_AUTH_SOCK")" store_runoption env "SSH_AUTH_SOCK=$(escapestring "${SSH_AUTH_SOCK:-}")" } || note "Option --sharessh: environment variable \$SSH_AUTH_SOCK not set:" ; note "Option --sharessh is deprecated. Please use (directly or with help of option --preset): --share \$(dirname \$SSH_AUTH_SOCK) --env SSH_AUTH_SOCK=\"\$SSH_AUTH_SOCK\"" ;; --showenv) Showdisplayenvironment="yes" ; note "Option --showenv is deprecated. Please use option --printenv instead." ;; --showid) Showcontainerid="yes" ; note "Option --showid is deprecated. Please use option --printid instead." ;; --showinfofile) Showinfofile="yes" ; note "Option --showinfofile is deprecated. Please use option --printinfofile instead." ;; --showpid1) Showcontainerpid1pid="yes" ; note "Option --showpid1 is deprecated. Please use option --printpid1 instead." ;; #### Removed options --vcxsrv) error "Option --vcxsrv is no longer supported. Please use either option --xwin in Cygwin/X or use option --runx in WSL or MSYS2. For 'runx' look at: https://github.com/mviereck/runx" ;; --no-init|--openrc|--runit|--sysvinit|--tini) error "Option ${1:-} has been removed. Please use option --init=INITSYSTEM instead." ;; --cachedir|--nothing|--no-xtest|--podman|--ps|--pull|--pw|--silent|--starter|--stderr|--stdout|--sys-admin|--trusted|--untrusted) error "Option ${1:-} has been removed. Please have a look at 'x11docker --help' for possible replacements or search for '${1:-}' in /usr/share/doc/x11docker/CHANGELOG.md." ;; ##### Custom docker options / image name + container command. Everything after -- --) shift [ "$(cut -c1 <<< "${1:-}")" = "-" ] && grep -q " -- " <<< " $* " && { while [ $# -gt 0 ] ; do [ "${1:-}" = "--" ] && shift && break Customdockeroptions="$Customdockeroptions '${1:-}'" shift done } while [ $# -gt 0 ] ; do [ -n "${1:-}" ] && [ -z "$Imagename" ] && [ "$(echo "${1:-}" | cut -c1)" = "-" ] && Customdockeroptions="$Customdockeroptions ${1:-}" [ -n "${1:-}" ] && [ -z "$Imagename" ] && [ "$(echo "${1:-}" | cut -c1)" != "-" ] && Imagename="${1:-}" && shift [ -n "${1:-}" ] && [ -n "$Imagename" ] && Containercommand="$Containercommand '${1:-}'" shift done ;; '') ;; *) error "Unknown option ${1:-} Parsed options: $Parsedoptions" ;; esac shift done # Generate a list of options in use for Line in -a,--xpra -A,--xpra-xwayland -c,--clipboard -d,--desktop -D,--debug -f,--fullscreen -g,--gpu -e,--exe -F,--xfishtank -h,--hostdisplay -H,--hostwayland -i,--interactive -I,--network -K,--kwin -l,--lang -m,--home -n,--nxagent -p,--pulseaudio -P,--printer -q,--quiet -t,--tty -T,--weston -v,--verbose -w,--wm -W,--wayland -x,--xorg -X,--xwayland -y,--xephyr -Y,--weston-xwayland; do grep -q -x -- "$(cut -d, -f1 <<< "$Line")" <<< "$Optionset" && Optionset="$Optionset $(cut -d, -f2 <<< "$Line")" done Optionset="$(grep -- '--' <<< "$Optionset" | sort | uniq)" Optionset="$(grep -v -x -- '--' <<< "$Optionset")" for Line in $(tr "," "\n" <<< "$Longoptions"); do Optionsetall="$Optionsetall $(sed 's/^/--/g ; s/://g' <<< "$Line")" done Optionsetall="$(grep . <<< "$Optionsetall")" return 0 } parse_preset() { # nested parsing for --preset local Presetfile Presetoptions # file already parsed? Avoid a loop grep -q -x "${1:-}" <<< "$Presetlist" && return 0 Presetlist="$Presetlist ${1:-}" # check global and local preset dir. Prefer local one. [ -f "$Presetdirsystem/${1:-}" ] && Presetfile="$Presetdirsystem/${1:-}" [ -f "$Presetdirlocal/${1:-}" ] && Presetfile="$Presetdirlocal/${1:-}" [ -f "$Presetfile" ] || return 1 # parse Presetoptions="$(sed '/^#/d' < "$Presetfile" | tr '\n' ' ')" [ -n "$(tr -d ' ' <<< "$Presetoptions")" ] && { note "Option --preset: Parsing $Presetfile: $Presetoptions" eval parse_options "$Presetoptions" # eval to preserve whitespace in arguments } } check_options_arguments() { # check for [likely] valid arguments # not checked here yet: # --home # --cachebasedir # --alsa # --network # --lang # --ipc # --border # --wm # --xhost # --group-add ## --hostuser ## --password ## --user # --env ## --shell ## --workdir # --print* # --preset ## --runasroot ## --runfromhost ## --runasuser # --runtime # --fallback case "$Fallback" in yes|no) ;; *) error "Option --fallback: Unknown argument '$Fallback'" ;; esac # CUSTOM_RUN_OPTIONS Customdockeroptions="${Customdockeroptions//"--cap-add "/"--cap-add="}" Customdockeroptions="${Customdockeroptions//"--runtime "/"--runtime="}" Customdockeroptions="${Customdockeroptions//"--ipc "/"--ipc="}" Customdockeroptions="${Customdockeroptions//"--network "/"--network="}" Customdockeroptions="${Customdockeroptions//"--net "/"--network="}" Customdockeroptions="${Customdockeroptions//"--net="/"--network="}" Customdockeroptions="${Customdockeroptions//"--user "/"--user="}" grep -q -- "--runtime=kata-runtime" <<< "$Customdockeroptions" && Runtime="kata-runtime" grep -q -- "--runtime=nvidia" <<< "$Customdockeroptions" && Runtime="nvidia" grep -q -- "--runtime=runc" <<< "$Customdockeroptions" && Runtime="runc" grep -q -- "--runtime=crun" <<< "$Customdockeroptions" && Runtime="crun" grep -q -- "--runtime=sysbox-runc" <<< "$Customdockeroptions" && Runtime="sysbox-runc" # --backend [ -z "$Imagename" ] && { case "${Cleanup}${Createlauncher}${Installermode}" in "") # Only create X server / --xonly Codename="xonly" Backend="${Backend:-host}" Containercommand="sleep infinity" Showdisplayenvironment="${Showdisplayenvironment:-yes}" ;; esac } Backend="${Backend:-docker}" case "$Backend" in docker|podman|nerdctl|proot|host) ;; *) error "Option --backend: Unknown argument '$Backend'." ;; esac # --clipboard case "$Shareclipboard" in yes|no|oneway|superv|altv) ;; *) note "Option --clipboard: Unknown argument '$Shareclipboard'. Fallback: Disabling option --clipboard." check_fallback Shareclipboard="no" ;; esac # --dbus [=system] case "$Dbusrunsession" in yes|user|session) Dbusrunsession="yes" ;; no) ;; system) Dbusrunsession="yes" Dbussystem="yes" ;; *) note "Option --dbus: Unknown argument '$Dbusrunsession'. Fallback: Enabling --dbus user session." check_fallback Dbusrunsession="yes" ;; esac # --dpi [ "$Dpi" ] && { isnum "$Dpi" || { note "Option --dpi only accepts numeric values. Not numeric: '$Dpi' Fallback: Disaling option --dpi." check_fallback Dpi="" } } # --gpu case "$Sharegpu" in yes|no|direct|iglx|virgl) ;; *) note "Option --gpu: Unknown argument '$Sharegpu'. Fallback: Disabling option --gpu." check_fallback Sharegpu="no" ;; esac # --homebasedir [ "$Hosthomebasefolder" ] && { Hosthomebasefolder="$(convertpath subsystem "$Hosthomebasefolder")" [ -e "$Hosthomebasefolder" ] || error "Option --homebasedir: Specified path does not exist: $Hosthomebasefolder" } # --init case "$Initsystem" in tini|systemd|sysvinit|openrc|runit|dockerinit|s6-overlay) ;; no|none) Initsystem="none" ;; *) note "Option --init: Unknown init system $Initsystem Possible: tini systemd sysvinit openrc runit s6-overlay none Fallback: Using --init=tini instead." check_fallback Initsystem="tini" ;; esac # --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>$Xinitlogfile | grep -q MIT-SHM" && Hostmitshm="yes" || Hostmitshm="no" xtool --check --quiet xdpyinfo || Hostmitshm="yes" # if unknown, assume yes } # --clipboard case "$Shareclipboard" in yes|oneway|superv|altv) [ -z "$Hostdisplay" ] && { note "Option --clipboard: Did not find a host X display to share the clipboard with. DISPLAY is empty. Fallback: Disabling option --clipboard." check_fallback Shareclipboard="no" } ;; esac case "$Shareclipboard" in superv|altv) xtool --check xbindkeys || { note "Option --clipboard=$Shareclipboard needs xbindkeys. Fallback: Setting --clipboard=oneway." check_fallback Shareclipboard="oneway" } [ "$Xtest" = "yes" ] && warning "Options --xtest --clipboard=$Shareclipboard: X extension XTEST is enabled that allows container applications to fake key presses like SUPER+v and ALT+v so they could fool x11docker to provide the host clipboard content without user interaction." ;; esac case "$Shareclipboard" in oneway|superv|altv) case "$Xserver" in --hostdisplay|--xwin|--runx) note "Option --clipboard: Option $Xserver only supports arguments yes|no. Fallback: Disabling option --clipboard" check_fallback Shareclipboard="no" ;; --weston|--kwin) note "Option --clipboard=$Shareclipboard is not supported for $Xserver. $Xserver only supports a limited --clipboard=yes. Fallback: Disabling option --clipboard." check_fallback Shareclipboard="no" ;; esac ;; esac case "$Shareclipboard" in yes|oneway|superv|altv) case "$Xserver" in --tty|--hostwayland) note "Option --clipboard is not supported for $Xserver. Fallback: Disabling option --clipboard." check_fallback Shareclipboard="no" ;; *) xtool --check xclip || { note "Option --clipboard with $Xserver needs xclip. Fallback: Disabling option --clipboard." check_fallback Shareclipboard="no" } case "$Xserver" in --weston|--kwin) xtool --check wl-copy || { note "Option --clipboard with $Xserver needs wl-copy and wl-paste. Fallback: Disabling option --clipboard." check_fallback Shareclipboard="no" } ;; esac ;; esac ;; esac case "$Shareclipboard" in oneway|superv|altv) case "$Xserver" in --hostdisplay|--xwin|--runx) note "Option --clipboard: Option $Xserver only supports arguments yes|no. Fallback: Disabling option --clipboard" check_fallback Shareclipboard="no" ;; --weston|--kwin) note "Option --clipboard=$Shareclipboard is not supported for $Xserver. $Xserver only supports a limited variation of --clipboard=yes. Fallback: Disabling option --clipboard." check_fallback Shareclipboard="no" ;; esac ;; esac case "$Shareclipboard" in yes) case "$Xserver" in --weston|--kwin) note "Option --clipboard=$Shareclipboard with $Xserver is limited yet to copy from host to container. Middle-mouse-click selection is not supported at all." Shareclipboard="wayland" [ -z "$Hostdisplay" ] && { note "Option --clipboard with $Xserver is only supported along with an X server running on host. Fallback: Disabling option --clipboard." check_fallback Shareclipboard="no" } ;; esac ;; esac # X server case "$Xserver" in --xorg) # check if --xorg can be started by an unprivileged user [ -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" } # xrandr: --scale --size --rotate xtool --check xrandr || { { [ "$Scaling" ] || [ -n "$Rotation" ] || [ -n "$Screensize" ] ; } && { note "Option --xorg needs 'xrandr' for several options. Fallback: Disabling options --size, --scale and --rotate." check_fallback Screensize="" Rotation="" Scaling="" } } ;; --tty) [ "$Interactive" = "no" ] && { tput lines >/dev/null 2>&1 && { store_runoption env "LINES=$(tput lines)" store_runoption env "COLUMNS=$(tput cols)" } } check_optionset "--tty" "--border --clipboard --composite --desktop --display --dpi --fullscreen --keymap --output-count --rotate --scale --size --vt --wm --westonini --xauth --xc --xfishtank --xhost --xonly --xoverip --xtest" ||: ;; --hostdisplay) check_optionset "$Xserver" "--xtest --composite" Xtest="" Xcomposite="" [ "$Runsoverssh" = "yes" ] && { ### FIXME [ "$Network" != "host" ] && error "For SSH connection with option --hostdisplay x11docker would need insecure option --network=host. It is recommended to use another X server option like --xpra, --xephyr or --nxagent." } # MIT-SHM [ "$Hostmitshm" = "yes" ] && [ "$Shareipc" != "host" ] && [ "$Xoverip" = "no" ] && { case "$Xcontainer" in yes) [ -e "/lib/x86_64-linux-gnu/libdl.so.2" ] && { store_runoption env "LD_PRELOAD=/lib/x86_64-linux-gnu/libdl.so.2:$Sharefoldercontainer/XlibNoSHM.so" } || { store_runoption env "LD_PRELOAD=$Sharefoldercontainer/XlibNoSHM.so" } ;; no) case "$Sharegpu" in yes|direct) [ "$Xauthentication" != "trusted" ] && { note "Option --hostdisplay --gpu=direct without image x11docker/xserver (option --xc) would require insecure option --ipc=host to avoid MIT-SHM errors. (If you don't care for MIT-SHM errors, use --xauth=trusted.) Fallback: Disabling option --gpu." check_fallback Sharegpu="no" } ;; esac case "$Sharegpu" in no) case "$Xauthentication" in yes) note "Option --hostdisplay: To avoid MIT-SHM errors, x11docker sets uncomfortable option --xauth=untrusted to limit X access. If you don't care about MIT-SHM errors, set --xauth=trusted. Insecure and discouraged option --ipc=host avoids MIT-SHM errors. Recommendation: Provide image x11docker/xserver (option --xc) that contains a fake MIT-SHM library, or use another X server option like --nxagent." Xauthentication="untrusted" ;; esac ;; esac case "$Xauthentication" in trusted) note "Option --hostdisplay --xauth=trusted: MIT-SHM errors might occur." store_runoption env "QT_X11_NO_MITSHM=1" store_runoption env "_X11_NO_MITSHM=1" store_runoption env "_MITSHM=0" ;; esac ;; esac } ;; esac # --vt [ -n "$Newxvt" ] && { case "$Xserver" in --weston|--weston-xwayland) [ -n "$Hostdisplay" ] && error "Option $Xserver: Opening a TTY for weston on console while still running on X would break TTY switching. Please run nested without option --vt or switch to console first." ;; esac case "$Xserver" in --weston) [ "$Xcontainer" = "yes" ] && note "Option $Xserver: TTY switching will not work with weston on console running in a container (option --xc). Alternative: Run with --xc=no using weston from host." ;; esac } # window manager [ "$Desktopmode" = "no" ] && [ -z "$Windowmanagermode" ] && [ "$Xfishtank" = "no" ] && case "$Xserver" in --xephyr|--weston-xwayland|--kwin-xwayland|--xorg|--xwayland) note "Option $Xserver: x11docker assumes that you need a window manager. If you don't want this, run with option --desktop. Enabling option --wm to provide a window manager." Windowmanagermode="auto" [ "$Autochooseserver" = "yes" ] && [ "$Runsonconsole" = "no" ] && { case "$Sharegpu" in no) note "Did not find a nice solution to run a seamless application on your desktop. (Only insecure option --hostdisplay would work). It is recommended to install xpra or nxagent to allow a seamless mode without the need of a window manager. $Wikipackagesimage" ;; yes) note "Did not find a nice solution to run a seamless application with option --gpu on your desktop. (Only insecure option --hostdisplay would work). It is recommended to install xpra, weston, Xwayland and xdotool to allow a seamless mode without the need of a window manager. $Wikipackagesimage" ;; esac } ;; esac # --checkwindow [ -n "$Checkforopenwindow" ] && { xtool --check xwininfo || { note "Option --checkwindow needs 'xwininfo'. Fallback: Disabling option --checkwindow." check_fallback Checkforopenwindow="" } } # --fullscreen is nonsense on tty at all. Avoids weston error on tty. [ "$Runsonconsole" = "yes" ] || [ "$Newxvt" ] && Fullscreen="no" # --gpu --xoverip case "$Xoverip" in socat) store_runoption env "LIBGL_ALWAYS_SOFTWARE=1" case "$Sharegpu" in virgl|no) ;; yes) note "Options --gpu=yes --xoverip=socat: Enabling --gpu=virgl." Sharegpu="virgl" ;; direct|iglx) note "Option --gpu=$Sharegpu is not possible with --xoverip=socat. You can try --gpu=virgl instead. (Needs image x11docker/xserver, option --xc). Fallback: Setting option --gpu=virgl." check_fallback Sharegpu="virgl" ;; esac ;; esac # --gpu case "$Xoverip" in yes|listentcp) case "$Sharegpu" in virgl|iglx) ;; yes) case "$Xcontainer" in yes) Sharegpu="virgl" ;; no) Sharegpu="iglx" ;; esac note "Option --gpu with X over IP: Enabling indirect rendering with --gpu=$Sharegpu." ;; direct) note "Option --gpu=$Sharegpu with --xoverip is not supported. Try --gpu=virgl or --gpu=iglx instead. Fallback: Disabling option --gpu." check_fallback Sharegpu="no" ;; esac ;; esac case "$Sharegpu" in yes) case "$Xserver" in --weston|--kwin|--hostwayland) Sharegpu="direct" ;; --xorg|*xwayland|--hostdisplay) Sharegpu="direct" ;; --xwin|--runx) Sharegpu="iglx" ;; --tty) Sharegpu="direct" ;; *) case "$Xcontainer" in yes) Sharegpu="virgl" note "Option --gpu: Enabling option --gpu=virgl. Option --gpu=direct might be more performant, supported by: --xpra-xwayland, --weston-xwayland, --xwayland, --xorg, --hostdisplay" ;; no) note "Option --gpu=direct is not supported by $Xserver. Only --gpu=virgl would work, but needs image x11docker/xserver (option --xc). Options supporting direct rendering: --xpra-xwayland, --weston-xwayland, --xwayland, --xorg, --hostdisplay Fallback: Disabling option --gpu" check_fallback Sharegpu="no" ;; esac ;; esac ;; esac case "$Sharegpu" in iglx) store_runoption env "LIBGL_ALWAYS_INDIRECT=1" ;; virgl) case "$Xcontainer" in yes) case "$Backend" in docker|podman|nerdctl) store_runoption env "LIBGL_ALWAYS_SOFTWARE=1" ### FIXME rather belongs to setup_gpu() store_runoption env "GALLIUM_DRIVER=virpipe" ;; *) note "Option --gpu=virgl is supported with backends docker|podman|nerdctl only. Fallback: Disabling option --gpu=virgl." check_fallback Sharegpu="no" ;; esac ;; no) note "Option --gpu=virgl needs image x11docker/xserver (option --xc). Fallback: Disabling option --gpu=virgl." check_fallback Sharegpu="no" ;; esac ;; esac # --no-setup case "$Containersetup" in no) check_optionset "--no-setup" "--dbus --no-entrypoint --runasroot --runasuser" ||: [ "$Langwunsch" ] && store_runoption env "LANG=$Langwunsch" case "$Initsystem" in none|tini|dockerinit) ;; *) note "Option --no-setup: Option --init=$Initsystem is not supported. Fallback: Setting --init=tini" check_fallback Initsystem="tini" ;; esac [ -z "$Workdir" ] && [ "$Sharehome" != "no" ] && note "Option --no-setup: You might need to specify e.g. '--workdir=/tmp' or '--env HOME=/tmp' to allow proper functionality." [ -n "$Sudouser" ] && [ "$Containeruser" != "root" ] && note "Option --no-setup does not support option --sudouser. Fallback: Enabling needed container capabilities to allow sudo just in case the container user is set up for su and/or sudo. Consider to use --user=root." ### FIXME check support of further options # --stdin? # --hostdbus Dbusrunsession="no" Noentrypoint="no" Runasroot="" Runasuser="" ;; esac # --cap-default [ "$Capdropall" = "no" ] && { case "$Allownewprivileges" in "yes"|"no") ;; "auto") note "Option --cap-default: Enabling option --newprivileges. You can avoid this with --newprivileges=no" Allownewprivileges="yes" ;; esac } # --keymap: XKB keyboard layout [ -n "$Xkblayout" ] && { case "$Xserver" in --kwin|--kwin-xwayland) [ "$Runsonconsole" = "yes" ] || [ "$Newxvt" ] && { check_optionset "$Xserver" "--keymap" || { note "Option --keymap does not work with option $Xserver if running from console. Fallback: disabling option --keymap." check_fallback Xkblayout="" } } ;; esac [ "$Xkblayout" = "clone" ] && case "$Xserver" in --nxagent) ;; *) note "Option --keymap=clone is supported with --nxagent only. Fallback: Disabling option --keymap." check_fallback Xkblayout="" ;; esac } # --rotate [ "$Rotation" = "0" ] && Rotation="normal" case "$Xserver" in --weston*) case "$Rotation" in 90) Rotation="rotate-270" ;; 180) Rotation="rotate-180" ;; 270) Rotation="rotate-90" ;; flipped-90) Rotation="flipped-rotate-270" ;; flipped-180) Rotation="flipped-rotate-180" ;; flipped-270) Rotation="flipped-rotate-90" ;; esac ;; esac # --shell case "$Containerusershell" in auto) ;; *) store_runoption env "SHELL=$Containerusershell" [ "$Containersetup" = "no" ] && note "Option --shell: With option --no-setup x11docker only sets variable SHELL, but does no setup in /etc/passwd. Some terminal emulators might not work as expected." ;; esac # --output-count [ "$Outputcount" != "1" ] && [ "$Runsonconsole" = "yes" ] && { note "Option --output-count works in nested/windowed mode only, but not on console. Fallback: disabling option --output-count." check_fallback Outputcount="1" } # --xfishtank [ "$Xfishtank" = "yes" ] && case "$Xserver" in --xpra*|--nxagent) [ "$Desktopmode" = "no" ] && Windowmanagermode="${Windowmanagermode:-auto}" && Desktopmode="yes" ;; esac # MSYS2, Cygwin, WSL case "$Winsubsystem" in WSL2) note "WSL2 support is experimental and barely tested yet. Feedback and bug reports are appreciated!" ;; esac case "$Mobyvm" in yes) case "$Winsubsystem" in WSL1|WSL2) grep -q "/c/" <<< "$Cachebasefolder" && [ -z "$Hosthomebasefolder" ] && note "With MobyVM and WSL x11docker stores its cache files on drive C: to allow cache file sharing. Your Docker setup might not allow to share files from drive C:. If startup fails with an 'access denied' error, please either allow access to drive C: or specify a custom folder for cache storage with option '--cachebasedir D:/some/cache/folder'. Same issue can occur with option '--home'. Use option '--homebasedir D:/some/home/folder' in that case. Option --preset can help to reduce typing for each command." ;; esac [ "$Initsystem" = "systemd" ] && { note "Option --init=systemd is not supported with MobyVM. You can try another init option instead, e.g. --init=openrc. Fallback: Disabling option --init=systemd" check_fallback Initsystem="tini" } [ "$Sharecgroup" = "yes" ] && { note "Option --sharecgroup is not supported with MobyVM. Fallback: Disabling option --sharecgroup." check_fallback Sharecgroup="no" } ;; esac case "$Winsubsystem" in MSYS2|CYGWIN|WSL1|WSL2) check_optionset "Windows" "--pulseaudio --printer --webcam" ||: Sharecupsmode="" Pulseaudiomode="" Sharewebcam="no" ;; esac # --wayland [ "$Setupwayland" = "yes" ] && Dbusrunsession="yes" # --init case "$Initsystem" in systemd|sysvinit|openrc|runit) Dbussystem="yes" ;; esac # --interactive case "$Interactive" in yes) [ "$Forwardstdin" = "yes" ] && { note "Option --stdin is not compatible with option --interactive. Fallback: Disabling option --stdin." check_fallback Forwardstdin="no" } [ "$Runsinteractive" = "yes" ] && { note "Option -i, --interactive: Does not work in interactive bash mode (option --enforce-i). Fallback: Disabling option --interactive." check_fallback Interactive="no" } case "$Winsubsystem" in MSYS2|CYGWIN|WSL1) Winpty="$(command -v winpty)" Winpty="$(escapestring "$Winpty")" [ -z "$Winpty" ] && error "Option --interactive on MS Windows needs 'winpty' to run x11docker in interactive mode. MSYS2 provides winpty as a package. On Cygwin it can be compiled from source. WSL1 isn't supported yet. WSL2 might work, but is not tested yet." ;; esac ;; esac [ "$Interactive" = "yes" ] && Showcontaineroutput="no" return 0 } check_options_messages() { # some messages depending on options, but not changing settings # X server specific messages case "$Xserver" in --hostdisplay) [ "$Autochooseserver" = "yes" ] && [ -z "$Winsubsystem" ] && note "Option --hostdisplay: To allow protection against X security leaks, please install 'xinit' and one or more of: xpra, Xephyr, nxagent, weston+Xwayland, kwin_wayland+Xwayland, or run a second Xorg server with option --xorg. $Wikipackagesimage" # --clipboard [ "$Shareclipboard" = "yes" ] || warning "Option --hostdisplay: The clipboard is not isolated." # --xauth case "$Xauthentication" in untrusted) ;; *) warning "Option --hostdisplay provides QUITE BAD container isolation! Abuse of X11 protocol for keylogging and remote host control is possible. It is recommended to use another X server option like --nxagent or --xpra. You can mitigate the issue with option --xauth=untrusted." ;; esac # --checkwindow [ "$Checkforopenwindow" = "yes" ] && note "Option --checkwindow along with --hostdisplay should only be used with a keyword as a check argument. Otherwise the container is likely to run forever." ;; --xorg) [ "$Xcontainer" = "yes" ] && note "Options --xorg --xc: Running Xorg in container is experimental and might misbehave, fail or crash. Please report issues at https://github.com/mviereck/x11docker" [ "$Hostsystem" = "opensuse" ] && [ "$Runsonconsole" = "no" ] && [ "$Startuser" != "root" ] && warning "openSUSE does not support starting a second Xorg server from within X. Possible solutions: 1.) Install nested X server 'Xephyr', 'nxagent' or 'Xnest', or for --gpu support: install 'Weston' and 'Xwayland'. 2.) Switch to console tty1...tty6 with ... and start x11docker there. 3.) Run x11docker as root." case "$Xlegacywrapper" in yes) warning "Although x11docker starts Xorg as unprivileged user, most system setups wrap Xorg to give it root permissions (setuid). Evil containers may try to abuse this. Other x11docker X server options like --xephyr are more secure at this point." ;; no) case "$Startuser" in "root") warning "x11docker will run Xorg as root." ;; *) [ "$Xcontainer" = "no" ] && [ "$Runsonconsole" = "no" ] && [ -z "$Newxvt" ] && warning "Your configuration seems not to allow to start a second core Xorg server from within X. Option --xorg may fail. (Per default, only root or console users are allowed to run an Xorg server). Possible solutions: 1.) Install one of nested X servers 'Xephyr', 'Xnest' or 'nxagent'. For --gpu support: install 'weston' and 'Xwayland'. 2.) Switch to console tty1...tty6 with ... and start x11docker there. 3.) Run x11docker as root. 4.) Edit file '/etc/X11/Xwrapper.config' and replace line: allowed_users=console with lines allowed_users=anybody needs_root_rights=yes If the file does not exist already, you can create it. On Debian and Ubuntu you need package xserver-xorg-legacy. Be aware that switching directly between Xorg servers can crash them. Always switch to a black console first before switching to Xorg." ;; esac ;; esac [ "$Runsoverssh" = "yes" ] && note "Option --xorg: x11docker can run Xorg, but you won't see it in an 'ssh -X' session. Rather install e.g. Xephyr on ssh server and use option --xephyr." # --gpu=iglx [ "$Sharegpu" = "iglx" ] && [ "$Xcontainer" = "no" ] && [ -e "/var/log/Xorg.0.log" ] && { Xorgversion="$(grep -m1 xorg-server &2, redirected to null with --silent. Already declared in main(). Previously &3 FDtimetosaygoodbye="" # message channel to send termination signal from or to containers. Previously &8 FDwatchpid="" # message channel for watchpidlist(). Previously &9 # Terminal colors used for messages and -V Esc="$(printf '\033')" Colblue="${Esc}[35m" Colyellow="${Esc}[33m" Colgreen="${Esc}[32m" Colgreenbg="${Esc}[42m" Colred="${Esc}[31m" Colredbg="${Esc}[41m" Coluline="${Esc}[4m" Colnorm="${Esc}[0m" # x11docker startup environment Runsinsnap="" # docker runs in Ubuntu snap yes/no Runsinteractive="" # --enforce-i: Script runs in bash interactive mode (bash -i) yes/no. Runsinterminal="" # x11docker runs in a terminal yes/no Runsonconsole="" # x11docker runs on tty yes/no Runsoverssh="" # x11docker runs over SSH yes/no. Makes a difference for --hostdisplay Runssourced="" # x11docker has been sourced yes/no # Generated scripts Clipboardrc="clipboardrc" # --clipboard: Generated script for text clipboard sharing Cmdrc="cmdrc" # Generated script starting container command Containerrc="containerrc" # Generated script starting cmdrc Containerrootrc="containerrootrc" # Generated script to set up container, e.g. user creation. Runs as root in container. Xinitrc="xinitrc" # Generated script to set up X, e.g. cookie and xrandr Xtermrc="xtermrc" # Generated script for password prompt # Internal messages Logmessages="" # Stores messages until logfile is available, needed by logentry() Messagefifo="message.fifo" # Message channel for warning/verbose/debugnote/note/error within container,, containerrootrc and others Storeinfofile="store.info" # File to store some info like id, pid, name, exit code Storepidfile="store.pids" # File to store pids and names of background processes that should be terminated on exit Timetosaygoodbyefile="timetosaygoodbye" # File giving term signal to all parties Timetosaygoodbyefifo="timetosaygoodbye.fifo" # Message channel for --init=openrc|runit|sysvinit to shut down on x11docker signal Usemkfifo="" # Not on Windows nor with kata-runtime Watchpidfifo="watchpid.fifo" # Message channel to transfer pids to watchpidlist() # Logfiles Backendcommandfile="docker.command" # File to store generated docker command, needed for --interactive Compositorlogfile="compositor.log" # Logfile for weston or kwin_wayland Containerlogfile="container.log" # Logfile for container output other than container command output Logfile="" # $Cachefolder/x11docker.log (current log) Logfilebackup="" # $Cachebasefolder/x11docker.log (latest terminated log) Messagelogfile="message.log" # Logfile for warning/verbose/debugnote/note/error Xinitlogfile="xinit.log" # Logfile for xinit/X server Xpraclientlogfile="xpra.client.log" # Logfile for xpra client Xpraserverlogfile="xpra.server.log" # Logfile for xpra server # Generated commands Compositorcommand="" # Command to start Weston or KWin Backendcommand="" # Command to run docker Xcommand="" # Command to start X server Xpraclientcommand="" # xpra client command Xpraservercommand="" # xpra server command # Users Hostuser="" # $Lognameuser or --hostuser. Unprivileged user for non-root commands. Compare unpriv() Hostusergid="" Hostuserhome="" Hostuseruid="" Containeruser="" # --user: Container user. Default: same as $Hostuser. Containeruseruid="" Containerusergid="" Containerusergroup="" Containerusergroups="" # --group-add: Additional groups for container user Containeruserhome="" # HOME path within container Containeruserhosthome="" # HOME path of container user on host Containeruserpassword='' Createcontaineruser="yes" # exception: --user=RETAIN Lognameuser="" # $(logname) or $SUDO_USER or $PKEXEC_USER Passwordfile="$HOME/.config/x11docker/passwd" Persistanthomevolume="" # --home: Path to shared host folder or docker volume used as HOME in container. Startuser="" # User who started x11docker Unpriv="" # Command to run commands as unprivileged user Containerusershell="auto" # --shell: Preferred user shell # Hostsystem Cgroupversion="" # Needed for --init=systemd Hostarchitecture="" # uname -m, checked Hostcanwatchroot="" # x11docker can watch root processes yes/no. Related to $Hosthidepid Hostdisplay="" # Environment variable DISPLAY Hostdisplaynumber="" # DISPLAY without : (and without possible IP) Hosthidepid="" # /proc is mounted with hidepid=2 yes/no. Seen on NixOS. Hostip="" # An IP address to access host. Preferred: IP of docker daemon Hostlibc="" # glibc or musl. Can be important for locale and timezone. Hostlocaltimefile="" # Time zone from host, myrealpath /etc/localtime Hostmitshm="" # X on host has extension MIT-SHM enabled yes/no. Assume yes, check later Hostsystem="" # $ID from /etc/os-release Hostutctime="" # Time zone from host as offset to UTC Hostwaylandsocket="$WAYLAND_DISPLAY" # Store host wayland socket name Hostxauthority="Xauthority.host.$(unspecialstring "${DISPLAY:-unknown}")" # File to store copy of $XAUTHORITY Hostxenv="" # Collection of host X environment variables Hostxoverip="" # Host X uses X over IP/TCP Hostxsocket="" # Socket of DISPLAY in /tmp/.X11-unix Nvidiacontainerfile="/usr/local/bin/NVIDIA-installer.run" # --gpu: Path to nvidia installer in container Nvidiaversion="" # --gpu: Proprietary nvidia driver version on host Nvidiainstallerfile="" # --gpu: Proprietary nvidia driver installer for container in [...]local/share/x11docker Pythonbin="" # path to python binary Jqbin="" # path to jq binary # MS Windows Winpty="" # Path to winpty for --interactive on MS Windows Winsubmount="" # Path within subsystem to mounted MS Windows drives Winsubpath="" # Path within MS Windows to subsystem files Winsubsystem="" # MS Windows subsystem WSL1, WSL2, MSYS2 or CYGWIN Mobyvm="" # MS Windows: Use MobyVM yes/no (No only for WSL2 possible) # Cache folders Cachebasefolder="" # --cachebasedir Base cache folder Cachefolder="" # Subfolder of $Cachebasefolder for current container Localsharedir="" Sharefolder="share" # Subfolder of $Cachefolder for cache files shared with container Sharefoldercontainer="/x11docker" # Mountpoint of $Sharefolder in container # stdin stdout stderr Cmdstdinfifo="stdin" # stdin for container command. fifo/named pipe to forward stdin of x11docker to container command Cmdstderrlogfile="stderr" # stderr for container command Cmdstdoutlogfile="stdout" # stdout for container command Forwardstdin="no" # --stdin: forward stdin to container command yes/no # X and Wayland configuration Autochooseserver="yes" # --auto: automatic choice of X server (default) Checkforopenwindow="" # --checkwindow Cleanxhost="no" # --clean-xhost: remove xhost access policies on host X Compositorerrorcodes="Failed to process Wayland|failed to create display|] fatal:" Desktopmode="no" # --desktop: image contains a desktop environment. Dpi="" # --dpi: dots per inch. Influences font size Fullscreen="no" # --fullscreen: Fullscreen mode Lastcheckedxserver="" # check_xdepends(): Last X server option that was checked Lastcheckedxserverresult="" # check_xdepends(): Result of last check. Avoids double-checking. Maxxaxis="" # Maximal X screen size of display Maxyaxis="" # Maximal Y screen size of display Modelinefilebasepath="modelines" Newdisplay="" # --display: New DISPLAY for new X server Newdisplaynumber="" # --display: New display number for new X server. Newwaylandsocket="" # Wayland socket of $Compositorcommand Newxenv="" # Environment variables for new X server: DISPLAY XAUTHORITY XSOCKET WAYLAND_DISPLAY XDG_RUNTIME_DIR Newxenv_cr="" # Like Newxenv, but with newlines Newxlock="" # .Xn-lock - exists for running X server with socket n Newxsocket="" # New X unix socket Newxvt="" # --vt: number of virtual console to use for --xorg Numbersinusefile="displaynumbers.$(date +%y_%m_%d)" # File to store display numbers used today. Helps to avoid race conditions on simultaneous startups Nxagentclientrc="nxagent.nxclientrc" # --nxagent NX_CLIENT script to catch nxagent messages Nxagentkeysfile="nxagent.keys" # --nxagent keyboard shortcut config Nxagentoptionsfile="nxagent.options" # --nxagent options not available on cli, but possible in config file Modeline="" # Screen modeline describing display size, see "man cvt". Outputcount="1" # --output-count: quantum of virtual screens for Weston or Xephyr Rotation="" # --rotate: Rotation for --weston, --weston-xwayland or --xorg: 0/90/180/270/flipped/flipped-90/.. Scaling="" # --scale: Scaling factor for xpra and weston Screensize="" # --size XxY: Display size Setupwayland="no" # --wayland, --kwin, --weston --hostwayland: Provide a Wayland environment 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" Xauthentication="yes" # --xauth: cookie authentication Xaxis="" # Virtual screen width Xcomposite="" # --xcomposite: +extension COMPOSITE yes/no Xkblayout="" # --keymap: Layout for keymap, compare /usr/share/X11/xkb/symbols Xfishtank="no" # --xfishtank: Show a fish tank on new X server Xhost="" # --xhost: custom xhost setting on new X server Xiniterrorcodes="xinit: giving up|unable to connect to X server|Connection refused|server error|Only console users are allowed" Xlegacywrapper="" # --xorg: /etc/X11/Xwrapper.config is configured to run within X yes/no Xpraborder="" # --border: Colored border for xpra clients Xpracontainerenv="UBUNTU_MENUPROXY= QT_X11_NO_NATIVE_MENUBAR=1 MWNOCAPTURE=true MWNO_RIT=true MWWM=allwm GTK_OVERLAY_SCROLLING=0 GTK_CSD=0 NO_AT_BRIDGE=1" # environment variables Xprahelp="" # Output of 'xpra --help' Xprashm="" # xpra server should use MIT-SHM yes/no Xpraversion="" # $(xpra --version) to decide some xpra options and messages Xserver="" # X server option to use Xoverip="" # --xoverip: Connect to X over TCP yes/no Xserveroptions="" # --xopt: Custom X server options Xtest="" # --xtest: Enable extension Xtest yes/no. If empty, yes for --xpra/--xvfb, otherwise no Yaxis="" # Virtual screen height # X and Wayland config and cookie files Customwestonini="" # --westonini: Custom config file for weston Westonini="weston.ini" # Generated config file for weston Xclientcookie="Xauthority.client" # Generated X client cookie. Normally same as $Xservercookie, except for --hostdisplay and --nxagent Xkbkeymapfile="xkb.keymap" # --keymap: File to store output of host keymap in xinitrc Xorgconf="" # --xorgconf: custom xorg.conf Xservercookie="Xauthority.server" # Generated X server cookie # X in container Xcnetworkid="" Xcnetworkname="" Xcontainer="auto" Xcontainerbackend="" Xcontainercommand="" Xcontainerimage="x11docker/xserver" Xcontainerip="" Xcontainername="" Xcontaineroptions="" Xcontaineroptionsconsole="" Xcontainertools="" Xcrootless="" # Window manager Windowmanagermode="" # --wm: Window manager to use: container/host/auto Windowmanagercommand="" # --wm: Argument for --wm or host wim command # Host integration Alsacard="$ALSA_CARD" # --alsa: Specified ALSA card Hosthomebasefolder="" # --homebasedir: Base directory for container home with --home Langwunsch="" # --lang: Search or create UTF-8 locale in container and set LANG Pulseaudioconf="pulseaudio.client.conf" # --pulseaudio: Client config in container Pulseaudiocookie="pulseaudio.cookie" # --pulseaudio: possible pulse cookie from host to share Pulseaudiomode="" # --pulseaudio: 'tcp', 'socket' or 'auto' Pulseaudiomoduleid="" # --pulseaudio: module ID, stored for unload in finish() Pulseaudioport="" # --pulseaudio: TCP port for --pulseaudio=tcp Pulseaudiosocket="pulseaudio.socket" # --pulseaudio: unix socket for --pulseaudio=socket Sharealsa="no" # --alsa: enable ALSA sound, share /dev/snd Shareclipboard="no" # --clipboard: Enable clipboard sharing Sharecupsmode="" # --printer: Share access to CUPS printer server: socket|tcp|"" Sharegpu="no" # --gpu: Use hardware accelerated OpenGL, share files in /dev/dri Sharehome="no" # --home: Share a folder ~/.local/share/x11docker/Imagename with created container Sharevolumes="" # --share: Host files or folders or devices to share, array Sharevolumescount="0" # --share: Counts shared folders in array Sharewebcam="no" # --webcam: Share webcam device /dev/video* # Image information Imagearchitecture="" Imagecommand="" Imageentrypoint="" Imageinspect="" # Output of --inspect Imageworkdir="" Imageuser="" # Container setup Adminusercaps="no" # --cap-default, --sudouser, --init=systemd: add capabilities for general container system administration Allownewprivileges="auto" # --newprivileges: Docker run option --security-opt=no-new-privileges. Default: no. Enabled by options --newprivileges, --cap-default, --sudouser. Backend="" # --backend: Backend to use, like docker, podman, nerdctl, proot, host. A default can be specified with default preset file. Backendbin="" # path to binary of backend Backendinfo="" # output of 'docker info' Capabilities="" # Capabilities to add. Default: none, exceptions for --init, --sudouser Capdropall="yes" # --cap-default: Drop all container capabilities and set --securty-opt=no-new-privileges yes/no Chrootmountlist="" Containercommand="" # Container command [+args] Containerentrypoint="" Containerenvironment="" # --env: Environment variables Containerenvironmentcount="0" Containerenvironmentfile="container.environment" # file to store final container environment Containerlocaltimefile="libc.localtime" # localtime file from host shared to container Containername="" # --name: Container name Containersetup="yes" Customdockeroptions="" # -- [...] -- : Custom options for "docker run". Defaultruntime="" Imagename="" # Image to run Interactive="no" # --interactive: Run docker with interactive tty yes/no Limitresources="" # --limit: Limit access to CPU and RAM, 0.1 ... 1.0 Network="none" # --network Noentrypoint="no" # --no-entrypoint: Disable entrypoint in image yes/no Optionset="" # list of set options for later support check Optionsetall="" # list of all available long options Podmanrootfs="" # --rootfs to indicate that podman should use a root file system Remountcgroup="" # --init=systemd with cgroupv2 in docker workaround Rootfs="" # --backend=proot Backendrootless="" # Check for rootful/rootless docker depending on DOCKER_HOST Runtime="" # Runtime to use. runc|nvidia|kata-runtime|crun Snapsupport="" # --snap: Fallback mode to support docker in snap Shareipc="no" Stopsignal="" # Signal to send on 'docker stop' Sudouser="" # --sudouser: Create user with sudo permissions and root user with password 'x11docker' Switchcontaineruser="no" # --init=systemd|openrc|runit|sysvinit: User switching to trigger login services yes/no Switchcontainerusercaps="no" # --init=systemd|openrc|runit|sysvinit, --sudouser: Add capabilities for su/sudo user switching Systemdjournallogfile="systemd.journal.log" Workdir="" # --workdir: Set working directory in container # Init and DBus Dbusrunsession="no" # --dbus, --wayland, --init=systemd|openrc|runit|sysvinit: Run container command with dbus-run-session / DBus user session Dbussystem="no" # --init=systemd|openrc|runit|sysvinit: Run DBus system daemon in container Initsystem="tini" # --init: Init system in container Sharecgroup="no" # --sharecgroup, --init=systemd: share /sys/fs/cgroup. Also needed for elogind Sharehostdbus="no" # --hostdbus: Connect to DBus user daemon on host Initbinaryfile="" # --init=tini (default): Binary of tini; either /usr/bin/docker-exec or provided by user in [...]/share/x11docker Initcontainerpath="/usr/local/bin/init" # --init=tini: Path of tini (or catatonit) in container # Custom additional commands Runasuser="" # --runasuser: Add container command to containerrc Runasroot="" # --runasroot: Add container command to container setup script running as root Runfromhost="" # --runfromhost: Add host command to xinitrc # Miscellaneous Buildimage="" # --build: x11docker image to build from repo Dockerfile Codename="" # created from image name and command without special chars for use with container name and cache folder Experimental="" # --experimental: use experimental code Fallback="yes" # --fallback: Allow or deny fallbacks for failing options. Imagebasename="" # Image name without tags and / replaced with -. For use of --home folders. Parsedoptions_global="" # Parsed options Presetdirlocal="$HOME/.config/x11docker/preset" # --preset storage dir (local) Presetdirsystem="/etc/x11docker/preset" # --preset storage dir (system) Presetlist="" # List of already parsed preset files to avoid a loop Preservecachefiles="no" # If yes, don't delete cache files on exit. For few failure cases only. # Verbosity options Debugmode="no" # --debug: Excerpt of --verbose, also bash error checks Printcheck="no" # --printcheck: Show X dependency check messages Showcontainerid="" # --printid: Output of container ID on stdout Showcontaineroutput="yes" # Show container command stdout Showcontainerpid1pid="" # --printpid1: Output of host PID of container PID 1 on stdout Showdisplayenvironment="" # --printenv: Output of environment variables of new display on stdout Showinfofile="" # --printinfofile: Show path of $Storeinfofile Silent="no" # --quiet: Do not show x11docker messages Verbose="" # --verbose: Be verbose yes/no Verbosecolors="no" # -V: colored output for --verbose (and delete some noisy systemd error messages) Wikipackages="You can look for the package name of this command at: https://github.com/mviereck/x11docker/wiki/dependencies#table-of-all-packages" Wikipackagesimage="$Wikipackages Alternatively you can provide image x11docker/xserver (option --xc)." # Special options not starting X or docker Cleanup="" # --cleanup: Remove orphaned containers and cache files Createlauncher="" # --launcher: Create application launcher on desktop and exit yes/no Installermode="" # --install/--update/--update-master/--remove Installerarg="" # --update/--update-master: show diff only, do not install # Lists of window managers # - these window managers are known to work well with x11docker (alphabetical order)(excluding $Wm_not_recommended and $Wm_ugly): Wm_good="amiwm blackbox cinnamon compiz ctwm enlightenment fluxbox flwm fvwm" Wm_good="$Wm_good jwm kwin kwin_x11 lxsession mate-session mate-wm marco metacity notion olwm olvwm openbox ororobus pekwm" Wm_good="$Wm_good sawfish twm wmaker w9wm xfwm4" # - these wm's are recommended and lightweight, but cannot show desktop options. best first: Wm_recommended_nodesktop_light="metacity marco openbox sawfish xfwm4" # - 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" # - these wm's are recommended, heavy AND desktop independent. best first: Wm_recommended_desktop_heavy="lxsession mate-session enlightenment cinnamon cinnamon-session plasmashell" # - these wm's are not really useful (please don't hit me) (best first): Wm_not_recommended="awesome evilwm herbstluftwm i3 lwm matchbox miwm mutter spectrwm subtle windowlab wmii wm2" # - these wm's cannot be autodetected by wmctrl if they are already running Wm_nodetect="aewm aewm++ afterstep awesome ctwm mwm miwm olwm olvwm sapphire windowlab wm2 w9wm" # - these wm's can cause problems (they can be beautiful, though): Wm_ugly="icewm sapphire aewm aewm++" # - these wm's doesn't work: Wm_bad="budgie-wm clfswm tinywm tritium muffin gnome-shell" # List of all working window managers, recommended ones first, excluding $Wm_bad: Wm_all="$Wm_recommended_nodesktop_light $Wm_recommended_nodesktop_heavy $Wm_recommended_desktop_light $Wm_recommended_desktop_heavy $Wm_good $Wm_ugly $Wm_not_recommended $Wm_nodetect" # x11docker communication functions to integrate into generated scripts Messagefifofuncs=' warning() { echo "$*:WARNING" >>$Messagefile } note() { echo "$*:NOTE" >>$Messagefile } verbose() { echo "$*:VERBOSE" >>$Messagefile } debugnote() { echo "$*:DEBUGNOTE" >>$Messagefile } error() { echo "$*:ERROR" >>$Messagefile exit 64 } stdout() { echo "$*:STDOUT" >>$Messagefile }' MessagefifofuncsX=' warning() { echo "$*:WARNING" | sed "s/\$/ /" >>$Messagefile } note() { echo "$*:NOTE" | sed "s/\$/ /" >>$Messagefile } verbose() { echo "$*:VERBOSE" | sed "s/\$/ /" >>$Messagefile } debugnote() { echo "$*:DEBUGNOTE" | sed "s/\$/ /" >>$Messagefile } error() { echo "$*:ERROR" | sed "s/\$/ /" >>$Messagefile exit 64 } stdout() { echo "$*:STDOUT" | sed "s/\$/ /" >>$Messagefile }' Messagefifofuncs_escaped=' warning() { echo \"\$*:WARNING\" >>\$Messagefile } note() { echo \"\$*:NOTE\" >>\$Messagefile } verbose() { echo \"\$*:VERBOSE\" >>\$Messagefile } debugnote() { echo \"\$*:DEBUGNOTE\" >>\$Messagefile } error() { echo \"\$*:ERROR\" >>\$Messagefile exit 64 } stdout() { echo \"\$*:STDOUT\" | sed \"s/\\\$/ /\" >>\$Messagefile }' Messagefifofuncs_escapedX=' warning() { echo \"\$*:WARNING\" | sed \"s/\\\$/ /\" >>\$Messagefile } note() { echo \"\$*:NOTE\" | sed \"s/\\\$/ /\" >>\$Messagefile } verbose() { echo \"\$*:VERBOSE\" | sed \"s/\\\$/ /\" >>\$Messagefile } debugnote() { echo \"\$*:DEBUGNOTE\" | sed \"s/\\\$/ /\" >>\$Messagefile } error() { echo \"\$*:ERROR\" | sed \"s/\\\$/ /\" >>\$Messagefile exit 64 } stdout() { echo \"\$*:STDOUT\" | sed \"s/\\\$/ /\" >>\$Messagefile }' } experimental() { [ "$Experimental" ] && { note "Option --experimental: changed something: ${1:-"(no comment)"}" return 0 } return 1 } unpriv() { # run a command as unprivileged user. Needed if x11docker was started by root or with sudo. # $Unpriv is declared in check_hostuser: 'eval' or 'su $Hostuser -c' local Command Command="$(oneline "${1:-}")" $Unpriv "$Command" return $? } unpriv_backend() { # run container backend rootful or rootless local Command Command="$(oneline "${1:-}")" [ -z "$Backendbin" ] && { warning "Binary path for backend $Backend not set." return 1 } case "$Backendrootless" in yes) unpriv "$Command" ;; no|"") eval "env DOCKER_HOST= $Command" ;; esac return $? } unpriv_xcbackend() { # run X container backend rootful or rootless local Command Command="$(oneline "${1:-}")" case "${Xcrootless:-no}" in yes) unpriv "$Command" ;; no) eval "env DOCKER_HOST= $Command" ;; esac return $? } main() { [ "$(uname -s)" = "Darwin" ] && echo "Error: x11docker does not run on MacOS. Please use Linux." >&2 && exit 64 trap finish EXIT trap finish_sigint SIGINT exec {FDstderr}>&2 # stderr channel for warning(), note(), debugnote() and --verbose declare_variables parse_options "$@" [ "$Silent" = "yes" ] && exec {FDstderr}>/dev/null # --quiet [ "$Debugmode" = "yes" ] && { # --debug set -Eu trap 'traperror $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]})' ERR } [ -n "$Containeruserpassword" ] && { # --password check_optionset "--password" "$(grep -v -- '--password' <<< "$Optionsetall")" ||: set_password "$Containeruserpassword" finish return 0 } check_host # get some infos about host system check_options_arguments # check for valid arguments check_hostuser # find unprivileged host user # --hostuser check_console # check if running on console create_cachefiles # create cache files owned by unprivileged user # --cachebasedir setup_verbosity # create [and show] summary logfile # --verbose check_containeruser # unprivileged user in container # --user check_containerhomebasedir # check base dir for --home and possible rootfs # --homebasedir # Special x11docker jobs [ "$Createlauncher" ] && { create_launcher # --launcher: Create application launcher icon on desktop finish } check_backend # --backend [ "$Installermode" ] && { # --install, --update, --update-master, --remove case "$Packagedversion" in yes) case "$Installermode" in --install|--update|--update-master|--remove|--remove-oldprefix) error "Options --install|--update|--update-master|--remove are not supported in packaged versions of x11docker. Please use your package manager instead." ;; esac ;; esac check_optionset "$Installermode" "$(grep -v -E -- '--install|--update|--update-master|--remove|--remove-oldprefix' <<< "$Optionsetall")" ||: installer "$Installermode" finish } [ "$Cleanup" ] && { # --cleanup: Clean up cache and orphaned x11docker containers check_optionset "--cleanup" "$(grep -v -E -- '--cleanup|--backend' <<< "$Optionsetall")" ||: cleanup finish } [ "$Buildimage" ] && { # --build: Build image from x11docker repository check_optionset "--build" "$(grep -v -E -- '--build|--backend' <<< "$Optionsetall")" ||: buildimage "$Imagename" finish } rocknroll || return 0 setup_fifo # open message channels for container, xinitrc and watchpidlist() check_xdg_runtime_dir check_hostxenv # check X environment from host check_snap # check if docker is installed in snap; causes restrictions check_xcontainer # check if x11docker/xserver can/will be used # --xc check_xserver # check chosen X server or auto-choose one check_newxenv # find free display setup_xcnetwork ||: # internal network for --xc --xoverip [ "$Xserver" = "--xorg" ] || [ -n "$Newxvt" ] || [ "$Runsonconsole" = "yes" ] && check_vt # --vt: find free tty/virtual terminal for Xorg or weston [ "$Xcontainer" = "yes" ] && { # --xc create_xcontainercommand debugnote "X container command (rootless ${Xcrootless:-undefined}): $Xcontainercommand" start_xcontainer } store_newxenv check_options_interferences # check options, change settings if needed check_containerhome # create persistent container home # --home check_options_messages # some messages depending on options, but not changing anything check_windowmanager # --wm [ "$Sharegpu" = "direct" ] && setup_gpu ||: # --gpu [ "$Sharewebcam" = "yes" ] && setup_webcam # --webcam [ "$Sharecupsmode" ] && setup_printer # --printer [ "$Pulseaudiomode" ] && setup_sound_pulseaudio # --pulseaudio [ "$Sharealsa" = "yes" ] && setup_sound_alsa # --alsa [ "$Sharehostdbus" = "yes" ] && setup_hostdbus # --hostdbus #### Create command to run X server [and/or Wayland compositor] [ "$Cleanxhost" = "yes" ] && clean_xhost # --clean-xhost [ "$Shareclipboard" != "no" ] && create_clipboardrc >> "$Clipboardrc" # --clipboard check_screensize # size of host X and of new X server # --size create_xcommand # set up start command for X server # all X server and Wayland options [ "$Xcommand" ] && debugnote "X server command: $Xcommand" [ "$Compositorcommand" ] && debugnote "Wayland compositor command: $Compositorcommand" storeinfo "x11dockerpid=$$" # store pid of x11docker debugnote "x11docker version: $Version Backend version: $(${Backendbin:-:} --version 2>&1) Running rootless: $Backendrootless OCI Runtime: $Runtime Host system: $(grep '^PRETTY_NAME' /etc/os-release 2>/dev/null | cut -d= -f2 || echo "$Hostsystem") Host architecture: $Hostarchitecture Command: '$0' $(for Line in "$@"; do echo -n "'$Line' " ; done) Parsed options: $Parsedoptions_global x11docker was started by: $Startuser As host user serves: $Hostuser Container user will be: $( [ "$Createcontaineruser" = "yes" ] && echo "$Containeruser" || echo "(retaining USER of image)") Container user password: $( [ "$Createcontaineruser" = "yes" ] && echo "x11docker" || echo "(unknown)") Running in a terminal: $Runsinterminal Running on console: $Runsonconsole Running over SSH: $Runsoverssh Running sourced: $Runssourced bash \$-: $-" [ "$Winsubsystem" ] && debugnote " Running on Windows subsystem: $Winsubsystem Path to subsystem: $(convertpath windows "$Winsubpath")/ Mount path in subsystem: $Winsubmount/ Using MobyVM: $Mobyvm" setup_initsystem # init in container. # --init case "$Backend" in docker|podman|nerdctl) [ -z "$Rootfs" ] && check_image setup_capabilities # add linux capabilities if needed for some options. Default: --cap-drop=ALL [ "$Sharegpu" = "yes" ] && setup_vaapi # --gpu ;; esac case "$Backend" in proot|docker|podman|nerdctl) create_backendcommand # create 'docker run' command echo "$Backendcommand" >> "$Backendcommandfile" debugnote "$Backend command (rootless ${Backendrootless:-undefined}): $Backendcommand" [ "$Containersetup" = "yes" ] && { ## 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 >> "$Containerrootrc" verbose "Generated containerrootrc: $(nl -ba <$Containerrootrc)" } ;; esac [ "$Containersetup" = "yes" ] && { create_containerrc verbose "Generated containerrc: $(nl -ba <$Containerrc)" create_cmdrc verbose "Generated cmdrc: $(nl -ba <$Cmdrc)" } { #### 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. # wait for X to be ready waitforlogentry 'start_container()' $Xinitlogfile 'xinitrc is ready' "$Xiniterrorcodes" # softlink (or socat) to X or Wayland unix socket in container # --xc case "$Xcontainer" in yes) #[ "$Newdisplay" ] && ln -s "$Cachefolder/tmp/.X11-unix/X$Newdisplaynumber" "/tmp/.X11-unix/X$Newdisplaynumber" 2>>$Xinitlogfile ||: command -v socat >/dev/null && { [ "$Newdisplay" ] && unpriv "socat -lf $Xinitlogfile -d UNIX-LISTEN:/tmp/.X11-unix/X$Newdisplaynumber,ignoreeof,fork UNIX:$Cachefolder/tmp/.X11-unix/X$Newdisplaynumber" & storepid "$!" socat-X } || { [ -n "$Showdisplayenvironment" ] && Message="note" || Message="debugnote" $Message "Option --xc --printenv: Command 'socat' not found. Using 'ln -s' to provide X unix socket on host. GTK3 application might fail to access X." [ "$Newdisplay" ] && ln -s "$Cachefolder/tmp/.X11-unix/X$Newdisplaynumber" "/tmp/.X11-unix/X$Newdisplaynumber" 2>>$Xinitlogfile ||: } [ "$Newwaylandsocket" ] && ln -s "$Cachefolder/XDG_RUNTIME_DIR/$Newwaylandsocket" "$XDG_RUNTIME_DIR/$Newwaylandsocket" 2>>$Xinitlogfile ||: ;; esac rocknroll || { saygoodbye main-runshell return 1 } # xinit(?) sets variables to new display for host applications, too. This undoes it. unpriv "dbus-update-activation-environment --systemd DISPLAY='$DISPLAY' XAUTHORITY='$XAUTHORITY'" >> "$Xinitlogfile" 2>&1 ||: [ "$Runfromhost" ] && { # --runfromhost while read Line; do unpriv "env $Newxenv $Runfromhost" done <<< "$Runfromhost" } # start container start_container Pid1pid="$(storeinfo dump pid1pid)" Containerip="$(storeinfo dump containerip)" # --xoverip -listen tcp: Use xhost instead of XAUTHORITY case "$Xoverip" in listentcp) verbose "--xoverip=listentcp: Replacing cookie authentication with host based authentication." xtool --check "xhost" && { xtool "env DISPLAY=:$Newdisplaynumber XAUTHORITY=$Xclientcookie xhost +INET:$Containerip" >>$Xinitlogfile 2>&1 && \ truncate -s0 $Xclientcookie } || { warning "--xoverip=$Xoverip: Failed to set up xhost access instead of using a cookie. If the container shares the cookie with others, others might be able to access X server $Xserver." } ;; esac touch $Sharefolder/xhostready # watch container case "$Winsubsystem" in "") setonwatchpidlist "${Pid1pid:-NOPID}" pid1pid ;; *) setonwatchpidlist "CONTAINER$Containername" ;; esac # watch xinit and X server case "$Xserver" in --tty|--hostdisplay|--hostwayland|--weston|--kwin) ;; *) Xinitpid="$(pgrep -a xinit 2>/dev/null | grep "xinit $Xinitrc" | awk '{print $1}')" checkpid "$Xinitpid" && setonwatchpidlist "$Xinitpid" xinit Xserverpid=$(ps aux | rmcr | grep "$(echo "${Xcommand:-nothingtolookfor}" | cut -d' ' -f1-2)" | grep -v grep | grep -v xinit | awk '{print $2}') checkpid "$Xserverpid" && setonwatchpidlist "$Xserverpid" Xserver ;; esac [ "$Pulseaudiomode" = "tcp" ] && start_pulseaudiotcp # --pulseaudio=tcp # some debug output checkpid "$Pid1pid" && debugnote "Process tree of container: (maybe not complete yet) $(pstree -cp "$Pid1pid" 2>&1 ||:)" debugnote "Process tree of x11docker: $(pstree -p $$ 2>&1 ||:)" debugnote "storeinfo(): Stored info: $(cat $Storeinfofile)" debugnote "storepid(): Stored pids: $(cat $Storepidfile)" # optional info on stdout case "$Showinfofile" in # --printinfofile "") ;; yes) echo "$Storeinfofile" ;; *) unpriv "echo '$Storeinfofile' > '$Showinfofile'" || error "Option --printinfofile: Invalid file $Showinfofile" ;; esac case "$Showdisplayenvironment" in # --printenv "") ;; yes) storeinfo dump Xenv ;; *) unpriv "echo '$Newxenv_cr' > '$Showdisplayenvironment'" || error "Option --printenv: Invalid file $Showdisplayenvironment" ;; esac case "$Showcontainerid" in # --printid "") ;; yes) storeinfo dump containerid ;; *) unpriv "storeinfo dump containerid > '$Showcontainerid'" || error "Option --printid: Invalid file $Showcontainerid" ;; esac case "$Showcontainerpid1pid" in # --printpid1 "") ;; yes) echo "$Pid1pid" ;; *) unpriv "echo '$Pid1pid' > '$Showcontainerpid1pid'" || error "Option --printcontainerpid1pid: Invalid file $Showcontainerpid1pid" ;; esac storeinfo "x11docker=ready" } <&0 & storepid $! containershell rocknroll || return 1 ## 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, share clipboard, scale/rotate --xorg, create set of screen resolutions. create_xinitrc >> "$Xinitrc" verbose "Generated xinitrc: $(nl -ba <$Xinitrc)" [ -s "$Westonini" ] && verbose "Generated weston.ini: $(nl -ba <$Westonini)" case "$Xserver" in --xpra*) { waitforlogentry xpra $Xinitlogfile "xinitrc is ready" infinity rocknroll && start_xpra # --xpra, --xpra-xwayland } & storepid $! xpraloop ;; esac rocknroll && [ "$Compositorcommand" ] && start_compositor rocknroll && start_xserver } main "$@" saygoodbye main