Remote Desktop for Pair Programming Interview

Some of the best interview processes I've been through have required me to write code - the job will require coding, so it is only natural to check people can actually code prior to making a hire. I've found that companies that do this typically have great competent colleagues, and as a result tend to be highly productive environments. To allow me to adopt a similar process with remote candidates, I've created a shared cloud desktop setup that we use to evaluate interview candidate's Python credentials. I thought it might be beneficial for others so I am sharing it here.

About This Setup

We use Apache Guacamole running on a standard Linux Virtual Machine on Digital Ocean. This involves running X on VNC, and then we set up a Python environment with a suitable set of packages and then install VS Code and Jupyter Lab. There are a few driving forces behind these choice:

Concurrent: Two or more people can be remoted in to the cloud desktop at the same time, which is critical for pair-programming interviews.

Usability: The low latency means that both interviewer and interviewee can interact with the code almost as if they were sitting side by side.

Cheap: Our setup can be run on standard cloud VMs for well under $1/hour.

Browser Accessibility: One of the key goals was to minimize the setup required for the candidate. Providing access through the browser means no cumbersome software installation, allowing us to focus on the task at hand.

Security: Avoiding sharing desktops eliminates the possibility of a candidate potentially seeing sensitive information in email notifications, for example. Also, because it is relatively quick to create a new environment, we can tear it down after each interview.

Compared to alternatives like Zoom and Teams, we like the low latency of our approach - the remote desktop would be quite usable as an everyday environment. We also looked at using VDI provider such as AWS WorkSpaces, together with remote connectivity software such as Team Viewer, but disliked the subscription cost for solutions that would sit idle most of them time. Let me just say that I think those are all great solutions, but they are great solutions for other problems - we wanted to create a usable but disposable sandbox development environment.

My Interviewing Philosophy

When I interview for Python-related roles, my objective isn’t to set up a series of hoops for candidates to jump through. Instead, it's about witnessing firsthand how a potential team member thinks, codes, and collaborates. Here's my approach:

Hands-on Collaborative Exercises: Different positions require varied skill sets. Depending on the role, candidates might find themselves cleaning up a dataset using pandas, diving deep into data analysis, or designing an algorithm. We aim to make the tasks representative - we want assess skills that will actually be used on the job.

Open Book Policy: The real world isn't an examination hall. Developers routinely consult Google, StackOverflow, or tap the collective wisdom of their peers. Recognizing this, our exercises are open book. Candidates can freely seek out resources or even ask me questions directly.

Supportive Environment: I'm acutely aware of the stress associated with coding in an observed environment. That's why, if I spot errors that are going to eat time, I'll step in, offering fixes and guidance. The goal isn't to trip up the candidate but to put them in the best light to see how they respond, adapt, and move forward.

How to create the shared cloud desktop

To create a shared cloud desktop:

  1. Modify the shell script below. Specifically you will want to replace GUACAMOLE_USER, GUACAMOLE_PASSWORD, VNC_PASSWORD and UNIX_USER_PASSWORD Remember them - you will need them later. Use temporary passwords - you will have to provide GUACAMOLE_USER and GUACAMOLE_PASSWORD to the candidate.
  2. Create a Digital Ocean droplet. Use an Ubuntu 22.04 LTS x64 image. Make sure you provide an appropriate authentication method. This recipe has been tested on a Digital Ocean droplet with 4 CPUs and 16GB RAM. It takes about 7 minutes for the script to run. You can probably use other VMs or other OS versions, but you will probably need to adapt.
  3. Connect to your VM with ssh. Log in as root.
  4. Copy the shell script to your virtual machine. The simplest way to do this is to run nano, copy the text in, then ctrl-O to save and ctrl-X to exist.
  5. Do chmod 700 and ./ to run the script. It will install packages, build Guacamole, configure Tomcat, install Chrome and VS Code, create a user called "ed", and create a virtual environment as "ed".
  6. Switch to user "ed" (su - ed) and run VNC with vncserver :1 -geometry 1920x1080. Guacamole will connect to this session and serve it over the web. You will need to provide the VNC_PASSWORD from step 1 - if these do not match, guacamole will not be able to connect to VNC and expose the desktop to the web.
  7. In your browser, go to http://[DROPLET IP]:8080/guacamole and log in with your GUACAMOLE credentials that you set in step 1. You should now see the desktop environment in your browser.
  8. Start a terminal in the desktop and do source ~/jupyter_env/bin/activate to activate the Python virtual environment and run jupyter-lab --browser=google-chrome to start Jupyter.
  9. You can run VS Code from the start menu at the bottom-left of the screen.
  10. Give the candidate the url http://[DROPLET IP]:8080/guacamole and the GUACAMOLE_USER and GUACAMOLE_PASSWORD so they can connect too.
  11. Enjoy your shared session!

