Using PCI pass-through in VMware ESXi 6.7 on a 2012 Mac Mini

I use a 2012 Mac Mini as a home VMware ESXi server. It’s a surprisingly great little machine, with exceptionally low power consumption and good performance. It runs several Linux and Windows VMs. I’ve been gradually migrating applications to it, and the most recent is my home PLEX server. To do that, I needed to move a Thunderbolt disk enclosure (OWC ThunderBay 4) to the Mini and attach it to a Linux VM. (I suppose an alternative might be to use raw disks, something I haven’t explored yet in ESXi.)

I ran into a snag trying to get this working. VMware let me put the ThunderBay’s two AHCI controllers in passthrough mode, but after doing the required reboot, they still weren’t in passthrough mode, and VMware still said a reboot was required.

I eventually found a KB article related to the issue, which wasn’t very helpful, but did provide some clues. Setting VMkernel.Boot.disableACSCheck to true in the system advanced settings did the trick. Will this shoot me in the foot? Time will tell, but it’s hard to believe this configuration knob would exist if it wasn’t necessary in the real world.

Update 2/13/2019: It turns out that this is pretty unreliable, so I’m going to move the disks over to a different enclosure and share them using USB. A Linux VM was able to access some data but suffered from frequent AHCI resets. FreeBSD and macOS VMs were unable to access anything at all. I found an additional KB article with a bit more information.

Setting up WireGuard between Linux and iOS

WireGuard is a modern VPN that’s designed to be easy to configure, performant, and secure. The ease-of-configuration is really important. If you’ve ever set up IPsec, you know what I mean. OpenVPN isn’t awful, but it isn’t good, either. WireGuard has both a Linux kernel implementation as well as a Go-based portable implementation that works on Mac and iOS. Official Windows support doesn’t exist yet, but is on the way.

I couldn’t find any information on configuring WireGuard to work with iOS. Here’s what I did to get it working.

On Linux:

Designated a /24 subnet in the RFC1918 space and set up wg0 according to the Wireguard quick start documentation. I actually configured it in /etc/network/interfaces like so:

auto wg0
     iface wg0 inet static
     pre-up ip link add $IFACE type wireguard
     pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf
     post-down ip link del $IFACE

Created /etc/wireguard/wg0.conf:

PrivateKey = my-private-key-goes-here
ListenPort = port-goes-here

# iPhone
PublicKey = public-key-goes-here
AllowedIPs = client-ip-address-goes-here/32

AllowedIPs is a bit of a misnomer. It’s not just an ACL for incoming packets; It’s also used to determine what traffic to route to each peer. The important thing here is to use /32 addresses in the AllowedIPs of the peers. If you use the entire /24 subnet, only the first peer using that subnet will work.

The iOS client side is where I had the most trouble. Part of the trouble is that the iOS app doesn’t show the state of the client. The other part is that since WireGuard is connection-less, even a bogus config will show up as active when you enable it.

Here’s what worked for me on iOS:

I created a key pair using the iOS app and put the public key in my Linux wg0.conf and restarted that interface.

Assign an address to the client from the /24 subnet. This needs to match the AllowedIPs line on the server. I used a /24 netmask on the iOS device, but /32 should work, too. I set the Listen port to 5555 to make it easy to verify incoming traffic using Wireshark, but that’s optional. A DNS server should be specified, because the existing one probably won’t be reachable with the VPN up. (Enabling Exclude Private IPs might fix that depending on the network you’re on.)

On the iOS peer config, enter the public key of the server and set the endpoint IP:port. For Allowed IPs, use This will cause all traffic to be routed through the VPN endpoint while it’s active.

That’s it. Now activate the VPN and send some traffic through it. The ‘wg’ command on the Linux peer should show a handshake and data transferred in and out. To make Internet access work from the iOS device, you’ll probably want to set up NAT on the Linux peer.

Note that WireGuard is silent on the wire by default, so you won’t see a handshake unless you force traffic through it. The easiest way to do that is to use Safari to try to connect to the Linux peer’s IP address. (It doesn’t matter if it doesn’t have a web server running.) Using for Allowed IPs on the client essentially forces a connection handshake because the iOS device will start sending traffic to the world through it on its own.

WireGuard roams peers between IPs effortlessly. Obviously one endpoint must have a fixed IP:port, but a peer roaming between Wi-Fi networks and LTE works beautifully.

TV standards are still a mess

I picked up a Vizio PQ65-F1 4K TV during the holiday sale season, and it’s a tremendous upgrade from our old LCD, an almost 10-year-old Sharp. The color and dynamic range are amazing and I don’t regret skipping OLED at all. I haven’t played with the built-in TV app platform at all, preferring instead to use an Apple TV 4K as the sole source device.

