dotfiles/app_containers/.local/bin/run_app_container

139 lines
4.7 KiB
Bash
Executable File

#!/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 <https://liolok.com/run-desktop-app-with-systemd-nspawn-container>
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 $@