Projects

Side projects I’ve been working on over the past couple of years in somewhat reverse chronological order.

2024/04-present Asbury Park happy hours
Static website listing happy hours in Asbury Park, NJ. Used to be jekyll, rewrote in Rust using minininja. The “database” is a RON file.
2026/02 ntsc
frei0r video filter plugin that uses ntsc.rs to emulate analog TV and VHS artifacts.
2026/01-2026/02 cyd-video
Rust no_std video player for the CYD (“Cheap Yellow Display) ESP32-2432S028R microcontroller. It works but the performance is not good enough. It’s based on MJPEG using the tjpgdec-rs decoder (see below) and riffparse (see below).
2026/01-2026/02 riffparse
Rust no_std RIFF/AVI video format demuxer. Extracts audio amd video frames from an AVI file.
2025/05-2026/02 frei0r-rs2
Fork of the Rust frei0r-rs framework for building frei0r video plugins in Rust. Made major changes to the API.
2026/02 tjpgdec-rs
Some vibe-coded performance improvements to the tjpgdec-rs JPEG decoder used with cyd-video.
2026/02 esp32-tv
ESP32 video player fork with vibe coded fixes and support for IR receiver.
2026/01 esp32-202432S028_video_player
ESP32 video player fork, ported to platformio.
2026/01 cyd-bevy-life
Conway’s game of life on no_std ESP32 MCU...
Read More

Tethering

In iOS when you enable the Personal Hotspot, it creates a separate bridge100 interface. So your tethered traffic goes out over this interface instead of the one your device traffic uses. So it counts against your tethered traffic instead of device traffic.

There is a way to make the tethered traffic use the same outbound interface that device traffic uses. This is a walk-through of how to do that.

First, install a-shell on iOS. Run it and enter pip install pvpn (see pvpn).

Then enter pvpn -wg 9000. You should see something like this: a-shell-wireguard.jpg Copy the PublicKey it prints.

Next install the macOS WireGuard client

Choose Add Empty Tunnel…, name it pvpn and configure similar to this, where [Peer] PublicKey is the key the server printed above: wireguard-config.png

Activate the WireGuard tunnel while you are tethered to the iOS Personal Hotspot, and macOS traffic should now be routed over the VPN running on the iOS device.

You can confirm both the iOS device and the macOS client have the same public IP by visiting http://ifconfig.co/ in...

Read More

Why rectalogic?

Years ago I found a company called analogic.

I thought it was brave that they put anal front and center in their name. Even their logo kind of looks like a techno-stylized puckered hole

analogic.png

So I figured if an international corporation can choose this name, I can do something similar.

Other words that make me laugh:

analyze, therapist, banal, the ASS file format

I turn 12 in February.

Read More

Haystack Arq 5 APFS Snapshot Backup

Arq 5 backup on macOS does not automatically create and back up from an APFS snapshot.

By configuring Arq preferences with before and after backup scripts, you can take an APFS snapshot, mount it and perform the backup from the snapshot, then unmount when finished. This way the backup is point-in-time consistent.

preferences.png

snapshot-before.sh

#!/usr/bin/env bash

diskutil unmount /tmp/snapshot
set -o errexit

tmutil localsnapshot
SNAPSHOT=$(tmutil listlocalsnapshots / | tail -n 1)
mkdir -p /tmp/snapshot
mount -t apfs -r -o -s=$SNAPSHOT / /tmp/snapshot

snapshot-after.sh

#!/usr/bin/env bash

set -o errexit

diskutil unmount /tmp/snapshot

Run snapshot-before.sh manually and then in Arq select your home directory inside the snapshot as the directory to back up (/tmp/snapshot/Users/XXX).

Read More

PostgreSQL rowcount with SQLAlchemy/psycopg2 Streaming Cursor

SQLAlchemy ResultProxy.rowcount does work with SELECT statements when using psycopg2 with PostgreSQL, despite the warnings - as long as you aren’t streaming the results. This is because psycopg2 uses libpq PQexec along with PQcmdTuples to retreive the result count (PQexec always collects the command’s entire result, buffering it in a single PGresult).

Using SQLAlchemy stream_results causes psycopg2 to use a named server-side cursor via PostgreSQL DECLARE. This will stream result records on demand, but ResultProxy.rowcount will not reflect the total result count.

To workaround this you can configure the psycopg2 server-side cursor to be scrollable (this allows moving backwards in the resultset). Then after the streaming query, execute MOVE FORWARD ALL to move to the end of the results without fetching any. PQcmdTuples will then set the pscyopg2 rowcount and you can then scroll absolute back to the beginning of the results and process them streaming.

>>> import sqlalchemy as sa >>> >>> table =...
      
Read More

AWS VPN with Public Subnet

This describes how to configure an ipsec VPN in an AWS VPC with a customer who does not allow RFC-1918 (private) IP addresses in the VPC subnet.

The basic idea is to expose a single host in the VPN using a /32 subnet of the VPN public IP. We can restrict each client peer to a specific port on that host and use port forwarding to connect them to internal hosts on private subnets in the VPC. So we can support multiple clients, and each client sees only a single host (with a public IP) and can access a single client specific port on that host.

The following applies to Ubuntu 14.04 and Strongswan 5.1.2. For purposes of discussion we have two clients. All public IPs are invalid examples. First, clientA with peer public IP 165.{A}.22.101 and an internal host with public IP 165.{A}.22.102. We will restrict clientA to port 2575. Second, clientB with peer public IP 180.{B}.89.101 and an internal host with public IP 180.{B}.89.102. We will restrict clientB to port 2576.

We create an EC2 instance in a VPC with CIDR 10.0.0.0/16 and place it in a public subnet with CIDR Read More