#!/usr/bin/env bash die() { echo "$1" >&2 exit 1 } cleanup() { rm -rf "$container_xdg_runtime" # Remove the temporary facl-based permissions setfacl -x u:$((user_on_host)) $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY xhost -si:localuser:\#$((user_on_host)) } [ -z "$CONTAINER_NAME" ] && die "\$CONTAINER_NAME not set" [ -z "$DISPLAY" ] && die "\$DISPLAY not set (you must run the script in a desktop environment" # Use a GUI-available askpass program for sudo # This should be made configurable export SUDO_ASKPASS=$HOME/.local/bin/askpass-bemenu # Source configuration files if any config="$HOME/.config/app_containers/$CONTAINER_NAME.sh" if [ -f "$config" ]; then source "$config" fi # Kill any previously running instances # This could be due to, say, a broken libappindicator icon (because of broken dbus) sudo -A machinectl kill $CONTAINER_NAME > /dev/null 2>&1 # Create a XDG_RUNTIME_DIR for guest on host container_xdg_runtime="$(mktemp -d -p /var/tmp)" trap cleanup EXIT # Link the current wayland session to the container's xdg runtime # Note that the session itself must be bind-mounted first ln -s /run/host/$WAYLAND_DISPLAY $container_xdg_runtime/$WAYLAND_DISPLAY # From if [[ -n $DBUS_SESSION_BUS_ADDRESS ]]; then # remove prefix host_bus=${DBUS_SESSION_BUS_ADDRESS#unix:path=} else # default guess host_bus=$XDG_RUNTIME_DIR/bus fi if [[ -n $PULSE_SERVER ]]; then # remove prefix host_pulse=${PULSE_SERVER#unix:} else # default guess host_pulse=$XDG_RUNTIME_DIR/pulse/native fi [ -S $host_pulse ] || die "PulseAudio UNIX socket not found" # Default user run_as=$UID if [ "$CONTAINER_RUN_AS_ROOT" = true ]; then run_as=0 fi homedir=/ for line in $(sudo -A cat /var/lib/machines/$CONTAINER_NAME/etc/passwd); do if [ "$(echo "$line" | cut -d: -f3)" == "$run_as" ]; then homedir="$(echo "$line" | cut -d: -f6)" break fi done # Userns-related config # Default to identity mapping, which does not provide uid isolation but does for capabilities private_users=identity bind_opts="" user_on_host=$run_as if [ "$CONTAINER_USE_USERNS" = true ]; then private_users=$(shuf -i 65536-$((2147483647 - 65536)) -n1) # Pick a random starting offset bind_opts="idmap" user_on_host=$((private_users + run_as)) # TODO: DBus is broken inside a user namespace due to permission issues # And in fact, in any case when the UID inside the container does not # match the one on the host. # TODO: Fix this, somehow. fi # Grant the user inside the container access to the Wayland / Xorg display # For the Wayland socket, a simple facl rule would suffice # For Xorg, we need to use the `xhost` facilities setfacl -m u:$user_on_host:rwx $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY xhost +si:localuser:\#$user_on_host # Grant the user inside the container access to input devices # Note: any new device plugged in when the container is running would not # be added properly here. for input in $(find /dev/input -type c); do sudo -A setfacl -m u:$user_on_host:rw- $input done # Bind-mounts defined by the user (possibly in the container-specific config file) # Format should be "src:target". target cannot be omitted for mount in ${CONTAINER_BIND_MOUNTS[@]}; do SYSTEMD_NSPAWN_EXTRA_ARGS+=" --bind=$mount:$bind_opts" done sudo -A systemd-nspawn -M $CONTAINER_NAME \ --private-users=$private_users --private-users-ownership=map \ `# DNS (when containers do not have their own netns)` \ --bind-ro=/run/systemd/resolve/stub-resolv.conf:/etc/resolv.conf \ `# GPU` \ --bind=/dev/dri \ --property=DeviceAllow='char-drm rw' \ `# Input devices` \ --bind-ro=/dev/input \ --property=DeviceAllow='char-input r' \ `# Xdg runtime` \ --bind=$container_xdg_runtime:/run/xdg:$bind_opts \ --setenv=XDG_RUNTIME_DIR=/run/xdg \ `# Xorg / Xwayland` \ --bind=/tmp/.X11-unix \ --setenv=DISPLAY=$DISPLAY \ `# Wayland (note the symlink created before in xdg runtime)` \ --bind-ro=$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/run/host/$WAYLAND_DISPLAY \ --setenv=WAYLAND_DISPLAY=$WAYLAND_DISPLAY \ `# PulseAudio` \ --bind-ro=$host_pulse:/run/host/pulse/native \ --setenv=PULSE_SERVER=unix:/run/host/pulse/native \ `# DBus` \ --bind-ro=$host_bus:/run/host/bus \ --setenv=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/host/bus \ `# Scaling (GDK_SCALE is needed for Java as well)`\ --setenv=GDK_SCALE="$GDK_SCALE" \ `# Make applications prefer Wayland when possible` \ --setenv=XDG_SESSION_TYPE=wayland \ `# Hacks for Proton (enable logging, disable futex-based synchronization)` \ --setenv=PROTON_LOG=1 \ --setenv=PROTON_NO_FSYNC=1 \ `# Extra params` \ $SYSTEMD_NSPAWN_EXTRA_ARGS \ `# Launch app` \ --user=$run_as --chdir=$homedir --as-pid2 $@