Category: Uncategorized

  • Recovering a damaged Ensoniq disk…well, trying, anyway

    I got a mail in early September from Alex Crabb, who had downloaded the sd1diskutil code from GitHub, and was trying to image some 35-year-old disks from his dad’s SD-1. (Pause while I feel old for a minute.)

    He was able, using a GreaseWeazel setup, to get some MFM flux transition files from the one disk he could not get to read, and asked me if I could see if we could get anything off of it.

    We had both an HFE file (which sd1diskutil does know how to read directly) and an SCP multi-pass read one, which at the time I got the files, it did not know how to read.

    Step 1 — Try the HFE directly

    sd1cli hfe-to-img failed immediately:

    Error: HFE missing sector at track 0 side 0 sector 0

    Raw inspection of the HFE showed only 255 of 1,600 sectors readable (15.9%). This was pretty bad news already, and I was worried we’d not be able to get anything at all off the disk. Fortunately, there were no CRC errors on what was there — the data that survived was clean, just very sparse.

    The damage pattern was worst on early tracks (0–15), exactly where the OS, FAT, and directory structures live. Ouch.

    BUT! We could see some files!

    ALEX 96      SixtySequences   59 blocks  28,193 bytes
    MUZAK 3      OneSequence      12 blocks   5,332 bytes

    Both files in subdirectory 0 (blocks 15–16), which survived intact. Optimism surged. Prematurely.

    Step 2 — Decode the SCP

    As mentioned, the codebase had no SCP support. No problem. I threw Claude at it, and we were able to reverse-engineer the format:

    • Header: "SCP" + metadata at fixed offsets, track table at byte 16
    • Track entries: "TRK\0" (4-byte magic), then 3 × 12-byte revolution headers
    • Revolution header: index time (LE u32), flux count (LE u32), data offset from
      TRK start (LE u32)
    • Flux data: big-endian u16 values in 25 ns ticks between flux reversals

    Inspecting the first few flux values gave us our in — ~80, ~160, ~240 ticks mapped cleanly to 2 µs / 4 µs / 6 µs, which are the three legal MFM inter-flux intervals for 250 kbps DD.

    Excellent! We’re not fumbling in the dark altogether. Quantizing these to the nearest 80-tick multiple gave us a clean MFM bitstream.

    The SD-1 uses standard IBM/ISO MFM: A1* sync marks (0x4489), IDAM (0xFE), DAM (0xFB), CRC16-CCITT, so our existing HFE decoder logic ported directly. Lucked out big.

    SCP result: 1,178 / 1,600 sectors (73.6%). This was a 3-revolution capture from the disk, and it recovered a lot more than the single-pass HFE.

    Claude’s script to do this was saved as tools/scp_to_img.py.

    Lots more sectors! Was it enough?

    Alas, it was not. Still a ton of damage where we really needed it: in the directories.

    Step 3 — Merge SCP + HFE

    We did our own Hail Mary here, on Claude’s suggestion, and tried merging the HFE MFM decode with the SCP one. They were both from the same Greaseweazle session, and the guess was maybe one had data the other did not.

    Cross-checking found 109 sectors the HFE had that the SCP missed — mostly whole tracks (T35S1, T51S0, T55S0, T57, T59S1, T61S1, T77S1, T78S0) where the SCP flux decoder apparently lost sync but the HFE decode succeeded. Would this image make the cut?

    Merged result: 1,287 / 1,600 sectors (80.4%).

    Saved this new tool as tools/merge_scp_hfe.py.

    Step 4 — Assess the files

    Sadly, no.

    Despite 80% overall recovery, both files were in the most-damaged zone (early tracks 0–14):

    FileBlocksReadableStatus
    ALEX 965919 (32%)Severely damaged
    MUZAK 3125 (42%)Unrecoverable

    FAT damage (blocks 5–14): FAT block 6 (covering entries ~171–340) was unrecovered, breaking MUZAK 3’s chain after the first block. The contiguous block count in the directory entry let us bypass the FAT and work directly with physical blocks.

    MUZAK 3 (OneSequence): 7 of 12 blocks missing, including a contiguous run from bytes 512–2559. The SD-1 cannot load a sequence with holes. Unrecoverable.

    ALEX 96 (SixtySequences): 40 of 59 blocks zero. The 60-slot header region (blocks 23–44) was mostly destroyed; only headers for slots 0 and 1 survived intact. Slot 1 had a readable header but its event data fell in a missing block.

    We saved this recovery extractor as tools/triage_sequences.py.

    Step 5 — Extract that one intact sequence

    SixtySequences layout places the event pool at file offset 11,776, with
    sequences stored in slot order, each padded to a 512-byte boundary. Slot 0’s event data (70 bytes) lands in the very first event-pool block (disk block 46), which was recovered.

    Wrapped up the 70 event bytes as a SingleSequence SysEx
    (F0 0F 05 00 00 09 [nybblized] F7) and saved it to extracted/ALEX_96_slot00.syx.

    Saved this very custom tool as tools/extract_intact_sequences.py.


    What did we get?

    • 1 sequence from ALEX 96, slot 0, 70 bytes — valid, loadable by SD-1 hardware.
    • Everything else on the disk is unrecoverable.

    Had to report this sad news to Alex, who said, “I am going to check the Logic projects I did a long time ago in early 2025 because I’m almost positive that disk was one of the ones I was able to load onto my dad’s SD-1 and play the sequence into my DAW, track by track, syncing everything up to the count off clicks …”

    So, in summary good(ish) news! The data had been saved elsewhere by other means, and the failure to read the disk wasn’t a complete loss.

    Lessons / Notes for Future Recovery Attempts

    The failure is consistent with magnetic decay from the outside in, very common as floppies age: early tracks (0–15) are worst, later tracks (30+) are largely intact, outermost tracks (55–78) have scattered whole-track losses. The critical OS/FAT/directory structures on tracks 0–1 took the worst hit.

    The disk was readable enough to identify and partially decode, but the files themselves were sadly stored in the most vulnerable zone.

    So now we know what to do if we want to try recovering old disks in the future:

    1. Always capture SCP + HFE in the same Greaseweazle session.
      The two decoders have complementary failure modes and together recovered 109 more sectors than either alone.
    2. More SCP revolutions help.
      This file had 3; 5 or more is probably better for marginal sectors. Greaseweazle supports --revs N. There’s no reason not to do ten or twenty other than total file size. As damaged as this disk was, though, this probably still wouldn’t have gotten all the data.
    3. The contiguous block count in the directory survives FAT corruption.
      When FAT chains are damaged, the contiguous_blocks field in the directory entry lets you still locate file data.
    4. Early track damage is the worst case for SD-1 disks.
      The OS loader, FAT (blocks 5–14), and directory (blocks 15–22) all live on tracks 0–1. A disk that looks mostly readable (80%) can still have completely inaccessible files if those tracks have failed.
    5. SCP format gotcha: the "TRK" magic is 4 bytes (TRK\0), not 3.
      The flux count and data offset are both relative to the TRK header start, not the file start. Flux values are big-endian u16 despite the rest of the SCP header being little-endian.

    Final thoughts

    Despite our not getting the data, at least Alex was able to find out what was on there (at least partially), and know he had it from other sources.

    It wasn’t a completely useless exercise, as we’ve been able to add some more recovery tools to the library’s arsenal.

    I did find it interesting that Claude generally prefers to use Python over reusing the library code; I suspect that’s because it’s easier to reason from a much larger corpus of Python than Rust.

    In any case, we’ve significantly expanded the capabilities of the library to recovery from just format conversion, and that was a worthwhile thing to spend time doing.

  • 2015 MacBook Air, 2026 version

    In 2024, we took our first return trip to Malaysia since the 1990’s. We thought we might need a laptop for the trip, so I picked up a 2015 MacBook Air. Soldered memory, unfortunately, so the 8GB max was all we were going to get, and a 128GB disk.

    Somehow we managed to cram enough working files onto a disk a quarter of the size of Shymala’s daily driver onto that machine and get Sequoia on it.

    It ran very, very hot. And very slow. So much so that we ended up not using it much at all.

    Fast-forward to 2026, and we’re planning another trip. We actually do need a working machine to manage, or at least monitor, our investments while we’re on travel.

    None of our daily drivers was light enough, or riskable enough, to take with us, so I started looking at options.

    Option 1: a Mac Neo. For about $700, I could get a Neo with 8GB memory and a 512GB disk. This would jump everything way forward, but a 512GB disk and 8GB memory might be enough, or it might not, and $800 is still expensive for a “this might get stolen” machine.

    Option 2: a refurbished M-series Air. Least-expensive option here is still about $1000 (M4, 512GB disk, 16GB memory — honestly a tremendous value for a machine that stays home but felt like a risk for a travel machine).

    Option 3: do something with this Air to make it work. This felt like the cheapest and least-stress option, but there are constraints.

    The real bottleneck on this machine is storage. We were not going to be able to do anything about the memory: we did get a maxed-out 8GB machine but only a 128GB disk was affordable. (OWC is a great source for older Macs, but if I wanted a big internal disk, the prices were extortionate.)

    The internal SSD is user-replaceable, at least; the real problem with the previous trip was trying to run Sequoia on a machine that was extremely strapped for disk space. No swap, no disk space left; it ran hot and slow.

    So what can we run that doesn’t stress the machine too much?

    I first tried running Elementary OS as the most Mac-like Linux option. Elementary does not like the Air. I tried multiple variations of installs and tweaks, and it just would not run on the machine.

    Okay, second option: let’s drop back to a “this absolutely will run” option and see how that works. Linux Mint was the recommended distro, and it did indeed work like a charm. Installed easily, ran great. Had the usual mess with the wifi, but tethering the machine to my phone to have internet until the drivers could be forced to build and install covered that.

    It was relatively easy to configure Cinnamon, the Mint window manager, to look pretty much like MacOS, and to install modificatons to properly use command-whatever instead of a Windows like control-whatever for keyboard shortcuts. It ran fast and well.

    Two shortcomings, though.

    • It still had a lot of “this is not a Mac” feel. I anticipated a lot of pushback from Shymala on this, and rightly so; if you have a work rhythm, having something intrude to say “no, you have to do that this other way” is jarring.
    • It could not run Numbers. Our financial planning is all implemented in Numbers, and porting it all to Google Sheets would a) be a ton of work, b) be a ton of repeated work, and c) put our finances on the servers of a company that decided “don’t be evil” was too limiting, In addition, if we suddenly lost access to the associated Google account, we’d have to build the whole thing from scratch again.

    So we needed a MacOS solution again.

    I had mistakenly gotten the idea that the 2015 Air was stuck at Mojave like the 2012, and had dismissed trying to just run whatever it could run natively, but I discovered that both it could run Monterey, and that upgrading the internal SSD to 1TB would cost me less than $300; this meant that I could get an 8GB, 1TB machine for $600.

    This was a no-brainer.

    I ordered the disk from MicroCenter (interestingly when it arrived, it had a $499 sticker on it! I checked, and yeah, I only paid $300), and a $20 adapter so it would fit the Air. The most difficult part was finding the pentalobe screwdriver I needed to get the back off the Air! I finally found a “phone repair toolkit” from WalMart for $20.

    The disk showed up and it was ridiculous. It was the size of a stick of gum.

    Teardown was easy.

    • remove the screws, pull on the edge of the back to open it up.
    • pull the battery connector loose so the machine couldn’t accidentally power up while I was working
    • remove the T3 screw holding the SSD in place.
    • pull the old disk out and pivot it up a little to get it to pop out. (The old disk was an OWC upgrade to 128GB! Thing must have had like NO disk space when it shipped.)
    • Slip the adapter onto the new disk’s pins, then reverse the removal to install the new one. Took a little push to make sure the disk was seated in the adapter, and the adapter was seated in the machine.
    • Put the disk retaining screw back.
    • Reconnect the battery connector.
    • Put the back on and reinstall all the screws (I hear you saying, “NO NOT YET”, and yes, you’re right.)

    Awesome! That was easy.

    Power up, hold down the magic key combo to start internet recovery, wait…and it doesn’t see the disk. Okay. Maybe it’s just not formatted. Run Disk Utility…and there ain’t no disk.

    Tear it down again, check all the connections, don’t put the back on again yet, just lay it in place. Try again. Still no disk. Check connections again. Looks fine.

    Panic. Did I fry this brand-new disk?

    Oh, wait. Here’s this card that came with the disk adapter, which says in teeny-tiny print that internet recovery doesn’t work with NVMe disks, and that I need the official Apple installer.

    Well, poop. Fine.

    Download the installer, follow directions to write the installer to a datastick. Go shopping while the installer app very slowly writes a bootable installer to the datastick.

    Come back, insert the datastick, boot up from that, run Disk Utility and there it is. Whew. Format it GUID and Apple format. Exit, install Monterey…and there’s the disk! Woot!

    Installer ran fine (if slow); reboot came up, finished the Monterey install, and all is well.

    Currently using the Air for browsing, verified Numbers works fine here. All good. So for about $625, I have an excellent alternative for the trip, and no one will be depressed if something happens or it dies.

    Followup post the trip

    I ended up installing Ventura as well in a separate partition, since I had a lot of disk space available. Ventura was a lot closer to Shymala’s Sequoia install; I had to fight pretty hard with Dropbox to get it to install the auto-download and sharing features, but I eventually managed to. Monterey doesn’t support this at all.

    Monterey also absolutely cannot run Chrome. Venture could, but the fans seriously spun up when I did.

    On the hotel wifi, Ventura network performance was abysmal, and I could not run any Second Life viewer whatsoever. Every one crashed instantly.

    I dropped back to Monterey, and network performance was fine.

    Shymala needed to use the laptop to do some prep for a reading, and Monterey’s “I can’t download that file when you click it, you have to make Dropbox do it, and I think I’ll decide to not download anything at all now because” was a serious issue. She was in writing flow, which is not a time to explain, “no, see, we need to reboot into the other OS and then the software you need will work”.

    On the other hand, if most everything is in the cloud and I’m not trying to do iOS development on the machine, it was quite easy to share the 1TB disk between two people, even with multiple OSes loaded. A Mac Duo would probably work fine as a travel machine as long as we both have an external disk for data.

    The 2015 Air was a noble experiment but is not a long-term option as a Mac. Either I’ll swap in the old disk, or repurpose it as some kind of a Linux box.

  • Writing as Music

    I subscribe to Recommendo, a mailing list from Kevin Kelly and Mark Frauenfelder. I used to read Boing Boing a lot for interesting stuff; I just took a peek and it’s mostly trivia now. Admittedly, some is interesting. (The sea permanently changing color in 1907 was a new one.) Generally, it hasn’t had much for me of late.

    Recommendo reminds me of the Whole Earth Catalog: intriguing tools and sites, Useful things. So I keep that one going.

    And in particular, the site Text As Music was a great find.

    You take a sample of your writing, paste it in, and it tells you what the statistics are, in terms of sentence lengths:

    • Micro: Very short. One or two words.
    • Short: Less than five, more than a couple.
    • Five: Well…five.
    • Medium: My best guess is that these are less than ten words.
    • Long: my usual elliptical blithering with parentheticals, asides, and dependent clauses.

    The idea is that your text should have a variety of sentence lengths. Tiny. A bit longer, still short. Sentences that stretch out a bit. And the indulgent, let’s-ramble-a-little, longer sentences, that tease out more of a thought.

    I’ve tried a few of my blog posts from here and okay, I am not a short-sentence guy. Most of mine are mid-size or long. I will blame my eighth-grade English book for all the frowning it did at sentence fragments. Which are useful, so there.

    And I am someone who likes musical prose, so seeing the long stretches of long sentences made me think, Okay., Punch it up some, huh? You do not need to Molly Bloom the hell out of every thought.

    So I’m going to go ahead and write as I do, but then look back over it and see where I can make those breaths and pauses. Where I can land a thought. Be terse, where it works.

    I think I learned to write in an academic setting more than anything else, and a long, well-rounded sentence that covers all the bases and properly excuses itself having the temerity to have like, an opinion, is a commonplace. So, no.

    I will be having short ones. And the long ones, too, still a place for those; but this is definitely a think to think about. Much appreciated!

    (And yes, there’s a “have AI improve your rhythm” prompt on that page, but I want to learn to play the drums. I don’t want a drum machine.)

    Tried it for this post; it’s definitely an effort! (I do get credit for short sentences when I use semicolons. Small blessings, I guess.)

  • Checkpoint

    A quick summary of a bunch of stuff I’ve done lately.

    • Developed a new special interest: the LGP-21. A real physical one was just recently revived by Usagi Electric, and his watchers have created a simulator and toolchain for this remarkable old machine — remarkable in that despite its relative primitiveness, the programmers for that machine back in 1963 created some impressive software for it.
      • Wrote some extensions for the lgp21-tools assembler: a test suite, and a macro facility (which may not get merged, but I can use it im my copy)
      • Ported a couple of BASIC computer games to the LGP-21, including good old WUMPUS, BAGELS, and an implementation of Steve Jackson Games’s Zombie Dice.
      • Translated the German LGP-21 Subroutine Manual (nowadays we’d call it the Software Library or somesuch) into English in LaTeX markup. (Learned a fair amount of LaTeX in the process. Like it!)
    • Continued working with the timed-voiding app. Working pretty well now, with over a month of data recorded. Added data export today (working) so it can be analyzed by the LLM of your choice, and currently continuing to refine it. It may make it to the App Store sooner or later.
    • sd1diskutil continues to evolve; between Sojus and I, we may have killed the final bug in the library, and it looks like it fully supports all file types.
    • Car research tabled for the moment.
    • Felt a little uncomfortable implementing Zombie Dice for the LGP-21. Admittedly we have an installed physical user base of exactly one machine, and maybe a half-dozen people who might try running it on the simulator, but it’s still a registered trademark of SJG. So I thought up a new game altogether that turned into a combination bidding war and Zombie-dice-like press-your-luck game. Rules complete and a tentative deck of cards made up, but I need to playtest it to see if it’s actually anything before trying to program it. It reminds me a LOT of the Cheapass games of yore.
    • Surgically removed the Chrome 4GB model download.
    • Big data storage cleanup, still ongoing.
    • Found out I’m still missing a bunch of music from my iTunes library. May re-up the iCloud Music library to recover it. Which will mean Yet Another iTunes library Merge. Song Sergeant does a great job cleaning up after one of those.
    • Did a lot of spreadsheets for changing investments in the current economic situation. Too specific to post about.

    Probably more, but that’s what I remember.

  • Shaping up

    The last few days have been devoted to other things like dental emergencies, but the app continues to shape up.

    I discovered that expecting live activities to do much beyond show you something and open the app if you tapped them is quite fragile and can make things very complicated. Switching that over to just an “open the app with a custom URL schema” both made the app a lot simpler and a lot more dependable.

    I took a refactoring pass at the code, and I’ve isolated the “what state is the app in now” into a single oracle class that takes in all the data (quiet schedule, interval since last event, current time, and so on) and figures out the state of play. All the other parts of the app just look at the oracle’s computed app state and then proceed based on that.

    This means I can mock the oracle and easily test all the rest of the behavior of the app, and I can mock the oracle’s inputs and validate that it makes all the right decisions.

    This vastly simplifies the app and makes all of it testable, so I can get a little more ambitious:

    • The app can query (if the user permits) HealthKit and get a “quiet time” estimate from collected sleep data
    • I can make the quiet-time boundaries a little more squishy to deal with early-morning wakeups and shifting the start of the window to match
    • The recording is dead simple and accurate. No more lost events.

    So far I’ve got something that does a good job of posting the reminders and recording the data; now I can get after making that recorded data more useful to both the user and to their urologist.

    It’s now an app that acknowledges (and explains) that you have two different situations where you do bladder training: going too much (the more common situation) and going too little (the less-common situation I’m in).

    It’s still very utilitarian straight-out-of-the-box SwiftUI, but: ship working code, then repackage with beauty.

    Very happy with it, it’s working great for my use case now; even if I didn’t refine it further, it’s helping me keep on schedule with my bladder training.

    Three months to go!

  • Bladder training app progress

    I’ve gotten to a decent beta at this point, and I’m eating my own dogfood: my urologist put me on bladder training with a two-hour interval, so the app is set up with that as the default interval.

    I’ve been testing it as I go along and added a few things that I needed:

    1. Custom alert sound. I get enough alerts that I need it to sound different than the others. I’ve got a nice water-drip noise, distinctive but not obtrusive, that fires when the interval is up.
    2. The process is not loads of fun, so I’ve added a little whimsy to the alert messages.
    3. I’ve got a live activity so I don’t have to unlock the phone to reset the interval when I go.
    4. Added quiet hours so it doesn’t ping me every two hours when I’m trying to sleep.
    5. Explanatory “why the hell are we doing this anyway” text. Okay, I don’t need that, but it’s useful for someone to refresh their memory, or to get an explanation of why we’re doing it if their urologist has said, “make sure you pee every two hours.”

    Running with this version a while to see what else it needs. Will look into art and design after it’s functional.

  • Not an app I thought I’d need, but here we are

    So one of the things that can happen after prostate cancer radiation treatment (and I note that I don’t think I’ve written a post about that — I suppose I was a bit distracted at the time) is that you can develop what’s called a stricture, ot narrowing, of the urethra from scar tissue forming.

    This can cause you to not completely empty your bladder when urinating, and that can cause your kidneys to back up and get stressed.

    You do not want to stress your kidneys.

    My urologist tells me, “Okay, we need to make sure you’re getting your bladder emptied as much as possible. I want you to go pee every two hours, whether you feel like you need to or not.” Which doesn’t seem like such a big deal, except that I am, as I have noted, somewhat ADHD, and if hyperfocus kicks in, I am very likely to get caught up in something for hours at a time, only dimly realizing that I’m tired/hungry/thirsty/need to pee.

    I could manually set myself alarms every two hours but that would be a pain to set up, and a pain to disable if I needed to. So I’m writing myself an app to do it.

    It pings me every two hours to remind me it’s time to go; if I do so early, it resets the next interval to two hours from that point. If I don’t respond, it keeps bugging me until I do.

    That’s the basic idea; I’m fairly sure I’ll want to add on to it (the process of writing this post reminds me that I really do want to be able to put it in vibrate-only mode when I’m, say, at a concert), but I’m going to get the basic version done and live with it for a while.

    No idea if this is common experience, but if it is, I’ll definitely put it out on the App Store.

  • Old man yells at cloud, redux

    I wrote about whether the cloud is a long-term safe option for your data a while back, and this article has brought me back around to thinking through that again.

    I think it’s important to realize that many of us (certainly I have) become heavily dependent on cloud services for extremely important things. Not “break the bank, on the streets” important but “huge sections of my memories not safe” important.

    I am not dissing cloud services just because they are cloud services. I am looking at them again in a more jaundiced light because they are not services in the sense of “there is something wrong, and I can talk to someone and sort this”.

    All cloud services hang a sword of Damocles over your head.

    If you somehow trigger their automated systems, they will deny you in a flash, and the chances that you will ever be able to resolve the problem are near zero, unless you are a major source of income for them (and we’re talking millions of dollars of income, not something a typical person-in-the-street can generate) or have friends in high places: either someone inside the walls who “knows a guy” or a large and public means of embarrassing them for doing something blatantly stupid via automation.

    For instance, there have been large numbers of automated account closings on Facebook of late. People have had their accounts closed in the process of opening them. Yes, this is completely nuts. No, Facebook is not doing anything. Why bother? They have plenty of users now. Who cares if a few hundred or a few thousand people lose access? (Hint: it’s the people who’ve had those accounts and trusted Facebook with their data.)

    There’s the guy who uploaded pictures of his kid’s rash for his doctor to check on Google — and lost his account.

    There have been a number of folks who lost access to their Apple IDs, resulting in major financial losses, and because they created the IDs long before two-factor and have forgotten their questions and answers…are just screwed.

    Shymala and I have had a lot of frustration with Google and Squarespace dropping the ball on her Google Workspace connected to a .studio domain. The workspace is still currently AWOL. And Squarespace is getting 5 Benjamins a year, so it’s not like this is a free account and they don’t have to care at all.

    What all these have in common is trusting an external entity to have their bests interests in mind.

    Unfortunately, even if you’re paying for it (see Squarespace), you cannot trust that a cloud provider will not arbitrarily decide that you have violated some rule – which they will not often reveal to you, because security – and you’re just done, with no options.

    So in this post, I’m going to try to look at what cloud providers I’m using and what I could do to mitigate risks with that provider.

    Google

    The biggest risk here is email. I have several different accounts, some quite old with a lot of archived email, and a few that are simply convenience accounts that I use to sign into things.

    I also have a Google workspace for mail at the pemungkah.com domain, with a couple accounts set up there.

    Risks:

    • Losing access to my primary GMail would break logins for a lot of things. I have used “Sign in with GMail” a lot. I would also lose a lot of archived mail.
      • A search in 1Password tells me I have 439 accounts associated with my primary GMail. 60-ish are “signs in with GMail” for that account.
    • Losing access for my secondary would inconvenience me more than anything else, mostly because I wouldn’t be able to receive password resets or change-of-email confirmations. Some would be harder than others to recover.
      • 29 accounts associated with my music GMail.
    • Losing access to the Google Workspace would be a relatively minor issue; I haven’t used it for a lot. I’d have to set up new logins for some of my streaming accounts.

    Probably the best thing would be to move my mail to a custom domain. I have several that would do as a base domain, and I could just set up mailboxes for the alternate accounts. A self-hosted mail service would be better; there seem to be some reasonable alternatives but they’d need to be evaluated. Using ProtonMail or Tuta with my own domain is a middle ground.

    Apple IDs

    Losing my Apple ID would be costly monetarily and emotionally.

    • Only 4 non-Apple accounts depend on my Apple ID, and I could live if I lost access to all of them.
    • All of my photos are in iCloud. If I lost access to my Apple ID, i could concievably permanently lose access to those. I have backed them up to Google Photos (yeah, not great either).
    • I’d lose access to my Apple Developer account.
    • My Find My would be broken, leading me to have to scramble to rehome all my devices. Stolen Device Protection would make that much harder to do, but I sure don’t want to turn that off.
    • I use Find My Boxes to track where stuff is in storage. If iCloud locks me out, I can’t access that data anymore. (I have written an unloader script that converts that data to HTML, so I’m less screwed than I might be there.)

    Microsoft ID

    I use this for very little. If it went away I could set up another one.

    GitHub

    A pain in the ass, but recoverable. I could create repositories somewhere else.

    Dropbox

    I need to more deeply investigate this. We use Dropbox a lot for archiving stuff, and sharing data for Shymala’s writing and art. If we lost access, there’d definitely be some problems — some of the original art has been sold, and it’d be impossible to get prints of it anymore.

    Plans

    I need to move my primary email away from GMail alone. If it’s coming to a custom domain, I can just move the mail somewhere else. Proton or Tuta seem best; self-hosting is probably even better but incurs a lot more work for me to do, plus the hosting costs, or doing it locally. (See below for thoughts on that.)

    Once I have the new mail, I need to change everything that uses GMail login first, then work through the 400 other accounts in batches. Some are simply “fuck you” accounts for the idiots who use my mail, and I can simply forget they’re there and remove them. The rest I’ll move to the new custom domain.

    I can’t completely get rid of my Apple ID without losing iCloud and Apple Developer access, so I’ll set up a new Apple ID for the custom domain, and then migrate to it. The purchases on the old account will have to stay there, so I’ll set up a new Family account with the new email as the primary and add the old Apple ID as a secondary. I will add the new account to my Apple Developer origanizatin, and move all the responsibilities over to the new account, then start purchasing the Apple Developer access from the new account.

    For GitHub, I’ll want to fork everything to the new account and close the old one. (GitHub may have a “move account”; if so I’ll use it). I will want to self-host a Git repository that I can push to as well.

    Self-hosting

    It’s possible to host most of this stuff on a server either at a colocation site or on a box literally in the house with Tailscale. Cohosted costs extra money, but has the advantage that I could put it at (say) Hetzner and outside the US, given the current political climate. In-house is max control and possibly a lesser ongoing investment but I’d incur a dependency on Tailscale. Going to have to work out the numbers and decide about this.

    Summary

    My heavy dependence on one GMail address and Google in general is not great. I should move the pemungkah.com domain to somewhere else (handwave) and use a custom email instead of GMail, and change everything over to that from the GMail address at a minimum.

    Dropbox is going to be an issue, and I still need to make sure I have a way to really for-sure back that up.

    Everything else depends on the GMail move, so I will be concentrating on that over the next few months.

  • Flutter experiences

    TL;DR: Flutter builds are as much fun as Java and Scala ones, and you spend more time screwing with the tools than you do getting anything done. I don’t think I’m going to switch, at least not now.

    As I’ve mentioned before on the blog, I maintain an iOS application for RadioSpiral’s online radio station. The app has worked well and successfully; the original codebase was Swift- Radio-Pro, which works as an iOS app and a MacOS one as well (I have been doing some infrastructure changes to support Azuracast, as previously documented on the blog.)

    We do have several, very polite, Android users who inquire from time to time if I’ve ported the radio station app to Android yet, and I have had to keep saying no, as the work to duplicate the app on Android looked daunting, and nobody is paying me for this. So I’ve been putting it off, knowing that I would have to learn something that runs on Android sooner or later if I wanted to do it at all.

    Randal Schwartz has been telling me for more than a year that I really should look at Dart and Flutter if I want to maintain something that works the same on both platforms, and I just didn’t have the spare time to learn it.

    Come the end of May 2023, and I found myself laid off, so I really had nothing but time. And I was going to need to update the app for IOS 16 anyway at that point (the last time I recompiled it, Xcode still accepted iOS 8 as a target!) and I figured now was as good a time as any to see if I could get it working multi-platform.

    I started looking around for a sample Flutter radio app, and found RadioSai. From the README, it basically does what I want, but has a bunch of other features that I don’t. I figured an app I could strip down was at least a reasonable place to start, so I checked it out of Github and started to work.

    Gearing up

    Setting up the infrastructure Installing Dart and Flutter was pretty easy: good old Homebrew let me brew install flutter to get those in place, and per instructions, I ran flutter doctor to check my installation. It let me know that I was missing the Android toolchain (no surprise there, since I hadn’t installed
    anything there yet). I downloaded the current Android Studio (Flamingo in my case), opened the .dmg, and copied it into /Applications as directed.

    Rerunning flutter doctor, it now told me that I didn’t have the most recent version of the command-line tools. I then fell into a bit of a rabbit hole. Some quick Googling told me that the command line tools should live inside Android Studio. I ferreted around in the application bundle and they were just Not There. I went back to the Android Studio site and downloaded them, and spent a fair amount of time trying to get sdkmanager into my PATH correctly. When I finally did, it cheerfully informed me that I had no Java SDK. So off to the OpenJDK site, and download JDK 20. (I tried a direct install via brew install, but strangely Java was still /usr/bin/java, and I decided rather than tracking down where the Homebrew Java went, I’d install my own where l could keep an eye on it.

    I downloaded the bin.tar.gz file and followed the installation instructions, adding the specified path to my PATH… and still didn’t have a working Java. Hm. Looking in the OpenJDK directory, the path was Contents, not jdk-18.0.1.jdk/Contents. I created the jdk-18.0.1 directory, moved Contents into it and had a working Java! Hurray! But even with dorking around further with the PATH, I still couldn’t get sdkmanager to update the command-line tools properly.

    Not that way, this way

    A little more Googling turned up a Stack Overflow post that told me to forget about installing the command-line tools myself, and to get Android Studio to do it. Following those instructions and checking all the right boxes, flutter doctor told me I had the command-line tools, but that I needed to accept some licenses. I ran the command to do that, and finally I had a working Flutter install!


    Almost.

    When I launched Android Studio and loaded my project, it failed with flutter.sdk not defined. This turned out to mean that I needed to add

    flutter.sdk=/opt/homebrew/Caskroom/flutter/ 3.10.5/flutter

    (the location that Homebrew had used to unpack Flutter — thank you find) to local.properties. After that, Gradle twiddled its fingers a while, and declared that the app was ready. (It did want to upgrade the build, and I let it do that.)

    Build, and…

    The option 'android.enableR8' is deprecated. 
    It was removed in version 7.0 of the
    Android Gradle plugin. 
    Please remove it from 'gradle.properties". 

    Okay, I remove it.

    /Users/joemcmahon/Code/radiosai/.dart_tool/ does not exist.

    More Googling, Stack Overflow says Run Tools > Flutter > Pub Get. Doesn’t exist. Okaaaaaay.

    There’s a command line version:

    flutter clean; flutter pub get

    Deleted dart_tool, then recreated it with package_config.json there. Right!

    Back to Android Studio, still confused about the missing menu entry, and build again. Gradle runs, downloads a ton of POMs and

    Couldn't resolve the package 'radiosai' in 'package:radiosai/audio_service/service_locator.dart'.

    Looking one level up, in :app:compileFlutterBuildDebug, Invalid depfile: /Users/joemcmahon/ Code/radiosai/.dart_tool/flutter_build/bff84666834b820d28a58a702f2c8321/ kernel_snapshot.d.

    Let’s delete those and see if that helps…yes, but still can’t resolve
    radiosai. Okay, time for a break.

    Finally, a build!

    Another Google: I wasn’t able to resolve the package because I needed to pub get again.

    Module was compiled with an incompatible version of Kotlin. 

    The binary version of its metadata is 1.8.0, expected version is 1.6.0. Another Google. One of the build Gradle files is specifying Kotlin 1.6…it’s in /android/ build.gradle. Update that to 1.8.10, build…Kotlin plugin is being loaded, good. Couple
    warnings, still going, good.

    BUILD SUCCESSFUL

    Nice! Now, how do I test this thing? Well, there’s Device Manager over on the right, that looks promising. There’s a “Pixel 3a” entry and a “run” button. What’s the worst that could happen?

    Starts up, I have a “running device” that’s a couple inches tall, on its home screen. Hm. Ah, float AND zoom. Cool. Now I realize I have no idea how to run an Android phone, and I don’t see the app.

    https://developer.android.com/studio/run/emulator…nope. Beginning to remember why I didn’t like working in Scala… Gradle upgrade recommended, okay, and now

    Namespace not specified. Please specify a namespace in the module's build.gradle. 

    Specified, still broken…googling…This is a known issue –
    https://github.com/ionic-team/capacitor/issues/6504

    If you are using Capacitor 4, do not upgrade to Gradle 8.


    Yeah, I remember why I stopped liking Scala. git reset to put everything back…

    Execution failed for task:gallery_saver:compileDebugKotlin'. 
    > compileDebugJavaWithJavac task (current target is 1.8) and 'compileDebugKotlin' task
    (current target is 17) 
    jvm target compatibility should be set to the same Java version.
    Consider using JVM toolchain: https://kotl.in/gradle/jvm/toolchain 

    Fix android/app/build.gradle so everyone thinks we’re using Java 17, which uses a different syntax, ugh.

    Fix it again. Same for the Kotlin target too.

    'compileDebugJavaWithJavac' task (current target is 1.8) and 'compileDebugKotlin' task (current target is 17) jvm target compatibility should be set to the same Java version.

    This is apparently actually Gradle 8 still lying around after the (incorrectly) recommended upgrade. Removing ~/ gradle to nuke from orbit. Also killing android/.gradle.


    [Aside: I am used to using git grep to find things, and it is just not finding them in this repo!]

    Cannot read the array length because "" is null

    WHAT.

    Apparently this means that Gradle 8 is still lurking. Yep, the rm ~/.gradle/* didn’t remove everything because of permissions. Yougoddabefuckingkiddingme. Sudo’ed it, relaunched with the fixes I made above. App runs!


    However it stops working after a bit with no reason indicating why. Let’s stop it and restart. Stop button did not stop it; had to quit Android Studio.

    Well. Okay. This is not promising, but let’s see the benefit of using Flutter; we’ll check out if the iOS side works. Seems a lot more straightforward, though I’m not doing much in Xcode. cd iOS, launch the simulator (important!), flutter run…and we get the Flutter demo project. Looks like the IOS version wasn’t brought over from the Android side. Why did you even do this.

    Do we all remember that I wanted something that worked on both platforms? I do. We don’t. Gah.

    So I’m putting Flutter aside, cleaning up the ton of disk space all this extra infrastructure took up, and will maybe come back to it another time.

    But for right now, the amount of work involved is absolutely not worth it because I’d have to write the damn thing from scratch anyway.

    Maybe I’ll run this through one of the LLMs and see if it can get me a common codebase as a starting point, but I am not sanguine.

    [Note from the future: my fellow DJ from RadioSpiral, DJ Cosmos, has written a Go/Flutter implementation that works great on Linux and Android, so I don’t have to do this anymore!]

  • So long, pemungkah@me.com

    That email address is now officially defunct.

    I checked; I created it back when I bought my iPhone 5.

    Years ago, it got leaked, and it has since been used for everything from someone in Canada’s VISA card to a bank account in Vietnam to some bozo’s Marriott account. (Hey David: thppppppppbt.)

    It got so bad that when Apple opened up creating Apple IDs with your own email, I did that, and essentially abandoned the me.com address.

    I used it for just political mail for a while, but I’ve gotten disillusioned that letting every random person running for office send me begging letters does any good, let alone giving them money. (They’re never “I did this because that’s what you sent me to Congress to do”, but “MY OPPONENT HAS MONEY! WE’RE CRYING! SEND ME MORE!” — and most of the time it’s futile anyway.) Mostly it was spam, people using it as a test email (especially screw you people: use MailHog or something. How are you going to know if the mail you’re sending looks right it it just goes somewhere you cant even read?)

    I closed it today. Only had a couple things still left from the downloads I used it for, and I can reinstall them from my primary if I want them.

    A grand experiment, but I’m not sad to never have to deal with it again.