Hello, this is a continuation of my previous post where I explained how to Set-Up a Remote Desktop experience fully encrypted for you or friends to use in your Machine

In that post I received a comment basically asking for what we’re about to do and I believed it to be a cool idea to integrate Containers to basically be able to spin-up Full Virtual Desktops on demand, with the Pros and Cons that entails (more on that at the end); so

Let’s establish things

  • We’re using the previous post as a baseline, I’ll assume everything there has been Set-Up as explained and we’ll focus on understanding and fabricating the Container here and the SSH environment; If you have read and done what’s in the post previously, please take a look again I have updated it to a better version that’s basically required for things to work here, particularly pipewire
  • Everything established on the previous post holds true, we will be using wlroots Wayland; Pipewire, SSHFS and a Terminal Emulator for all our interactive needs
  • I’ll be using Podman in Arch Linux with an Arch Linux container image, it would be best you stick to an image of your same Distribution

Where do we start?

First we better install Podman as things are straight up not going to work without it so refer to your Distribution’s instructions on how to install it and get rootless support going, it’s really easy

Now that the hard part is done let’s get to Build a Container Image, we will be building an image for each user we want to have to make things easy and have a reproducible environment should the user want to restart their Space

We have to build a Containerfile like this;

# Base distribution image to use
FROM archlinux 
# Set the user name with a variable passed from the shell
ARG USER=$USER  
# Create the Home Folder in the image
WORKDIR /home   
# Create the user Home folder in the image
WORKDIR ${USER} 
# Distro-Specific set-up for the package manager inside the image
RUN pacman-key --init  
# Distro-Specific set-up for the package manager inside the image
RUN pacman-key --populate archlinux  
# Installing base user packages inside the image
RUN pacman -Syu base-devel sudo bash htop git nano fastfetch curl wget --noconfirm --needed 
# Making ALL users able to use SUDO
RUN echo "ALL ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers  
# Copy userspace initialization script into the image
COPY ./initialization.sh /initialization.sh
# Make initialization script executable inside the image
RUN chmod +x /initialization.sh 
# Set the user's default entrypoint to the BASH shell
CMD /bin/bash 

Take a moment to read the comments on each of the Lines, as we preferably adjust things to match our Distribution of preference, all Distro-Specific steps should be replaced to match what your Distribution needs, and also what you want there to be on container for your user available from the get-go

There are 3 elements to take note of;

  • ARG USER=$USER; this will be a variable passed for usage during the build-time of the image and it’s only use is to create the user’s Home, as this is the best moment to do so
  • RUN echo “ALL ALL=(ALL:ALL) NOPASSWD:ALL” >> /etc/sudoers; this will make all Users able to use SUDO which might seem risky at first but don’t worry this will be undone as soon as the User gets control of the container; it is only for the Set-up Process to function
  • COPY ./initialization.sh /initialization.sh and RUN chmod +x /initialization.sh; This is a script we’re going to write next that will set-up the Container to be appropriately used like a Bare-Metal installation

Finished customizing your Containerfile to your needs? Next up we’ll write that script I mentioned like this

echo "Welcome to your Personal Container, This script will ensure Userspace is properly initialized"
if [ "x${SUDO_USER}" = "x" ]; then
  echo "This script is not being run with SUDO, run it with SUDO or edit it to work with whatever elevated command system you're using"
  exit
fi

rm /home/"$SUDO_USER"/.bashrc
cp -r /etc/skel/. /home/"$SUDO_USER"
#echo "Please input your password for SUDO within the container"
#passwd $SUDO_USER
export PASSWORD="changeme"
echo "$SUDO_USER:$PASSWORD" | chpasswd
echo "$USER:$PASSWORD" | chpasswd
echo "Your (and root's) password within the container is: $PASSWORD"
echo "You can (and should) change it by invoking 'passwd' on both accounts!"

chown -R "$SUDO_USER" /home/"$SUDO_USER"
sed -i '$d' /etc/sudoers
echo "ALL ALL=(ALL:ALL) ALL" >> /etc/sudoers
echo "All done, this should be usable now!"
rm $0

This script is rather simple, but important, you see Containers are not usually built to be comfortable User Spaces but instead to be fast and discard-able, this is cool but with a bit of tweaking we can make it suit our needs, this is all that tweaking required all within the Container;

  • We initialize the User’s Home
  • We give a default Password to the user and root to be able to restrict SUDO to only users who know their Passwords
  • We restrict SUDO as mentioned and prompt the user to change their password
  • We delete the Script

With the Initialization Script and Containerfile written up and saved in the same directory we have to give the User’s account access to reading them both, I personally just copy them to their Home Folder; You can spin it to them however you’d like

Since Podman is being used rootless, we naturally want to Switch to that User’s account (unless we already ARE the user) and get things set-up

First we Build the Container image with a recognizable name to make things easier for us AND passing the user’s name as a Build Flag as we expect in our Containerfile, such a command could go like;

podman build --tag "$USER"_container --build-arg USER="$USER" </directory/to/containerfilefolder>

If that succeeds with no errors, we’re basically done, next we should make the user’s home inside the container a folder that’s inside their real Home, so that all their files are accessible through Remote File Sharing; so for example

