Tailscale battery life instrumentation #

I wrote a valedictory blog post about the instrumentation we're adding to the Tailscale client to get a handle on battery life issues. It's been an interesting "full-stack" project, involving thinking about cell phone internals, hacking on the Go standard library, and exploring visualization options.

You won't believe what this one weird ioctl will do #

Darwin was one of the first things Apple open-sourced (24 years ago). It's been mostly a "throw it over the wall" approach, but being able to peek under the hood has been very handy.

I wrote a short tailscale.dev blog post showing how we've been able to improve Tailscale's macOS and iOS apps by seeing how things are implemented.

Infinite Mac: infinitemac.org #

tl;dr: Infinite Mac has a new home at infinitemac.org. Using a new Emscripten port of Mini vMac, it is now able to run almost every notable version of Mac OS, from 1984’s System 1.0 to 2000’s Mac OS 9.0.4. The project is also now accepting donations (via GitHub Sponsors or PayPal).

3 emulated Macs and a real one on a desk
Spot the odd one out.

Mini vMac

As I mentioned in my last post on the project, my immediate plans were to make even older versions of System Software runnable in the browser. Em Adespoton maintains a compatibility matrix spreadsheet showing which emulators can run specific OS versions, and it looked like my best bet was Mini vMac. There is an existing Emscripten port, but I decided to start from scratch for two reasons:

  1. It used Emscripten’s SDL compatibility layer, presumably to minimize the work to get something up and running. However, that’s both less efficient (as far as additional layers of abstraction to go through) and would not benefit from the JavaScript/browser API wrappers that I’d already developed for Basilisk II and SheepShaver.
  2. It first ran Mini vMac’s custom build system to generate the Makefile and other configuration data for Linux/SDL, and then modified the generated code to get things working. This seemed like a not very maintainable approach for the long run, since any re-runs of the generation system (e.g. to generate with different options) would require the modifications to be redone.

The first step in doing it myself was to get Mini vMac building. It’s an “interesting” project for a few reasons:

  • It doesn’t use version control (source code is available as tarballs for major versions).
  • Rather than a traditional autoconf + make setup, it has its own build system generator (also written in C).
  • Almost all options are specified at compile time (to minimize binary size and runtime cost presumably), so there's a lot of branching that relies on the C preprocessor.
  • It can be built by very old compilers for a broad range of platforms (even classic Mac OS, Carbon, Nintendo DS). Therefore it targets C89 and makes heavy use of macros and typedefs to make things work in such a broad range of environments.

This blog post has a rather negative take on the whole setup, but I have some empathy for Paul (Mini vMac’s author). This is clearly a passion project for him (dating back to 2001), and he’s set things up in a way that suits him. Incidentally, Paul himself has not been heard from for a couple of years, hence Mini vMac is in a bit of a limbo state. An import of the codebase and website to a GitHub org was done in 2022, in case Paul’s site does eventually go down.

With that context out of the way, getting it to build was actually not as painful as it sounds. While the build system is custom, it’s also arbitrary C code, so it’s very easy to hack on (vs. dealing with m4 for autoconf). Since it targets some very low-end platforms it’s also very fast to build, so the iteration speed was great. The initial bring-up involved adding an alternate Makefile generation mode that used the Emscripten toolchain and a glue file to get configuration data in and the framebuffer out. With that scaffolding in place, adding input, disk image mounting, color and sound was pretty straightforward.

Since the emulator allows the speed to be controlled, I exposed that as well. I knew that modern machines emulate classic Mac OS significantly faster than 68K-era Macs ran it (even when targeting WebAssembly and not being able to use JIT), but it was made truly apparent when running in 1x speed mode. I was a lot more patient back in the 90s, back when you had to be aware of how big the event queue was and make sure the computer could keep up.

The framebuffer updating in Mini vMac keeps track of dirty rectangles (another example of its focus on low-end host machines), and it was a nice bit of API “mechanical sympathy” to be able to hook that up to the dirty region option of CanvasRenderingContext2D.putImageData(). Though as it turns out that code path is perhaps not well-tested on some versions of Chrome — non-macOS users reported glitches and I had to add some opt-outs.

