-- Leo's gemini proxy

-- Connecting to shit.cx:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini; lang=en


A Macos/linux Hybrid Laptop - Part 2


As I described in Part 1, my goal was to configure my Mac with a Linux VM and have the two tightly integrated so that the Linux VM feels native.

Practically, this means that the following continue to work as expected:

the `open` command

clipboard sync

file availability

To do this, I chose to use VirtualBox. It's mature, free and runs on macOS.


The 2019 Macbook Pro has a crazy fast disk and I wanted its speed in my Linux VM. Initially, I was going to send a [raw partition to VirtualBox][1] but it got complicated so I tried out a fixed size partition instead.

I installed Debian 10, and measured the disk speed. The VM recorded 1.6GB/s. Running on the metal gave me 2.0GB/s. (`dd if=/dev/zero of=./test bs=1MB count=$((1024 * 10 ))`)

It wasn't perfect, but I was happy to move on.

For files that must be available on both the Host and the VM, I use VirtualBox shared folders. The performance isn't as good as a fixed-size disk, but it's still a very respectable 660MB/s.

My files are organised to balance the performance and availability requirements.


Then the VM needed the internet and access to and from the host. To do that, I discovered that the VM requires two network interfaces. A NAT interface for accessing the Internet and a host-only interface for the host and VM communicate between themselves.

I configured the host-only with static ip addresses. The `/etc/network/interfaces` looks like this.

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The NAT network interface
allow-hotplug enp0s3
iface enp0s3 inet dhcp

# The host-only network interface
allow-hotplug enp0s8
iface enp0s8 inet static


SSH keys are stored in an SSH agent on the host and forwarded to the VM using the `ForwardAgent` SSH option. This is configured using `~/.ssh/config`. It allows logging in with the command `ssh devbox` rather than the full `ssh -A jon@`.

Host devbox
        User jon
        ForwardAgent true

To avoid password prompts, you'll need to drop your public key into `~/.ssh/authorized_keys`.

On the VM, the `~/.ssh/config` file is configured to back to the host. This is critical to allow files, state and commands to be sent to the host for that seamless experience I'm looking for.

Host mac
        User jon
        ForwardAgent true

Opening Files on the Host

To open files on the host, I made a script to copy the local file, then run the `open` command against it on the host. This makes a few assumption (which I've found to be valid so far, but will eventually need to be improved).

There is a default application configured to open the file.

The file doesn't require references to any other files.

The application doesn't need more than one input file.

This script is saved to `~/bin/ropen`.

#!/usr/bin/env bash
set -e
[[ $# -eq 0 ]] && echo "No file provided" && exit 1

ARG="$(readlink -f "$1" || echo $1)"
REMOTE_ARG="$(echo "$ARG" | sed -E 's#^/home/jon#/Users/jon)"

mode() {
  if [[ -f "${ARG}" ]]; then
    if remote_file_exists; then
      echo "file_remote" && return 0
      echo "file_copy" && return 0

  [[ -d "${ARG}" ]]                       && echo "dir"   && return 0
  [[ "${ARG}" =~ ^(https|http|ftp):// ]]  && echo "url"   && return 0
  return 1

remote_file_exists() {
  local lmd5=$(md5sum "${ARG}" | awk '{ print $1 }')
  local rmd5=$(ssh "${HOST}" md5 \""${REMOTE_ARG}"\" | awk '{ print $NF }')
  [[ "${lmd5}" == "${rmd5}" ]]
  return $?

case $(mode) in
      REMOTE_FILE="/tmp/$(basename "${ARG}")"
      scp "${ARG}" "${HOST}:\"${REMOTE_FILE}\"" &> /dev/null
      ssh ${HOST} -- open "\"${REMOTE_FILE}\"" 2> /dev/null
      ssh ${HOST} -- open "\"${REMOTE_ARG}\"" 2> /dev/null
      ssh ${HOST} -- open "\"$1\"" 2> /dev/null
      echo "Invalid input"
      exit 1

Next, it's trivial to make a command to run a command on the host. (`~/bin/rrun`)

#!/usr/bin/env bash
[[ $# -eq 0 ]] && echo "No file provided" && exit 1
ssh mac "$@"

Now, we can clone the macOS tools, `pbcopy` and `pbpaste` to set and fetch the clipboard.

Plus, I would like to be able to pipe things in and out of the clipboards much like you do with `pbcopy` and `pbpaste`.

This is `pbpaste`. It dumps the latest tmux buffer to stdout. If I trust that my clipboards are in sync then I could have equally chosen any of the clipboards. I chose tmux because it's local and unlike Vim, is always available.

#!/usr/bin/env bash

latest_buffer() {
  tmux list-buffers | awk -F: '{ print $1; exit 0 }'

tmux save-buffer -b $(latest_buffer) -

And `pbcopy` takes content via stdin, sets the tmux buffer, then sets the macOS clipboard.

#!/usr/bin/env bash
cat /dev/stdin | tmux load-buffer -
pbpaste | rrun pbcopy

The last part is setting the Vim yank buffer. Using an [autocmd][2], the `pbcopy` script is run whenever the Yank buffer changes.

autocmd TextYankPost * silent! call system('pbcopy &',join(v:event["regcontents"],"\n"))

So this has brought us to the point where we have a Linux VM with synchronised files and clipboards. It basically works, but next I will go though how I have ironed out the experience.


[1] Raw partitions on Virtualbox

[2] Vim autocmds

More Posts Like This

Return to Homepage

The content for this site is CC-BY-SA-4.0.

-- Response ended

-- Page fetched on Mon Sep 20 03:13:13 2021