sshfs_directory="$HOME"/."$USER"_container
mkdir -p "$sshfs_directory"

And then the indispensable part about this, we make the user’s container run the initialization script as sudo first of all on first start by adding it to their .bashrc

echo "sudo /initialization.sh" >> "$sshfs_directory"/.bashrc

As the script removes both .bashrc and itself this won’t happen every time, only the first run of the Container

Finally we’re ready to create the Container, I have compiled this podman create command with everything that’s necessary so like;

podman create -ti \ # Make the Container interactable 
--mount type=bind,src="$XDG_RUNTIME_DIR",dst="$XDG_RUNTIME_DIR" \ # Mount Real user's runtime to the Container user's runtime, allowing Remote Clients to connect into the Container
--mount type=bind,src="$sshfs_directory",dst=/home/"$USER" \ # Mount Real user's Home Container Directory into the Container's user Home directory
--env=XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ # Pass the Real user's runtime directory variable to the Container's, needed for all Runtime applications to work
--env=USER="$USER" \ # Set the user as the USER envrionment variable inside the Container like all SHELL logins do which is expected by a lot of things
--env=SHELL="$SHELL" \ # Set the Container's SHELL environment variable which is really useful
--device /dev/dri:/dev/dri \ # Give the container access to the Graphics Hardware of the Machine
--device /dev/snd:/dev/snd \ # Give the container access to the Audio Hardware of the Machine
--userns keep-id:uid=$(id -u),gid=$(id -g) \ # Run the Container as a full-copy of the user which is super necessary for things like running Wayland and Accessing container Files from Local or Remote
--name "$USER"_container \ # Naming the container appropiately to easily identify and start or remove it
--hostname "$USER"_container \ # Naming the container's runtime to be easily identified
localhost/"$USER"_container # Use the container image we just built

After you’re done understand what’s going on, make sure to delete all the comments (The # and everything after each line) otherwise the command will fail

This step can take some time, as Podman has to do quite some processing to put the User’s namespace to use within the image given, so give it time

After that succeeds, our user could log-in through SSH and start using the Container IF they’re not gonna use Wayland or Audio, we have to set-up both of these for the Container to use after each log-in, this is easy, we just have to add some lines to the real user’s .bash_profile (outside the container!) to do everything on each SSH log-in for example;

if ! [ "x${SSH_TTY}" = "x" ]; then
  export PULSE_SERVER=unix:$(find $XDG_RUNTIME_DIR/pulse-server-* | tail -n 1)
  podman update --env PULSE_SERVER=$PULSE_SERVER "$USER"_container
  if ! [ "x${WAYLAND_DISPLAY}" = "x" ]; then
    podman update --env WAYLAND_DISPLAY=$WAYLAND_DISPLAY "$USER"_container
  else
    podman update --unsetenv WAYLAND_DISPLAY "$USER"_container
  fi
  podman start -a "$USER"_container
  exit
else
  podman update --unsetenv PULSE_SERVER "$USER"_container
  podman update --unsetenv WAYLAND_DISPLAY "$USER"_container
fi

This is a modified version of the .bash_profile explained in the first post, here we just add commands to update the container’s environment variables as needed since these are volatile on each SSH log-in and are required for Wayland and Audio to work properly

Also take notice, we have added “podman start -a “$USER”_container” and “exit” one after the other on .bash_profile, this basically jails the user into using the Container on SSH log-in as their session will only start once they are inside it and be immediately terminated once they exit it, they will be unable to do ANYTHING outside the Container and to me this is desirable behavior, if you’re not into that you can remove it and the container will just be an option to them

However should you also wish to do this, make sure to keep .bash_profile read-only to the user as with remote file access they can modify it by accessing it through Remote File Sharing

Speaking of Scripts, how does that look for the user? It mostly looks the same as the first post but just for a refresher here’s an example script for connecting to our Remote Host;

mkdir -p "$HOME"/.remote_files
sshfs -p 7777 [email protected]:"$HOME"/."$USER"_container "$HOME"/.remote_files
waypipe --video h264 ssh -p 7777 -l user -R $XDG_RUNTIME_DIR/pulse-server-"$RANDOM":$XDG_RUNTIME_DIR/pulse/native 192.168.100.2
umount "$HOME"/.remote_files

And that’s it, we’re done, next log-in through SSH your user will be dropped inside a Container, where as far as I’ve tested, works pretty well for a Virtual Desktop

The obvious advantage of doing all of this is

  • The User can install and run any software without requiring root to intervene as they can do anything they want on the Container
  • The Administrator can easily limit the Resources a guest user can access (CPU, RAM, GPU) with podman commands should they wish to offer an affordable workspace for them (check the documentation for this)
  • If the User’s space is compromised, the problem is only as big as the user’s access to the real user’s permissions
  • As opposed to Virtual Machines, this set-up only occupies resources on demand and has minimal overhead

While the obvious disadvantages basically summarize as It’s not as flexible as a Virtual Machine

But I still find it novel and useful for testing software before committing but for example you could donate me a container like this in your machine since I could use the processing power ;) <shameless-plug>off</shameless-plug>

That’s all from me today, Have suggestions? Let me know what can be done better and I’ll update this post, thanks for reading and have a good one

Author @[email protected]