Docker Desktop Launcher

2019-04-10 14:39:08 +0000 UTC

I recently started using docker images for all my desktop applications and encountered an issue wherein I need apps in different containers to launch or send messages to others. Of course this is easy to do when using containers in the traditional sense and passing data via network connections or UNIX sockets, but not so much when you want your email app to open a link in your browser app.

To solve this issue a created a crude “launcher” system using UNIX sockets.

The systemd service

To create the server end on the host machine I used bash scripting and systemd’s simple service this comprises of two files

File:~/.config/systemd/user/dlaunch@.service, Contents:

[Unit]
Description=Docker Laucher Service
After=network.target dlaunch.socket
Requires=dlaunch.socket

[Service]
Type=simple
ExecStart=/bin/bash %h/.local/bin/dlaunch
StandardInput=socket
StandardError=journal
TimeoutStopSec=5

[Install]
WantedBy=default.targetbash

File: ~/.config/systemd/user/dlaunch.socket, Contents:

[Unit]
Description=Docker Launch Socket
PartOf=dlaunch.service

[Socket]
ListenStream=%t/cyberdummy/dlaunch
Accept=yes

[Install]
WantedBy=sockets.target

Then install this new service with the commands

systemctl --user daemon-reload
systemctl --user start dlaunch@.service
systemctl --user enable dlaunch@.service

What this does is have systemd create a UNIX socket in ${XDG_RUNTIME_DIR}/cyberdummy/dlaunch which usually equates to /var/run/user/1000/cyberdummy/dlaunch, when a connection is made to this socket systemd will then call the command ${HOME}/.local/bin/dlaunch and allow it to deal with the connection.

Essentially allowing us to have a simple UNIX socket powered by bash without have to do any heavy lifting.

The host socket script

Next I created a bash script to handle the socket connection all this script needed to do was read the first “line” (anything up to a newline) written to the socket parse it and act upon the parsed data.

File ~/.local/bin/dlaunch, Contents:

#!/bin/bash
set -euo pipefail

file=~/.config/dotdummy/docker.sh

if [[ -r "$file" ]] && [[ -f "$file" ]]; then
    source "$file"
fi

read -r line

action=`echo "${line}" | awk '{print $1;}'`

if [ "${action}" == "mpv" ]
then
    mpv ${line:4}
elif [ "${action}" == "browser" ]
then
    qutebrowser "${line:8}"
elif [ "${action}" == "feh" ]
then
    feh ${line:4}
fi

To break it down:

Lines 4-8 source my docker aliases script which swaps out commands like mpv, qutebrowser etc for their docker run ... equivalent. See here for examples.

Lines 10-12 collect the first line written to the socket, then parse it to see what the first “word” is. An example line would look like:

browser http://www.google.co.uk where the first word is “browser”.

Lines 13-end execute the desired command based on the action and place anything remaining on the line after the action as arguments to the command.

Making it work in the containers

In order to enable the containers to use this service you have to share the socket with them which is easy enough you can just use the volume mounting option.

docker run -ti \
    -v ${XDG_RUNTIME_DIR}/cyberdummy/dlaunch:/tmp/dlaunch \
    somecontainer

Now all that remains is to give the containerized app an easy way to use the socket as if they were just launching it natively. To do this I mount a simple script that acts as if it was the local binary but instead sends the arguments down the socket.

File ~/.local/bin/dlaunch-browser (ensure to set the executable flag chmod +x ~/.local/bin/dlaunch-browser), Contents:

echo "browser $1" | nc -U /tmp/dlaunch

Then I mount this file into the container and for the browser set the $BROWSER environment variable to point to it, if that is not suitable just update whatever configuration is required to point at this “binary”.

docker run -ti \
    -v ${XDG_RUNTIME_DIR}/cyberdummy/dlaunch:/tmp/dlaunch \
    -v ${HOME}/.local/bin/dlaunch-browser:/usr/bin/browser \
    -e "BROWSER=/usr/bin/browser" \
    somecontainer

Sharing files

Sometimes an application wants to share files with another application for this purpose I just mount a shared directory and set the $TMPDIR environment variable.

docker run -ti \
    -v "/tmp/dummy/:/tmp/dummy" \
    -e "TMPDIR=/tmp/dummy" \
    somecontainer