Launch PS Remote Play with Controller on macOS

Here is a simple way to use the "PS" button on a Playstation controller to launch the PS Remote Play app on macOS. This requires Keyboard Maestro.

First you must disable the "Press Home button to open Launchpad" setting. This is on by default after you pair a controller to macOS. You can find it in System Settings -> Game Controllers. Turn this off. Now when you press the PS button on your paired controller, nothing should happen.

Now you can build a simple Macro in Keyboard Maestro. Set the Trigger to "USB Device Key Trigger". Press the PS button, mine shows up as "DUALSHOCK 4 Wireless Controller Button 13".

Now in the Actions for the Macro, choose "Activate a Specific Application" and find "PS Remote Play".

This same idea could be used to launch your favorite emulator application instead of Remote Play. I tried to find a way to trigger a Macro anytime it detected the controlled was connected via Bluetooth, but it seems like querying Bluetooth devices that way is too energy hungry and isn't allowed. This is the next best thing for me.

Using Bartender to only display Wireguard icon if connected to VPN

Intro

I don't remember if I figured this out myself or found the suggestion elsewhere, but I just searched and couldn't find anything online so I decided to document this neat trick.

I use Bartender on all my Macs to clean up my Menu Bar. However I missed being able to glance up and look at the Wireguard icon to tell if I was connected to a Wireguard VPN or not. Bartender has a "Triggers" feature that makes it possible to show certain menu bar icons based on the output of a shell script.

Setup

Open Bartender settings and visit the Triggers menu. Click Add Trigger. Name your Trigger and leave the "Activate a Preset" alone. Choose your Wireguard icon from the "Select menu bar items". Click Add Trigger Condition and choose Script. Paste the following script:

scutil --nc list | grep -c -e "\(Connected\).*wireguard"

This script uses the built-in scutil command to list all VPN connections configured on your Mac. It then uses grep to filter for only the Wireguard connections that have a status of Connected. The -c flag on grep counts the number of lines instead of outputting them. Bartender's Triggers expect a script result of 1, yes, or true to activate the Trigger. As far as I know, Wireguard does not allow you to connect to more than one endpoint at a time, so this script should only ever output a 0 or 1.

I have mine set to run every 1 minute. This means that after you connect to a VPN with Wireguard, it could take the icon up to 1 minute to appear in your Menu Bar. Same thing after you disconnect.

That's it! Click Done and your new Trigger is live and ready to use.

Troubleshooting

If this isn't working for you, my best suggestion would be to connect to a VPN with Wireguard and run the script above in your Terminal and verify that it is outputting 1.

Routing Wireguard networks with OSPF on Linux

I have a couple of Linux servers whose main purpose is to serve as a Wireguard server. The peers on these servers are a combination of pure clients (like a mobile phone or laptop) and more traditional site-to-site tunnel connections (like a router at a remote location). The site-to-site connections usually are routing a remote network over the wireguard tunnel, something like a /24 network so you can access the far site's local network.

Wireguard makes this easy to do, you just add the /24 network as an "AllowedIP" for that peer. Wireguard does the work of adding that route to the routing table on the Linux server itself. If this Wireguard server is part of a more complex network, though, you need to be sending these remote networks to your Wireguard server so everyone can access them, not just those using Wireguard. I have been forced to add static routes on my main router to point those networks to my Wireguard servers.

Enter OSPF! If you are reading this, I am going to assume you know what OSPF is and why it's preferred over static routing. What follows is the steps I took to get my Wireguard "AllowedIPs" network automatically advertised to my main router using OSPF.

Install FRR

