For reasons I'm running vagrant with VirtualBox inside a docker container.  Most of the time this goes great and no problems, but when it fails it's difficult to see what's happening on the machine.  I've tried and failed to get VNC going on the vm running inside the container. I should note that my problem is with Windows VMs, with Linux VMs it's never a problem to get console. My workaround for Windows is to use screenshots and send keystrokes to the vm via vboxmanage.

First step is to get the uuid or name of the vm

List vms

$ vboxmanage list vms
"foo" {34bcbbef-ffff-4d2d-8546-031d7b597332}
"bar" {06e5696c-ffff-4c44-908e-e38aac1152fd}
"rhel76" {312f38c2-ffff-4e46-af79-6c951eeef63d}
"win2012" {4230645c-ffff-4c38-a933-a78787521ea6}
"win2019" {92d7e563-ffff-444d-afd4-242a9e24d219}

You can use either the name or the uuid to then controlvm:


$ vboxmanage controlvm win2019 screenshotpng win2019.png

You can then view that PNG to see what's on the screen right now. Next, you can send keyboard input to the VM using keyboardputscancode

Control-Alt-Delete on the VM

$ vboxmanage controlvm foo keyboardputscancode 1D 38 53 D3 B8 9D

Using this scancodes page to figure out what keystrokes you wish to type on the VM. The above corresponds to Keypress Ctrl, Keypress Alt, Keypress Del, Release Del, Release Alt, Release Ctl. You can use printf to print the Release codes, the release codes are the Keypress codes + 0x80, so for 1d:
$ printf '%#X\n' "$((0x80+0x1d))"

Using the same trick we can fake a Windows Logo key press and release with "E0 5B E0 DB", so to do a "Run Command" with the Windows Logo Key:

$ vboxmanage controlvm foo keyboardputscancode E0 5B 13 E0 DB 93
$ vboxmanage controlvm foo keyboardputstring "CMD"
$ vboxmanage controlvm foo keyboardputscancode 1C 9C

This types a Windows Key + R, then releases both. I then type "CMD" and then 1C 9C is an Enter. You can also cheat above using a Ctl-J in the string, if you type CMD then Ctrl-V Ctl-J, you'll get a literal carriage return in your keyboardputstring which will be sent to the remote host.

Using this method you can "view" the screen on your vm and "type" without having a real console.

It's worth noting that VRDP should work here, but I've had no success getting that working remotely. I only have SSH access to the vsphere host running docker/vagrant. VRDP will not work without the EXTPACK installed in virtualbox as well, you need to install the EXTPACK before trying VRDP.

Getting nokogiri to install was failing for me. I'm using bundler and needed nokogiri. Gem is trying to compile it and failing on libxml2.

At first I tried using the libxml from brew, that that didn't work, so specified where to find XCode's version of libxml in my bundle config.

BUNDLE_BUILD__NOKOGIRI: "--use-system-libraries=true --with-xml2-include=/Applications/"

You might need to tweak the MacOSX10.14.sdk part, that's for Mojave, I've been using this line for a while, and just updated from 10.12->10.13->10.14.

I also like to install the gems in my home directory, so I add the following to my bundle config:
BUNDLE_PATH: "/Users/thomas/.gem"

We build our Jenkins from a docker container, setting up that with code requires setting up the plugin from groovy. I didn't want to figure out how to translate our config for the plain mailer plugin to the email-ext plugin. I started using the plain mailer plugin but decided I'd like to send the email in HTML format. Once again, Jenkins docs were terrible, but reading the code, it looked like mimetype was supported.