System 6

Once I had Mini vMac up and running in the browser, I booted a System 6.0.8 image. I only started using a Mac with System 7.1, so this was my first time spending any significant amount of time with this older version. I recall that System 7 had a reputation for being more RAM hungry and slower than its predecessor, but by modern standards they seem equally snappy and stingy with their memory use (with MultiFinder enabled System 6 uses the same amount of RAM as System 7).

What was more surprising was that things that I took from granted from System 7 (and assumed had been there for ages) were in fact recent additions. Two that I ran into very quickly were typing into Finder windows to select items with that prefix and being able to drag files onto application icons to open them. Around this same time Quinn had a thread about how earlier versions of the Mac UI were not as refined as people remembered them, which was a nice confirmation of my experience.

One of the other surprises that I ran into was that the Infinite HD disk (with its 1GB of Mac software) was taking a very long time to mount. I eventually figured out that its desktop file was being rebuilt (having a visible progress bar for this is another System7 refinement). It turned out that the Desktop DB/Desktop DF files that I was used to were another System 7 advance, while System 6 used a simpler (single) “Desktop” file, which causes problems when switching back and forth. The solution for my situation turned out to rebuild the desktop files twice, first under System 6 and then under Mac OS 8, so that both sets would be included in the generated image.

Once I had it all working, it was a matter of registering system6.app and configuring it.

System 6 showing the “About the Finder…” window
System 6.0.8 running on an emulated Mac Plus.


System 6 showing the “About the Finder…” window
The mid-90s were a tough time.

With Mini vMac running, running older OSes was mostly a matter of figuring out which machines to build and tracking down install disks. MARCHintosh was coming up, and I figured it would be a worthwhile goal to get a gallery up and running in time for that.

But first, I needed a domain name. While system7.app, macos8.app, etc. were all very catchy and memorable, system2point1.app was somehow less so (this was the first version to add support for HFS, and thus notable, even if it was a point release). I initially considered system.software (to further extend my use of more obscure TLDs), but it’s a premium domain name costing $2,700/year, and thus not really within my budget. macos.museum was a more affordable $84/year, but it was only one of the few macos.<TLD> domains that was unclaimed, leading me to conclude that Apple’s legal department may have an interest in it (“Mac OS” is also technically an anachronistic way to refer to pre-System 7.5.1 releases). In the end the “obvious” infinitemac.org won out, both for its affordability ($12/year) and because it matched the project name. The .app domains names will remain as shortcuts/redirects.

I used the word “gallery” above, and that was the experience I was going for. I wanted a curated experience, with pristine, period-accurate installations. I appreciate what the Internet Archive has done (and will often use it as a source) but at some point the amount of choice (400+ bootable images) becomes overwhelming. I wanted to have a place where you can experience first-hand what someone means by “spatial Finder”. Or if you come across some trivia about option-double clicking only getting its modern behavior (close windows behind you) in System 6.0.4, you can open it and its predecessor side-by-side to see for yourself.

A reference point I had was Stephen Hackett’s macOS screenshot gallery (or his more recent Rogue Amoeba one), or this one of early Mac systems. While they were certainly curated, even a great set of screenshots can’t capture what it’s like to use one of these systems (e.g. the feeling of zoom rects going across the screen).

My other inspiration was UTM’s gallery (which does provide one-click access, but requires native software and waiting for download and installation) and PCjs Machines (which run in the browser, but being about PCs is inherently inferior in the eyes of this 90s Mac fanboy).

For getting pristine versions of each OS, I tried to use “primary” sources as much as possible, i.e. directly from Apple. In some cases Apple still hosts the downloads (unclear why Mac OS 8.6 gets such treatment). In other cases, there were “archive” releases from the 90s that have multiple operating systems (e.g. the 1991 E.T.O. Essentials CD has System 7.0 and earlier, and the 1999 Legacy Recovery CD has some later releases).