FRR is a Linux package that implements a lot of networking routing protocols. On your server running Wireguard (I'm assuming Debian/Ubuntu), running apt get install -y frr gets everything you need installed. This includes a vtysh command that dumps you into a Cisco IOS-like terminal to configure your OSPF routing.

Enable OSPF daemon

Before configuring OSPF, you must enable the ospfd daemon in the FRR config. Edit /etc/frr/daemons and change the ospfd=no` line to read ospfd=yes. Then systemctl restart frr is needed to restart FRR with OSPF enabled.

Configure OSPF inside FRR

Run vtysh to enter the FRR VTY interface. config terminal to enter configuration mode. Here is the simplest configuration required to enable OSPF:

interface ens160
  ip ospf network point-to-point
!
router ospf
  ospf router-id 192.168.0.0
  redistribute kernel
  redistribute connected
  network 192.168.0.0/31 area 0

Here is the same config with my comments explaining the important parts:

interface ens160 # this is your linux interface that is facing your main router.
  ip ospf network point-to-point # I am using a point-to-point style OSPF network, yours might be a broadcast type.
!
router ospf
  ospf router-id 192.168.0.0 # this could be anything, but traditionally this is your LAN IP of this wireguard server
  redistribute kernel # this was the tricky part. This is required to insert the Wireguard "AllowedIPs" networks into OSPF
  redistribute connected # this is required to insert your wg0 (and other) networks in OSPF
  network 192.168.0.0/31 area 0 # this is your LAN network for this wireguard server. Whatever is assigned to ens160 in my case. This is required to establish a neighbor relationship with my router.

This guide doesn't cover any of the OSPF configuration on your main router. There are far too many different routers out there to even attempt to cover. The main trick to getting all of this working was stumbling across the redistribute kernel command. I was familiar with the redisribute static and redistribute connected commands from administering Cisco IOS, but the way that Wireguard inserts these routes into the routing table requires use of this redistribute kernel command.

Save your changes

That's it! You can end and then write memory to save your configuration to /etc/frr/frr.conf. If you miss this part, all of this configuration will be erased when FRR is restarted.

Troubleshooting

Here are a couple of commands I used in the vtysh interface to verify my configuration.

  • show ip ospf neighbor - this should return a record showing your neighbor relationship with your main router

  • show ip route kernel - this should return all of your AllowedIPs networks that you have configured in Wireguard. These are the networks we are redistributing over OSPF.

  • show ip ospf interface - this should return a record indicating ens160 is up, or whatever your LAN interface on your Wireguard server is. You can also see neighbor count here, which should be 1 in a point-to-point OSPF network.

Python script to retrieve DHCP leases from Palo Alto firewall

I have been using a Palo Alto PA-220 firewall for my home router for years. It is my DHCP server for my LAN. I often find myself needing to view the DHCP leases to see what IP address some random device (WLED, ESPHome devices, etc.) has. The web interface for the PA-220 is unbearably slow and the SSH CLI takes 30+ seconds after login to give me a prompt.

To speed up this task, I wrote this fairly simple script using python3. It uses the REST API that PAN-OS has to retrieve the DHCP leases. This script completes for me in less than 1 second. It outputs a JSON object. This works best for me as I find JSON to be humanly-readable and also allows me to pipe it to a utility like jq to filter it quickly.

Here is the code:

import requests
import json
from xmltodict import parse, ParsingInterrupted
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

host = "192.168.2.1" # IP of Palo-Alto firewall goes here.
key = "KEY_GOES_HERE" # Run `curl -X GET 'https://<host>/api/?type=keygen&user=<username>&password=<password>'` to create API key.
interface = "ethernet1/2" # can also use "all"

def get_dhcp_leases(host, key, interface):
url = "https://{host}/api/?type=op&cmd=<show><dhcp><server><lease><interface>{interface}</interface></lease></server></dhcp></show>&key={key}".format(host=host, key=key, interface=interface)
response = requests.get(url,verify=False)
return response

if __name__ == "__main__":
dhcp_leases = get_dhcp_leases(host, key, interface)
data = parse(dhcp_leases.content)
entries = data['response']['result']
print(json.dumps(entries, indent=4))

I also have this hosted as a gist on github. Any changes I make to it are more likely to end up there than here. Comments are welcome over there, too!

Control Palo Alto GlobalProtect VPN using AppleScript

I know it's a little strange to still be writing AppleScript in 2023, but this was the best way I found to easily connect and disconnect from a GlobalProtect VPN "automatically". I trigger this from a Keyboard Maestro shortcut, you are free to trigger it any way you wish!

This is a simple script that will toggle the connect/disconnect state of GlobalProtect on macOS. Tested with the latest version of GlobalProtect (v6.2.0-89) and macOS Ventura (13.4.1).:

(*
Toggle GlobalProtect VPN with AppleScript
Tested using macOS Ventura 13.4.1 and GlobalProtect version 6.2.0-89
Written by Trevor Manternach, August 2023.
*)

tell application "System Events" to tell process "GlobalProtect"
  click menu bar item 1 of menu bar 2
  set statusText to name of static text 1 of window 1
  if statusText is "Not Connected" then
    # GlobalProtect is disconnected, so let's connect
    click button "Connect" of window 1
    set entireContents to entire contents of window 1
  else if statusText is "Connected" then
    # GlobalProtect is connected, so let's disconnect
    set windowText to entire contents of window 1
    repeat with theItem in windowText
      if (class of theItem is button) then
        if (value of attribute "AXTitle" of theItem is "Disconnect") then
          # We found a Disconnect button on the main page, let's click it.
          click theItem
          exit repeat
        else
          # We did not find a Disconnect button on the main page, let's hope there is one in the Settings Menu.
          click button "Global Protect Options Menu" of window 1
          click menu item "Disconnect" of menu "Global Protect Options Menu" of button "Global Protect Options Menu" of window 1
          exit repeat
        end if
      end if
    end repeat
  end if
  click menu bar item 1 of menu bar 2
end tell

I also have this hosted as a gist on github. Any changes I make to it are more likely to end up there than here. Comments are welcome over there, too!

Home Assistant Quick Bar

I discovered that Home Assistant has a not-so-recent feature called "Quick Bar" that I somehow missed when it was released in 2020. In short, it allows you to press the e (for entity search) or c (for command search) anywhere in the Home Assistant web interface to access a Spotlight-style search box. This allows you access settings and devices in Home Assistant with just a few keystrokes.

Audiobooks

It’s been years since I last paid for an Audible.com subscription. I just signed up this week ($5/month for 3 months) and it’s been a delight so far. I discovered that you can pretty easily remove the DRM on the audiobook .aax file. I am using Audible Tools to help me create the ffmpeg command to do this.

Another recent discovery is that Overcast, my podcast player of choice, seems to natively support .m4b files (audiobook file format). It even includes artwork and chapters, so it behaves just like most of the podcasts I listen to.

My iPod history

Last month Apple announced the end of the iPod. I thought it might be fun to document my history with the product.

--

I got my first iPod in March 2005. I believe I bought mine at Best Buy on a weekend trip 350 miles away from home.

Picture of my first iPod

I was listening to a lot of podcasts in those early days and had to use "podcatcher" software because iTunes didn't support podcasts yet. That changed later that year when iTunes 4.9 added podcasts and the rest is history.

I visited that same Best Buy a couple years later when the iPod Touch was released. I remember checking their website and they said they had them in stock, but when I got to the store I couldn't find them. I finally asked and they were still in the back, they went and got one for me.

My first iPod Touch

The iPod Touch was so much fun back then. The iPhone wasn't "in" Montana back then (no joke) and so the Touch was as close as I could get. I remember jailbreaking the thing, and even hanging out in IRC chatrooms trying to troubleshoot my jailbreak attempts. :-D

This past winter I spent ~$100 on new parts to revive my old 4th gen iPod. It now has new life with a fresh battery and an SD card in place of the 20GB spinning hard drive. It's hard to believe a piece of consumer technology from 17 years ago is still alive and well.

I love iPod. It was one of my first adventures into a lifetime of technology.