Docker Desktop Launcher
2019-04-10 14:39:08 +0000 UTCI 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