diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e266dc6 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +GOCMD=go +GOTEST=$(GOCMD) test +GOVET=$(GOCMD) vet +BINARY_NAME=calaos-container +VERSION?=1.0.0 + +GREEN := $(shell tput -Txterm setaf 2) +YELLOW := $(shell tput -Txterm setaf 3) +WHITE := $(shell tput -Txterm setaf 7) +CYAN := $(shell tput -Txterm setaf 6) +RESET := $(shell tput -Txterm sgr0) + +.PHONY: all test build + +all: build + +## Help: +help: ## Show this help. + @echo '' + @echo 'Usage:' + @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' + @echo '' + @echo 'Targets:' + @awk 'BEGIN {FS = ":.*?## "} { \ + if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \ + else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \ + }' $(MAKEFILE_LIST) + +## Build: +build: ## Build the project and put the output binary in bin/ + @mkdir -p bin + $(GOCMD) build -v -o bin/$(BINARY_NAME) . + +clean: ## Remove build related file + rm -fr ./bin + rm -fr ./out + rm -f ./junit-report.xml checkstyle-report.xml ./coverage.xml ./profile.cov yamllint-checkstyle.xml + +## Test: +test: ## Run the tests of the project +# $(GOTEST) -v -race ./... $(OUTPUT_OPTIONS) + @echo test disabled + +coverage: ## Run the tests of the project and export the coverage + $(GOTEST) -cover -covermode=count -coverprofile=profile.cov ./... + $(GOCMD) tool cover -func profile.cov \ No newline at end of file diff --git a/bin/calaos-container b/bin/calaos-container new file mode 100755 index 0000000..053b45e Binary files /dev/null and b/bin/calaos-container differ diff --git a/debian/.debhelper/calaos-container/dbgsym-build-ids b/debian/.debhelper/calaos-container/dbgsym-build-ids new file mode 100644 index 0000000..886a2c5 --- /dev/null +++ b/debian/.debhelper/calaos-container/dbgsym-build-ids @@ -0,0 +1 @@ +9a106f7c45341688341edd85237782c9196f7ac6 \ No newline at end of file diff --git a/debian/.debhelper/calaos-container/dbgsym-root/DEBIAN/control b/debian/.debhelper/calaos-container/dbgsym-root/DEBIAN/control new file mode 100644 index 0000000..1c7b211 --- /dev/null +++ b/debian/.debhelper/calaos-container/dbgsym-root/DEBIAN/control @@ -0,0 +1,12 @@ +Package: calaos-container-dbgsym +Source: calaos-container (1.0-0) +Version: 1.0.0-0~bookworm0 +Auto-Built-Package: debug-symbols +Architecture: amd64 +Maintainer: Calaos Team +Installed-Size: 9230 +Depends: calaos-container (= 1.0.0-0~bookworm0) +Section: debug +Priority: optional +Description: debug symbols for calaos-container +Build-Ids: 9a106f7c45341688341edd85237782c9196f7ac6 diff --git a/debian/.debhelper/calaos-container/dbgsym-root/DEBIAN/md5sums b/debian/.debhelper/calaos-container/dbgsym-root/DEBIAN/md5sums new file mode 100644 index 0000000..689141d --- /dev/null +++ b/debian/.debhelper/calaos-container/dbgsym-root/DEBIAN/md5sums @@ -0,0 +1 @@ +1545a016a6a17703c2cedc821c603125 usr/lib/debug/.build-id/9a/106f7c45341688341edd85237782c9196f7ac6.debug diff --git a/debian/.debhelper/calaos-container/dbgsym-root/usr/lib/debug/.build-id/9a/106f7c45341688341edd85237782c9196f7ac6.debug b/debian/.debhelper/calaos-container/dbgsym-root/usr/lib/debug/.build-id/9a/106f7c45341688341edd85237782c9196f7ac6.debug new file mode 100644 index 0000000..f59278b Binary files /dev/null and b/debian/.debhelper/calaos-container/dbgsym-root/usr/lib/debug/.build-id/9a/106f7c45341688341edd85237782c9196f7ac6.debug differ diff --git a/debian/.debhelper/calaos-container/dbgsym-root/usr/share/doc/calaos-container-dbgsym b/debian/.debhelper/calaos-container/dbgsym-root/usr/share/doc/calaos-container-dbgsym new file mode 120000 index 0000000..39a3dc1 --- /dev/null +++ b/debian/.debhelper/calaos-container/dbgsym-root/usr/share/doc/calaos-container-dbgsym @@ -0,0 +1 @@ +calaos-container \ No newline at end of file diff --git a/debian/.debhelper/generated/calaos-container/dh_installchangelogs.dch.trimmed b/debian/.debhelper/generated/calaos-container/dh_installchangelogs.dch.trimmed new file mode 100644 index 0000000..363accd --- /dev/null +++ b/debian/.debhelper/generated/calaos-container/dh_installchangelogs.dch.trimmed @@ -0,0 +1,5 @@ +calaos-container (1.0-0) UNRELEASED; urgency=medium + + * Initial release + + -- Calaos Team Tue, 01 Jul 2023 00:00:00 +0200 \ No newline at end of file diff --git a/debian/.debhelper/generated/calaos-container/installed-by-dh_install b/debian/.debhelper/generated/calaos-container/installed-by-dh_install new file mode 100644 index 0000000..b351862 --- /dev/null +++ b/debian/.debhelper/generated/calaos-container/installed-by-dh_install @@ -0,0 +1,2 @@ +./bin/calaos-container +./scripts/x11docker diff --git a/debian/.debhelper/generated/calaos-container/installed-by-dh_installdocs b/debian/.debhelper/generated/calaos-container/installed-by-dh_installdocs new file mode 100644 index 0000000..e69de29 diff --git a/debian/.debhelper/generated/calaos-container/postinst.service b/debian/.debhelper/generated/calaos-container/postinst.service new file mode 100644 index 0000000..202c20d --- /dev/null +++ b/debian/.debhelper/generated/calaos-container/postinst.service @@ -0,0 +1,30 @@ +# Automatically added by dh_installsystemd/13.11.4 +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + # The following line should be removed in trixie or trixie+1 + deb-systemd-helper unmask 'calaos-container.service' >/dev/null || true + + # was-enabled defaults to true, so new installations run enable. + if deb-systemd-helper --quiet was-enabled 'calaos-container.service'; then + # Enables the unit on first installation, creates new + # symlinks on upgrades if the unit file has changed. + deb-systemd-helper enable 'calaos-container.service' >/dev/null || true + else + # Update the statefile to add new symlinks (if any), which need to be + # cleaned up on purge. Also remove old symlinks. + deb-systemd-helper update-state 'calaos-container.service' >/dev/null || true + fi +fi +# End automatically added section +# Automatically added by dh_installsystemd/13.11.4 +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + if [ -n "$2" ]; then + _dh_action=restart + else + _dh_action=start + fi + deb-systemd-invoke $_dh_action 'calaos-container.service' >/dev/null || true + fi +fi +# End automatically added section diff --git a/debian/.debhelper/generated/calaos-container/prerm.service b/debian/.debhelper/generated/calaos-container/prerm.service new file mode 100644 index 0000000..9090102 --- /dev/null +++ b/debian/.debhelper/generated/calaos-container/prerm.service @@ -0,0 +1,5 @@ +# Automatically added by dh_installsystemd/13.11.4 +if [ -z "${DPKG_ROOT:-}" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + deb-systemd-invoke stop 'calaos-container.service' >/dev/null || true +fi +# End automatically added section diff --git a/debian/calaos-container.debhelper.log b/debian/calaos-container.debhelper.log new file mode 100644 index 0000000..edc7119 --- /dev/null +++ b/debian/calaos-container.debhelper.log @@ -0,0 +1 @@ +dh_gencontrol diff --git a/debian/calaos-container.install b/debian/calaos-container.install index fdb58fe..9c00fc8 100644 --- a/debian/calaos-container.install +++ b/debian/calaos-container.install @@ -1,2 +1,2 @@ -calaos-container usr/bin +bin/calaos-container usr/bin scripts/x11docker usr/bin \ No newline at end of file diff --git a/debian/calaos-container.postrm.debhelper b/debian/calaos-container.postrm.debhelper new file mode 100644 index 0000000..7fa98f7 --- /dev/null +++ b/debian/calaos-container.postrm.debhelper @@ -0,0 +1,12 @@ +# Automatically added by dh_installsystemd/13.11.4 +if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section +# Automatically added by dh_installsystemd/13.11.4 +if [ "$1" = "purge" ]; then + if [ -x "/usr/bin/deb-systemd-helper" ]; then + deb-systemd-helper purge 'calaos-container.service' >/dev/null || true + fi +fi +# End automatically added section diff --git a/debian/calaos-container.substvars b/debian/calaos-container.substvars new file mode 100644 index 0000000..aa876b3 --- /dev/null +++ b/debian/calaos-container.substvars @@ -0,0 +1,3 @@ +shlibs:Depends=libc6 (>= 2.34), libdevmapper1.02.1 (>= 2:1.02.97), libgpgme11 (>= 1.4.1) +misc:Depends= +misc:Pre-Depends= diff --git a/debian/calaos-container/DEBIAN/control b/debian/calaos-container/DEBIAN/control new file mode 100644 index 0000000..7154c0a --- /dev/null +++ b/debian/calaos-container/DEBIAN/control @@ -0,0 +1,8 @@ +Package: calaos-container +Source: calaos-container (1.0-0) +Version: 1.0.0-0~bookworm0 +Architecture: amd64 +Maintainer: Calaos Team +Installed-Size: 22710 +Depends: libc6 (>= 2.34), libdevmapper1.02.1 (>= 2:1.02.97), libgpgme11 (>= 1.4.1), bash, podman +Description: Calaos-OS container service diff --git a/debian/calaos-container/DEBIAN/md5sums b/debian/calaos-container/DEBIAN/md5sums new file mode 100644 index 0000000..e3a8226 --- /dev/null +++ b/debian/calaos-container/DEBIAN/md5sums @@ -0,0 +1,4 @@ +7e62a4c4310565a1beb44eca7daddde3 lib/systemd/system/calaos-container.service +bfba85cce7f575423fff9a163110e02c usr/bin/calaos-container +76eb3a0df90ff27a69fb3a5a90846c8b usr/bin/x11docker +ee8994ea36678b03ee182c1a83915a97 usr/share/doc/calaos-container/changelog.Debian.gz diff --git a/debian/calaos-container/DEBIAN/postinst b/debian/calaos-container/DEBIAN/postinst new file mode 100755 index 0000000..a2e1850 --- /dev/null +++ b/debian/calaos-container/DEBIAN/postinst @@ -0,0 +1,32 @@ +#!/bin/sh +set -e +# Automatically added by dh_installsystemd/13.11.4 +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + # The following line should be removed in trixie or trixie+1 + deb-systemd-helper unmask 'calaos-container.service' >/dev/null || true + + # was-enabled defaults to true, so new installations run enable. + if deb-systemd-helper --quiet was-enabled 'calaos-container.service'; then + # Enables the unit on first installation, creates new + # symlinks on upgrades if the unit file has changed. + deb-systemd-helper enable 'calaos-container.service' >/dev/null || true + else + # Update the statefile to add new symlinks (if any), which need to be + # cleaned up on purge. Also remove old symlinks. + deb-systemd-helper update-state 'calaos-container.service' >/dev/null || true + fi +fi +# End automatically added section +# Automatically added by dh_installsystemd/13.11.4 +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + if [ -n "$2" ]; then + _dh_action=restart + else + _dh_action=start + fi + deb-systemd-invoke $_dh_action 'calaos-container.service' >/dev/null || true + fi +fi +# End automatically added section diff --git a/debian/calaos-container/DEBIAN/postrm b/debian/calaos-container/DEBIAN/postrm new file mode 100755 index 0000000..f004f32 --- /dev/null +++ b/debian/calaos-container/DEBIAN/postrm @@ -0,0 +1,14 @@ +#!/bin/sh +set -e +# Automatically added by dh_installsystemd/13.11.4 +if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section +# Automatically added by dh_installsystemd/13.11.4 +if [ "$1" = "purge" ]; then + if [ -x "/usr/bin/deb-systemd-helper" ]; then + deb-systemd-helper purge 'calaos-container.service' >/dev/null || true + fi +fi +# End automatically added section diff --git a/debian/calaos-container/DEBIAN/prerm b/debian/calaos-container/DEBIAN/prerm new file mode 100755 index 0000000..f192993 --- /dev/null +++ b/debian/calaos-container/DEBIAN/prerm @@ -0,0 +1,7 @@ +#!/bin/sh +set -e +# Automatically added by dh_installsystemd/13.11.4 +if [ -z "${DPKG_ROOT:-}" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + deb-systemd-invoke stop 'calaos-container.service' >/dev/null || true +fi +# End automatically added section diff --git a/debian/calaos-container/lib/systemd/system/calaos-container.service b/debian/calaos-container/lib/systemd/system/calaos-container.service new file mode 100644 index 0000000..2a64909 --- /dev/null +++ b/debian/calaos-container/lib/systemd/system/calaos-container.service @@ -0,0 +1,11 @@ +[Unit] +Description=Calaos-OS container daemon +After=network.target + +[Service] +Type=simple +ExecStart=/usr/bin/calaos-container +Restart=always + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/debian/calaos-container/usr/bin/calaos-container b/debian/calaos-container/usr/bin/calaos-container new file mode 100755 index 0000000..47e48a4 Binary files /dev/null and b/debian/calaos-container/usr/bin/calaos-container differ diff --git a/debian/calaos-container/usr/bin/x11docker b/debian/calaos-container/usr/bin/x11docker new file mode 100755 index 0000000..1335e24 --- /dev/null +++ b/debian/calaos-container/usr/bin/x11docker @@ -0,0 +1,11640 @@ +#! /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.1-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:-}" =~ ^[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 + + command -v jq >/dev/null && { + Jsonstring="${1:-}" ; shift + [ "${Jsonstring:0:1}" = "[" ] && Keystring=".[]" || Keystring="" + while [ $# -gt 0 ]; do + Keystring="$Keystring.${1:-}" + shift + done + Output="$(jq "$Keystring" <<< "$Jsonstring")" + while [ "${Output:0:1}" = "[" ]; do + Output="$(jq '.[]' <<< "$Output" )" + done + [ "$Output" = "null" ] && Output="" + [ "${Output:0:1}" = '"' ] && Output="${Output:1:-1}" + echo "$Output" + return + } + + Parserscript="$Cachefolder/parse_inspect.py" + Parserscript="#! $Pythonbin +$(cat << EOF +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() + if inspect[0] == "[" : + inspect=inspect[1:-2] # remove enclosing [ ] + + obj=json.loads(inspect) + + 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=output+"'"+str(i)+"' " + else: + output=str(obj) + + if output == "None": + output="" + + print(output) + +parse_inspect(*sys.argv) +EOF + )" + 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: Connect over tcp or with shared socket + # g $Pulseaudioport TCP port + local Lowerport= Upperport= + + 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=host instead." + ;; + esac + + case "$Pulseaudiomode" in + socket) + # create pulseaudio socket + Pulseaudiomoduleid="$(unpriv "pactl load-module module-native-protocol-unix socket='$Pulseaudiosocket' 2>&1")" + + [ "$Pulseaudiomoduleid" ] && { + storeinfo "pulseaudiomoduleid=$Pulseaudiomoduleid" + store_runoption env "PULSE_SERVER=unix:$(convertpath share "$Pulseaudiosocket")" + store_runoption env "PULSE_COOKIE=$(convertpath share "$Pulseaudiocookie")" + } || { + note "Option --pulseaudio: command pactl failed. + Is pulseaudio running at all on your host? + Fallback: 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" + ;; + 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" +# store_runoption env "PULSE_COOKIE=$(convertpath share "$Pulseaudiocookie")" + ;; + esac + 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 + + # 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")" + debugnote "Image WORKDIR: $Workdir" + [ -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 + echo " +debugnote 'cmdrc: running --runsasuser command: + $Line' +" + echo " +$Line +" + done <<< "$(grep . <<< "$Runasuser")" + } + + 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_host(): 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 + + # 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 diff --git a/debian/calaos-container/usr/share/doc/calaos-container/changelog.Debian.gz b/debian/calaos-container/usr/share/doc/calaos-container/changelog.Debian.gz new file mode 100644 index 0000000..8c1c9b9 Binary files /dev/null and b/debian/calaos-container/usr/share/doc/calaos-container/changelog.Debian.gz differ diff --git a/debian/control b/debian/control index f3c83f7..93c665a 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,8 @@ Source: calaos-container Maintainer: Calaos Team Build-Depends: debhelper (>= 11), - golang + pkgconf, golang, + libbtrfs-dev, libdevmapper-dev, libgpgme-dev Package: calaos-container Architecture: any diff --git a/debian/debhelper-build-stamp b/debian/debhelper-build-stamp new file mode 100644 index 0000000..d5b7308 --- /dev/null +++ b/debian/debhelper-build-stamp @@ -0,0 +1 @@ +calaos-container diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..de83f79 --- /dev/null +++ b/debian/files @@ -0,0 +1,3 @@ +calaos-container-dbgsym_1.0.0-0~bookworm0_amd64.deb debug optional automatic=yes +calaos-container_1.0-0_amd64.buildinfo - - +calaos-container_1.0.0-0~bookworm0_amd64.deb - - diff --git a/debian/rules b/debian/rules old mode 100644 new mode 100755 index 3c18b34..d4a1570 --- a/debian/rules +++ b/debian/rules @@ -5,9 +5,9 @@ VERSION = 1.0.0 PACKAGEVERSION = $(VERSION)-0~$(DISTRIBUTION)0 %: - dh $@ + dh $@ export GOCACHE=/tmp override_dh_gencontrol: - dh_gencontrol -- -v$(PACKAGEVERSION) \ No newline at end of file + dh_gencontrol -- -v$(PACKAGEVERSION) \ No newline at end of file