Sadly, TV’s continue to be anything but plug-and-play. For some reason the HDMI2 input does not work with Dolby Vision HDR (but works fine with HDR10). As far as I can tell this is not a limitation of the TV itself, so it must be a manufacturing defect. I wasted a lot of time isolating this problem. The TV itself has five HDMI inputs, and they’re not differentiated except that HDMI4 & HDMI5 require a minimum 1080P input, and HDMI1 supports the HDMI audio return channel (ARC).

Our Onkyo TX-NR545 receiver supports the latest HDMI/HDCP standards and HDR10 pass-through, but for some reason it cannot pass through Dolby Vision signals. This seems to just be a firmware limitation, but it’s an EOL product and Onkyo’s solution is to buy a new device. A workaround is to use HDMI ARC to pass audio from the TV to the receiver, and connect source devices directly to the TV. That works fine, but prevents using the latest audio formats (e.g., Dolby Atmos).

I’m still using a 3.0 audio setup, so I don’t care about Atmos right now, but it’s obnoxious to have to choose between having the latest audio standards and having the latest video standards (or buying a new receiver). There are some HDMI splitters (such as HDFury) that could work, but they’re 1/3 or more the price of a new receiver.

The TV works well as a dumb monitor; the Internet connection is completely optional. If connected to a network, it will update its firmware without asking. My current plan is to leave it on the network for home automation and Chromecast support, but to block its Internet access at the router.

LEGO Nightstand Light Switch

I’ve been playing a lot with home automation recently, and in particular I’ve been installing a lot of cheap ESP8266-based Wi-Fi relays, such as the Sonoff Basic, Sonoff SV, Sonoff S31, Sonoff iFan02, and Shelly1, which have all been flashed to run the open-source Tasmota firmware. These communicate with Home Assistant through an MQTT message broker over Wi-Fi. Home Assistant, in turn, allows the devices to work with schedules, timers, voice-activated cylinders, and so on. With the exception of the Sonoff Basic, I would happily recommend these devices to anyone with some electrical knowledge and DIY skills.

One advantage of having automation-enabled lights in the kids rooms is being able to turn on their lights to help get them out of bed on school days. For that reason, I set up the ceiling fan in Lucas’s room with an iFan02 (replacing the Hunter RF control module), one of his floor lamps on an S31 and the other on a Basic.

Now he needed a convenient way to turn on/off the floor lamp and nightstand lamp without physically switching them off, which would prevent them from being turned on by automations.

Here is the result:

Lego Light Switch
The LEGO light controller (right), pictured with a Sonoff iFan02 remote (left)

Each of the buttons toggles the state of a different light. The LEGO parts were scavenged from a large parts bin. The buttons came from a local electronics shop. Inside is a Sonoff SV, powered by a re-purposed USB cable. The Sonoff SV was modified somewhat to make it fit in the small enclosure: I removed the relay and the side of the board carrying the relay outputs, and the header pins were bent at about a 45 degree angle to keep them out of the way of the pushbuttons. Power comes from an old iPhone USB charger.

Installing the buttons was probably the hardest part of the build, but that’s not to say it was difficult. I hit the center of the smooth-surface blocks with a punch and then drilled them handheld without any issues. The bore is slightly more than 1/2″, which required some extra trimming. The depth of the LEGO prevented use of the button mounting hardware, so I used hot glue to hold them in place.

Here you can see the Sonoff SV, trimmed to fit and relay removed.
Here you can see the Sonoff SV, trimmed to fit and relay removed. This was done to save space.


Internally, the buttons are connected to ground, with the other leg connected to a female header cable that plugs into the corresponding GPIO port. I used GPIO4, 5, and 14.

Inside the controller



The USB power cable is fed through a block with a hole in it. A zip tie is used internally for strain relief.

Here you can see the inside of the assembled unit. Everything barely fits.

Everything packed together
Everything packed together


Moving onto the software side, this is how I configured the Sonoff SV module with Tasmota

Sonoff Module Configuration
Sonoff Module Configuration

Finally, I had to configure it to toggle the other modules on and off with button presses. This doesn’t use Home Assistant at all; I used Tasmota rules to publish MQTT messages to the other devices directly. Home Assistant correctly observes their changed states automatically.

Tasmota supports a bunch of active rules at once, but they all end up concatenated together on one line. Ignore the line wrap below! This is the configuration I ended up using:

switchtopic1 0
switchmode1 5
switchmode2 5
switchmode3 5
setoption32 20
rule on switch1#state=2 do publish cmnd/sonoff-3325/power 2 endon on switch2#state=2 do publish cmnd/sonoff-3443/power 2 endon on switch3#state=2 do publish cmnd/sonoff-2833/power 2 endon
rule 1

The other lights correspond to topics sonoff-3325, 3443, and 2833. (I’m still new at this, but so far I’m keeping with the pre-assigned names rather than friendly names.)

That’s it! I hope this helps someone.

This build was inspired by @mike2nl and @andrethomas on the Tasmota Discord channel. If you get stuck with Tasmota, I’ve found the channel to be very helpful.