I came up with the following code to send an HTML email from the Jenkinsfile

  mail to: env.REQUESTOR,
    subject: "Pipeline: ${currentBuild.fullDisplayName} ${currentBuild.result}",
    mimeType: "text/html",
    body: """
<h1>Pipeline: ${currentBuild.fullDisplayName}</h1>
<h2><a href="${env.RUN_DISPLAY_URL}">Build ${} Results</a></h2>
<h2><a href="${env.JOB_DISPLAY_URL}">${currentBuild.projectName}</a></h2>

This results in an email that is correctly displayed by Outlook and Gmail.

Just in case someone wants to see the config for the plain mailer...

import hudson.tasks.Mailer
def m = Mailer.descriptor()

I needed to checkout some code from another repository into my project, but I only wanted a specific directory within the other repository.

As usual with Jenkins, the docs are lacking, but reading the source for a bit I came up with the following:

dir('otherrepo') {
          checkout([$class: 'GitSCM',
            branches: [[name: '*/master']],
            doGenerateSubmoduleConfigurations: false,
            extensions: [
                $class: 'SparseCheckoutPaths',
                sparseCheckoutPaths: [
                  [ $class: 'SparseCheckoutPath', path: 'goodthing/' ]]
            submoduleCfg: [],
            userRemoteConfigs: [[credentialsId: 'ACTIVE_DIRECTORY',
            url: '']]

According to the code, you should also be able to remove paths with `!` in the path, but I had trouble with this. I believe if the badpath is nested under the good path, it doesn't work.

dir('otherrepo') {
          checkout([$class: 'GitSCM',
            branches: [[name: '*/master']],
            doGenerateSubmoduleConfigurations: false,
            extensions: [
                $class: 'SparseCheckoutPaths',
                sparseCheckoutPaths: [
                  [ $class: 'SparseCheckoutPath', path: '!goodthing/badthing/*', path: 'scripts/' ],
            submoduleCfg: [],
            userRemoteConfigs: [[credentialsId: 'ACTIVE_DIRECTORY',
            url: '']]

Doing this I only get the goodthing directory in the otherrepo directory in the workspace.

Talk I gave at #LISA2018. I've given this talk as a half day tutorial, at LISA2018 I gave it in 90minutes. The bulk of the talk is laying a solid foundation on how Linux works.


My iPad got updated to 11.3 and I lost the ability to use my usb-midi interface reliably. I had an M-Audio Uno which worked great on 10.3.3. On 11.3 it keeps stopping and a dialog is displayed stating that the "accessory is unsupported".

I think this is related to the M-Audio using more than 100mA. My lightning adapter couldn't inject voltage, so I looked at getting a Bluetooth MIDI adapter.

I ordered an MD-BT01 and of course the iPad could not see the adapter. I was able to see the device on my Android Phone and other Bluetooth devices. I found that you can install the MD-BT01 utility from the App Store, you have to search for MD-BT01, then select Filters and change from iPad only to iPhone only.

After installing that, you can see the adapter but still not use it. I had to install "Yamaha Synth Book" (you also need to filter for iPhone only to install this) and follow the instructions on yamaha's website

After that the adapter works. I haven't received any disconnect messages from the piano after that.

I made a data_hash backend for hiera that uses the puppet certificate certname to connect to a remote http service and retrieve hieradata for a node. The http backend is up to you, in my implementation I also verified the certificate was signed by the Puppet CA.

This backend uses data_hash, so that it only looks up hiera once per catalog compilation. If it finds hieradata for a node, it updates the hiera cache.

The github repo is located here

I wanted to export my playlists with Plex, I installed Export Tools and ended up with CSV files.

I looked around and couldn't find something to convert those to m3u. I did get a m3u8 file but it didn't work with my devices...

I made a quick python script to convert the csv to's possibly useful to someone else...maybe.

#!/usr/bin/env python

import csv
import getopt
import sys

options,remainder = getopt.getopt(sys.argv[1:], 'f:r:', ['filename=','root='])

for opt,arg in options:
if opt in ('-f', '--filename'):
filename = arg
elif opt in ('-r', '--root'):
root = arg

# convert time to seconds
def get_s(t):
return sum(int(x) * 60 ** i for i,x in enumerate(reversed(t.split(":"))))
return 0

with open(filename,'r') as csvfile:
# skip header
playlist = csv.reader(csvfile)
print "#EXTM3U"
for song in playlist:
print "#EXTINF: %s,%s - %s" % (get_s(song[9]),song[3],song[5])
print song[-1][len(root):]
print "Error opening CSV file"

I find this really useful, just a call out to sed so I don't have to remember the syntax.

ssh_delete_key() {
sed -i -e ${1}d ~/.ssh/known_hosts
alias sshdel=ssh_delete_key


[thomas@laptop: ~] $ ssh
OpenSSH_6.2p2, OSSLShim 0.9.8r 8 Dec 2011
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
Please contact your system administrator.
Add correct host key in /home/thomas/.ssh/known_hosts to get rid of this message.
Offending RSA key in /home/thomas/.ssh/known_hosts:426
RSA host key for has changed and you have requested strict checking.
Host key verification failed.
[thomas@laptop: ~] $ sshdel 426
[thomas@laptop: ~] $ ssh
The authenticity of host ' (' can't be established.
RSA key fingerprint is b7:da:35:ad:65:1c:5b:09:cf:a5:b3:e6:e7:0e:cf:43.
Are you sure you want to continue connecting (yes/no)?

I published a Thing on @thingiverse! #thingalert Tue Jul 23 19:27:57 +0000 2019

Nokogiri install on MacOSX Fri Jul 12 15:06:49 +0000 2019

HTML email with plain mailer plugin on Jenkins Thu Jul 11 21:07:25 +0000 2019

git sparse checkout within Jenkinsfile Thu Jul 11 20:40:53 +0000 2019

#lfnw node red looks like fun Sun Apr 28 21:02:11 +0000 2019