The one release that was surprisingly hard to track down was System 0.97/1.0 that shipped with the original Macintosh 128K. I was not the first to run into this, and it appears to boil down to it being a short-lived release (1.1 came out a few months later), relatively few 128K Macs being sold, and the system disk also being the primary data storage mechanism for users. I tracked down all the disk images I could find, and put together a combined one that matched the original’s spirit.

The other thing that was surprisingly hard to find was a detailed changelog for each release. Perhaps there are comp.sys.mac.system posts from the era or Apple press releases, but my archeology skills do not extend to Usenet or wire services. I mostly used Macworld’s Mac Secrets (which being from 1996 is somewhat contemporary) and Mac 512’s System Showcase.

As previously mentioned HFS was introduced with System 2.1, thus earlier versions (that only support MFS) cannot mount the Infinite HD HFS image with the software library. I briefly considered making an MFS generator too, but the only MFS libraries I could find were either read-only or embedded inside sample code from 20 years ago, thus it seemed like a yak too far. I ended up hand-creating an image with some representative programs.

The final gallery includes every notable system software release (I'm sorry, fans of System 7.0.1). Perhaps over time I'll go back and backfill truly everything, but for now generating 36 bootable images was plenty. I tried to make the browsing experience as pleasant as possible — each release has a permalink, and you can command-click on the “Run” button to open things in new windows. Some releases only work with specific Macs, and some can run on multiple (I used the Apple ROMs spreadsheet and Mac ROMan to build up a library of machines) — which machine is used can be chosen via the “Customize” button.

Other Improvements

The focus on gathering together all of the OS releases meant that I didn’t have as much time to work on the emulation aspects. The one thing I spent some time on was improving the audio playback system. Audio is one of the areas that I know less about, and I had kept most of the structure from James Friend’s original Basilisk II port in place. However, as I was bringing up the audio subsystem for Mini vMac I remembered that audio worklets are now widely supported, and they also have an API “mechanical sympathy” with what the emulator expects. I’d also already set up a ring buffer system for the networking support, and in fact the library mentioned emulation as a use-case. The current implementation is a system that is quite a bit simpler, has lower latency, and I understand better, though there are still bugs. Between the audio worklet, the service worker, and the dedicated worker, I’ve managed to use most of the worker types — I now just need an excuse to use a paint worklet to catch them all.

One small tweak that I made was related to scaling. Nearest-neighbor is nice for preserving a crisp look and feel, but it’s not appropriate when trying to fill the screen in full-screen mode or when the host screen itself is doing non-integer scaling. I can see why emulators end up with configuration settings to allow for more historical visual accuracy, but for now I’ve managed to stay away from too many fiddly settings.

I made a few improvements to the experience on mobile/touch devices — there’s now a way to bring up the keyboard and import files (without drag and drop) and controls are visible even when the screen chrome is in its smallest mode. There are still fundamental limitations, these are all desktop UIs that were designed for very precise pointing devices, and they rely on actions like double-clicking that are hard to do precisely on mobile. PCjs appears to have taken a slightly different approach that may be worth considering: touches appear to drive a virtual trackpad that moves the cursor and allows more complex operation (like drag-and-drop) to be accomplished.

I also ran into a few cases where the emulator would not load, especially in-app web views. Those turned out to be caused by the service worker trying to do caching (but not being allowed to), but in general some basic error reporting seemed appropriate. Perhaps setting up Sentry or the like would be appropriate, though I would like to minimize the number of other services I depend on.

I had mentioned wanting to have a way to cover the infrastructure costs of the project ($332.48 so far), and I got around to setting up GitHub Sponsors and PayPal donation accounts. A few people have already donated, for which I am very grateful. I will try not to blow it all on domain names.

What’s Next

First, a break. I’ve been working almost every day on the project to hit my (self-imposed) MARCHintosh deadline of having all OS releases, and it’s gotten to be a bit of a grind.

Beyond that, there are a bunch of software requests that I could incorporate into the Infinite HD image. Though making it even easier to load software (even if I don’t include it directly) would be even better — I recently came across Discmaster and something that lets you load anything from the Internet Archive with one click would be great.

Improving the persistence support is another area I’d like to explore — the “Saved” folder in “The Outside World” is a bit too obscure, and because it uses the File System Manager some programs don’t work well with it. Ideally there should be a whole HFS volume that can be persisted. That would also integrate nicel with the browser’s file system APIs.

Finally, though I said I would take a break from adding additional operating system images, there are pre-release and obscure ones that might be fun to get running. Or perhaps even parallel universe alternatives.

Update: See also the discussion on Hacker News.

Reducing Tailscale’s Binary Size on macOS #

How I Consume Mastodon #

tl;dr: I use Masto Feeder to generate a nicely-formatted RSS feed of my timeline, and then I can read it in my preferred feed reader.

A bit more than 10 years ago I wrote a post called “How I Consume Twitter”. It described how I contort Twitter to my completionist tendencies by generating RSS¹ feeds for my timeline (for more fine-grained updates) and lists (to read some accounts in a digest). That allowed me to treat Twitter as just another set of feeds in my feed reader, have read state, not need another app, and all the other benefits that RSS provides.

A bunch of things have changed since then: (lots of) Twitter drama, product changes and feed reader churn (I’m currently using NetNewsWire), but the Bird Feeder and Tweet Digest tools in Stream Spigot have continued to serve me well, with the occasional tweaks.

When the people I follow started their migration to Mastodon in early November, I initially relied on Mastodon’s built-in RSS feeds for any user. While that worked as a stopgap solution until I decided what instance to use², it proved unsatisfying almost immediately. The feeds are very bare bones (no rendering of images, polls, or other fancier features) and do not include boosts. Additionally, having potentially hundreds of feeds to crawl independently seemed wasteful, and would require manual management to track people if they ever moved servers.

I saw that there was a Mastodon feature request for a single feed for the entire timeline, but it didn’t seem to getting any traction. After a couple weeks of waffling, I decided to fix it for myself. The Stream Spigot set of tools I had set up for Twitter feed generation were pretty easily adaptable³ to also handle Mastodon’s API. Over the next few weeks I added boost, spoiler/CW, and poll rendering, as well as list and digest support.

The end result is available at Masto Feeder, a tool to generate RSS feeds from your Mastodon timeline and lists. It can generate both one-item-per-post as well as once-a-day digests. It’s been working well for me for the past few weeks, and should be usable with any Mastodon instance (or anything else that implements its API). The main thing that would make it better is exclusive lists, but there’s some hope of that happening.

Mastodon viewed as a feed in NetNewsWire

As for Twitter, with the upcoming removal of all free API access it likely means the end for Bird Feeder and Tweet Digest. What this means for me is that I’ll most likely stop reading Twitter altogether — I certainly have no interest in using the first-party client with its algorithmic timeline. However, I’ve been enjoying Mastodon more lately anyway (@mihai@persistent.info should find me), and with being able to consume⁴ it in my feed reader, it’s truly more than a 1:1 replacement for Twitter.

  1. Technically Atom feeds, but I’ve decided to use the term RSS generically since it’s not 2004 anymore.
  2. My dilly-dallying until I settled on hachyderm.io mean that someone else got @mihai a few days before I signed up.
  3. It did pain me to have to write a bunch more Python 2.7 code that will eventually be unsupported, but that’s a future Mihai problem.
  4. For posting I end up using the built-in web UI on desktop, or Ivory on my iPhone and iPad. Opener is also handy when I want to open a specific post from NetNewsWire in Ivory (e.g. to reply to it).

Tailscale iOS and macOS Shortcuts #

I worked on adding iOS and macOS Shortcuts support for Tailscale's latest release. I wrote a blog post with examples of shortcuts and automations that the Tailscale actions could be combined with. One that didn't make the cut was using sound recognition, for things like “In case of an emergency, break glass to activate Tailscale”.

iOS Shortcuts automation that connects Tailscale when the sound of breaking glass is detected Shortcut automation sound triggers: baby crying, shouting, kettle, and others

Other possibilities include "Cry/shout/scream to connect" and "Let your pet control your connection state".

Places Mihai Has Committed Code From #

I've been running lolcommits for 10 years, and it's captured some interesting moments from my time at Quip and Tailscale.

I've put together a gallery, it was a fun nostalgia trip to revisit so many places and coworkers.

Infinite Mac: 2022 In Review #

I've come to think of Infinite Mac as my forever project. There's always something to work on, whether it's expanding the library, improving compatibility, adding more platforms, improving performance, debugging data structures, bridging APIs from 30 years ago with modern web platform features, or fighting with frontend tooling. With that in mind, here's where things stand at the end of the year — there have been quite a few changes since my last post on the project.


Befitting a long-term endeavor, I invested some time into maintainability. This included small changes like setting up auto-formatting and bigger ones around code organization. I moved all of the browser-specific audio, video, clipboard and other subsystem implementations into their own modules, instead of adding lots of branching to existing ones.

That cleaner separation, combined with changes to reduce diffs with the upstream, made it possible to rebase the repo on a more recent version of Basilisk II — I had still been basing my work on James Friend’s initial Emscripten port, which was a snapshot as of 2017. Most Basilisk II development is happening in the kanjitalk755’s fork, and I switched to building on top of that.

Finally, I made it easier to do native (macOS) builds of Basilisk II (and SheepShaver) from the same repo. This reduced the friction when tracking down behavioral differences between the native and web-based versions: I can instrument or modify shared code and then run it in both builds to see how it differs.

SheepShaver and Mac OS 9

With things on a more maintainable path, I decided to tackle a bigger project: PowerPC support (which would allow Mac OS 8.5 and later to run). This involved porting SheepShaver to WebAssembly/Emscripten. Luckily, it shares a lot of code and architectural decisions with Basilisk II (not surprising, since they were both created by Christian Bauer). The initial bringup and integration involved similar autoconf tweaks and #define changes to get the Emscripten build on the right code path. After that it was a matter of hooking up each subsytem to the existing implementations that bridged to the JavaScript/browser world.

The end result is running at macos9.app. My main takeaway is that it feels more sluggish than System 7 or Mac OS 8. A lot of that appears to be due to bloat in Mac OS 9 itself, running a PowerPC version of System 7 feels snappier. There’s probably low-hanging fruit in the emulation itself when targeting WebAssembly, but I have not done any investigations in that area.


Infinite Mac Mac OS 9 ScreenshotMac OS 9 in an Apple Cinema Display bezel with dynamically-generated Stickies

A somewhat silly feature I wanted to implement was to show the changelog in the set of stickies that is shown at startup. I had been previously been embedding the data by hand (by booting each image and editing the Stickies file), but this was becoming tedious now that there were four separate variants and more frequent edits. I therefore reverse engineered the Stickies data format and then switched to dynamically generating it, including the changelog. A bit over-engineered perhaps (see the caveat below), but it was fun to reconstruct what Jens Alfke had implemented almost 30 years ago.

Another “because I felt like it” feature was adding era-appropriate screen bezels to the ersatz monitor that is shown around the screen. Technically beige was no longer in use by the time System 7 was released (the Snow White design language was fully rolled out a few years prior), so this may be something to revisit if older OS versions are supported.

I also added a couple of useful features: the ability to swap the control and command keys (so that shortcuts like Command-Q and Command-W can be used even when not in full screen mode) and clipboard syncing support. The latter has some interface impedance mismatch issues, since the clipboard API is asynchronous and has gesture trigger requirements. However, it seems to work well enough to get text in and out of the emulator in a more natural fashion than files saved in the “Uploads” folder.

Improved Compatibility

For the emulators to be more than just a curiosity (that gets played with for a few minutes and then forgotten), having compatibility that’s at least as good as the native builds is important. I spent some time fixing small bugs, like missing thousands/16-bit support, handling screen resolution changes, and making the audio less laggy.

A bigger task was improving the accuracy of FPU emulation. This manifested itself as two bugs that initially seemed unrelated - a calculator program failed when doing any operation, and scroll thumbs did not move in Mac OS 8. A tip from a user pointed out that native builds of Basilisk II used to have the latter problem too, back in 2018. Running with a build from that era reproduced the scrollbar behavior, and the calculator issue too, when I tried it on a hunch (presumably the scroll thumb drawing routines do a floating point division to compute the offset).

I then did a bisect of changes to find where it got fixed, and ended up with the switch in FPU implementations to a standard IEEE 754-based one (instead of a custom implementation). However, the WebAssembly version was already using the IEEE 754 implementation, thus it should be on the same code path. I eventually realized that the native build (on an Intel Mac) was on the USE_LONG_DOUBLE sub-path, while the WebAssembly one ends up with vanilla 64-bit doubles. Both x87 and the 68881 support extended precision (80-bit specifically), which makes IEEE 754 a good match for them, but that’s not the case for the WebAssemby virtual machine. I then checked to see what the arm64 port of Basilisk II does (as an example of another platform without extended precision support), and it uses a different FPU implementation, based on mpfr. Switching to it resolved the issue, albeit with a performance hit (hopefully no one is doing long Infini-D renders in a browser).

Another place where an external tip provided a key clue was in tracking down the case of missing sound support in Mac OS 8 and 9. A user that was hosting their own instance of the emulator reported that sound worked for them, which was surprising. I initially thought it was due to a different system image, but I could still not get it to work even when I used theirs. I eventually realized that they had a simpler build process, and were not using machfs to do the dynamic Stickies insertion mentioned above. When I switched to passing through my system image unmodified, sound began to work.

There’s probably another subtle bug in the HFS data structures that machfs emits (I have already fixed a couple), but I did not feel like diving into Inside Macintosh: Files again just yet. Instead I switched to a lower-tech way of inserting the dynamically-generated Stickies file into the disk image: a placeholder file whose contents can then replaced by operating at the raw bytes level.


Somewhat surprisingly, the SheepShaver/Mac OS 9 work ended up on Hacker News before it was fully ready. The discussion was nice, though mostly in a nostalgic/“they don’t make them like they used to” vein. Amusingly, the TL of the .app domain project noticed their use. The discussion also inspired me to change the default alert sound to Sosumi.

The Register also had an article about Mac OS 9 where the author actually reached out and got some quotes from me — I appreciated the effort. A Japanese site had a pretty in-depth article about kanjitalk7.app, it’s nice that the localized version was noticed.

My favorite was a very thorough YouTube video about the project, including a demo of the LAN/AppleTalk functionality that I cobbled together over the summer.

What’s Next

I’d like to broaden the versions of System Software/Mac OS that can be run in a browser. There is some amount of “gap filling” between the 7.5, 8.1 and 9.0.4 images (Mac OS 8.5 has a special place in my heart because I got my start doing real development for the 32-bit icns icons that it introduced). However, older versions (System 6 and earlier) are only supported in other emulators, as this handy spreadsheet shows. There is an existing mini vMac Emscripten port that could serve as a starting point.

There is some cost involved with all this. Currently this includes 4 .app domain names at $10/each per year, the Cloudflare Workers Paid plan at $5/month (to get Durable Objects that are used for LAN) and a GitHub Git LFS data pack at $5/month. This is still at a point where it’s a reasonable “hobby” budget (especially compared to woodworking or 3D printing or photography, as examples of hobbies where gear cost can escalate), but I’m considering setting up a Patreon or GitHub sponsorship in case others do want to support the work.

Though I’m enjoying the solo aspects of this work (as far as working on whatever I want with no coordination overhead), I’m not opposed to outside contributions — it was nice to get a PR to improve the scrolling behavior on small screens. Hopefully there’ll be more of that in 2023.

But really the main goal is to continue to have fun.