set -euxo pipefail
trap 'echo "Error: Command failed. Exiting." >&2' ERR

if [[ $EUID -ne 0 ]]; then
    echo "This script must be run as root"
    exit 1

export DEBIAN_FRONTEND=noninteractive
apt update
apt upgrade -y --no-install-recommends
apt install -y build-essential libcairo2-dev libjpeg-turbo8-dev libpng-dev libtool-bin \
    libossp-uuid-dev libvncserver-dev freerdp2-dev libssh2-1-dev libtelnet-dev libwebsockets-dev \
    libpulse-dev libvorbis-dev libwebp-dev tomcat9 tomcat9-admin tomcat9-common tomcat9-user nginx \
    libavcodec-dev libavutil-dev libswscale-dev libfreerdp-client2-2 libpango1.0-dev libssh-dev \
    libssl-dev libvorbis-dev libwebp-dev python3-pip xfce4 xfce4-goodies \
    tightvncserver lxde tmux python3.10-venv software-properties-common apt-transport-https wget

wget -O guacamole-server-1.5.3.tar.gz
tar -xvf guacamole-server-1.5.3.tar.gz
pushd guacamole-server-1.5.3/
./configure --with-init-dir=/etc/init.d
make install
systemctl enable guacd
systemctl start guacd

wget -O  guacamole-1.5.3.war
mkdir -p /etc/guacamole
mv guacamole-1.5.3.war /etc/guacamole/guacamole.war
ln -s /etc/guacamole/guacamole.war /var/lib/tomcat9/webapps/
mkdir -p /etc/guacamole/{extensions,lib}

echo "guacd-hostname: ::1
guacd-port: 4822
user-mapping: /etc/guacamole/user-mapping.xml" > /etc/guacamole/

mkdir -p /usr/share/tomcat9/.guacamole/
ln -s /etc/guacamole/ /usr/share/tomcat9/.guacamole/

cat > /etc/guacamole/user-mapping.xml <<EOL
    <authorize username="GUACAMOLE_USER" password="GUACAMOLE_PASSWORD">
        <connection name="Ubuntu Desktop">
            <param name="hostname">localhost</param>
            <param name="port">5901</param>
            <param name="password">VNC_PASSWORD</param>

ufw allow 4822,8080,8888/tcp

apt install ./google-chrome-stable_current_amd64.deb -y

wget -q -O- | apt-key add -
add-apt-repository -y "deb [arch=amd64] stable main"
apt install code

systemctl restart tomcat9

if id "ed" &>/dev/null; then
    echo "User ed already exists. Skipping user creation."
    useradd -m ed -s /bin/bash
    echo "ed:UNIX_USER_PASSWORD" | chpasswd

su - ed -c "mkdir -p ~/.vnc"
echo '#!/bin/sh
xrdb $HOME/.Xresources
startlxde &' > /home/ed/.vnc/xstartup
chown ed:ed /home/ed/.vnc/xstartup
chmod +x /home/ed/.vnc/xstartup

cat > /home/ed/requirements.txt <<EOL
chown ed:ed /home/ed/requirements.txt

su - ed -c "python3 -m venv ~/jupyter_env"
su - ed -c "source ~/jupyter_env/bin/activate && pip install -r ~/requirements.txt"
su - ed -c "mkdir ~/notebooks"

echo << EOL
    Run as ed: vncserver :1 -geometry 1920x1080
    Then connect to http://[DROPLET IP]:8080/guacamole and run the following in a terminal:
    source ~/jupyter_env/bin/activate
    jupyter-lab --browser=google-chrome