4 min read

2026

I have no particular '26' theme for Paper Tiger beyond hoping to deliver something worth reading about once every two weeks. TV Guide is now published only once every three weeks, so it feels like there's room to grow into this niche.

Software frustrates me. That the default unit of computing has become a whole software instead of a snippet or an expression frustrates me. That the problems of software are so intractable that a software is now a container frustrates me. That I'm a frustrated old man living in a containerized world ... meta-frustrates me. Fortunately, this is a moment where such artificially intelligent havoc is upon us that we may yet get to start over. I mean, after anyway.

Amid this frustration, I recently decommissioned my main home machine and replaced it with a Raspberry Pi in a cupboard. In fact, I had long wanted to replace it with a Raspberry Pi but I was never able to make a Raspberry Pi work as both a reliable computer that provided a consistent computing environment and a box into which I could plug a random device that made its way into my collection. The answer, obviously, was two Raspberry Pis.

This is where Plan 9 enters the chat. I didn't want a workstation to use with hardware and a separate file server. I wanted the opposite. I wanted a reliable server I could reach from anywhere and a way to project that computing environment forward to perhaps several places where some hardware happened to be. Sometimes, I want to push the environment towards a piece of hardware. Sometimes I want to pull it towards a comfortable and powerful interactive workstation where I happen to be typing.

Plan 9 could sort of do that. A modern Linux system has enough apparatus that it can pretend to be the parts of Plan 9 that did that. How does it work? Well, the first part of it is completely ordinary. Install Raspbian or whatever on one (1) Raspberry Pi and mount your home drive. Enable SSH access and essentially nothing else. Build the program ‘u9fs’. You have a headless computer that can do whatever you want and also serve the Plan 9 filesystem protocol. Hack and enjoy over ethernet but don’t touch.

Install essentially whatever you want on the second pi. GUI desktop? Sure. Interesting peripherals? Certainly! No need to harmonize anything on Pi #2 with Pi #1 except to ensure you can connect from one to the other and that you place about the same trust in both.

On Pi #1, go grab and build a copy of u9fs and put it in your path. I use this one:

GitHub - unofficial-mirror/u9fs: Unofficial mirror of plan9-from-bell-labs/u9fs
Unofficial mirror of plan9-from-bell-labs/u9fs. Contribute to unofficial-mirror/u9fs development by creating an account on GitHub.

On Pi #2, log in as whatever sudo-capable user you created, and then run a script like this.

#!/bin/bash

# pull an environment from a remote system

INFERIOR=false ; if [ "$1" = "--inferior" ] ; then INFERIOR=true ; shift ; fi

fatal() {
  echo "$1" 1>&2 ; exit 1
}

if [ -z "$1" ] ; then
  echo "no dest" 1>&2
  fatal "usage: $0 <DEST>"
else
  DEST="$1"
fi

if [ "${INFERIOR}" = false ] ; then
  # alas, we need some privilege to mount 9p on linux
  exec sudo unshare --pid --fork -m "$0" --inferior "${DEST}"
  fatal "exec failed"
fi
# else running inferior

OBOE_TMP=`mktemp -d`

declare -a OBOE_MOUNTS

domount() {
  MP="$1" ; shift ; if [ ! -f "${MP}" ] ; then mkdir -p "${MP}" ; fi &&\
    mount -n "$@" "${MP}" && OBOE_MOUNTS+=("${MP}")
}

cleanup() {
  ML="${#OBOE_MOUNTS[@]}"
  for (( i=0 ; i<${ML} ; i++ )) ; do
    umount -n -l "${OBOE_MOUNTS[$(( ${ML} - ($i + 1) ))]}"
  done
}

domount "${OBOE_TMP}" -t tmpfs tmpfs || fatal "unable to mount oboe tmp"

OBOE_SSH_SOCK="${OBOE_TMP}/foosock"

# ssh on some systems has perm trouble when called in a namespace
subsh() {
  ssh -F /dev/null -S "${OBOE_SSH_SOCK}" "$@"
}

# establish a master connection
subsh -M -N "${DEST}" -o "ControlPersist=4h" /bin/true || \
  fatal "couldn't create ssh master session"

REMOTE_USER=`subsh foohost sh -c "/bin/echo\ \\\${USER}"`
REMOTE_UID=`subsh foohost sh -c "/bin/echo\ \\\${UID}"`
REMOTE_GID=`subsh foohost sh -c "/bin/cat\ /proc/self/status" | \
  grep "Gid:" | awk '{print $2}'`
REMOTE_HOME=`subsh foohost sh -c "/bin/echo\ \\\${HOME}"`
REMOTE_U9FS="u9fs"

# a smarter use of coproc could eliminate the fifos
mkfifo "${OBOE_TMP}"/m_in
mkfifo "${OBOE_TMP}"/m_out
coproc U9FS { ssh -F /dev/null -S "${OBOE_SSH_SOCK}" foohost "${REMOTE_U9FS}" \
  -D -a none -u "${REMOTE_USER}" < "${OBOE_TMP}"/m_out > "${OBOE_TMP}"/m_in ; }

OBOE_ROOT="${OBOE_TMP}/mnt"

domount "${OBOE_ROOT}" -t 9p -o cache=loose,trans=fd,rfdno=0,wfdno=1 \
  -o uname="${REMOTE_USER}",dfltuid="${REMOTE_UID}" \
  -o dfltgid="${REMOTE_GID}",aname=/ foo \
  > "${OBOE_TMP}"/m_out < "${OBOE_TMP}"/m_in || fatal "couldn't mount 9p"

# disallow further forwardings"
ssh -S "${OBOE_SSH_SOCK}" -O stop bogus || fatal "couldn't stop ssh forwarding"

domount "${OBOE_ROOT}/proc" -t proc proc
domount "${OBOE_ROOT}/sys" -t sysfs sysfs
domount "${OBOE_ROOT}/dev" -t devtmpfs udev
domount "${OBOE_ROOT}/dev/pts" -t devpts devpts
domount "${OBOE_ROOT}/dev/shm" -t tmpfs tmpfs
domount "${OBOE_ROOT}/run" -t tmpfs tmpfs
domount "${OBOE_ROOT}/tmp" -t tmpfs tmpfs

if [ -n "${XAUTHORITY}" ] ; then
  domount "${OBOE_ROOT}/tmp/.X11-unix" --bind /tmp/.X11-unix
  touch "${OBOE_ROOT}"/tmp/.Xauthority
  domount "${OBOE_ROOT}/tmp/.Xauthority" --bind "${XAUTHORITY}"
  chown "${REMOTE_UID}:${REMOTE_GID}" "${OBOE_ROOT}"/tmp/.Xauthority
  chmod 600 "${OBOE_ROOT}"/tmp/.Xauthority
  export XAUTHORITY=/tmp/.Xauthority
fi

export HOME="${REMOTE_HOME}" ; export USER="${REMOTE_USER}"
chroot --userspec "${REMOTE_UID}":"${REMOTE_GID}" \
  --groups lp,render "${OBOE_ROOT}/" /bin/bash -l

cleanup

This is a blurb, not software. I guarantee neither that it's reliable, nor secure, nor useful, nor final. I guarantee only that it's under a hundred lines and that I like it.

What I don't like about it is the privilege required on Pi #2 to perform the 9p mount in the namespace. It has little practical impact for the way I use it, which is to interact with devices on Pi #2 that require hardware access anyhow.

While the clever part seems like it's all about plan 9 and namespaces, the subtle part I like is the use of SSH socket mode.

This script is the 'pull' part of a suite I call oboe, for the lightweight soprano double-reed woodwind.