How I rid my life of social media

If social media is working for you and you don’t care about the moral implications of using social media, then this post isn’t for you.

On the other hand, if the MAGA shift of social media, the love fest between Zuck, Musk, and Tr*mp and their slimey ilk makes you feel a little cringey. Or if you realize that you’re wasting countless minutes of your one wild and precious life, then this may be for you. Fair warning, it gets pretty technical; so stop wherever you want. It takes little more than a decision and a healthy dose of willpower. But if you want to block social media and cast it into the fires of Mt. Doom, here’s how.

macOS
If you run Windows, some of this won’t apply. If you run any of the various flavours of Linux, some will not apply either; but in the case of Linux you probably already know what you’re doing. So, some of the technical stuff will be macOS-only.

Step 1 - I made the the decision to leave social media

This is a consequential decision, one that you should consider carefully. I quit for several reasons:

  • Privacy, security and the basic human freedom to be left alone - As Shoshana Zuboff details in “The Age of Surveillance Capitalism”, companies like Meta have transformed our personal experiences into raw material for behavioral data markets. Mark Zuckerberg once said, “Senator, we run ads,” when questioned about Facebook’s business model, but this barely scratches the surface. These platforms operate within a far-reaching system that thrives on extracting, repurposing, and commodifying the “digital exhaust” of your browsing activities, turning you into a predictable and manipulable product.
  • Time - Do you know that negative feeling that arises when you find yourself scrolling social media? Some would call it “cringey”; and it’s something between shame and regret. When I really began to pay attention to my feelings while scrolling social media, I found that the overall feelings that it left were negative, mostly regret over having used my time in this way.
  • The moral dimension - You are supporting billionaires owners like Mark Zuckerberg (Meta/Facebook/Instagram/WhatsApp) and Elon Musk (Twitter/X) who appear to be amoral accumulators of wealth and power. Both have aligned themselves with an anti-democratic, autocratic political movement. I’m not fine with that.

I had long ago quit Twitter when even before the first Tr*mp Administration and before Musk purchased it, when it had already become a cesspool of extremism. And when Instagram became something more than a place to share interesting pictures, it didn’t hold much appeal to me. Remember how upset people became when they announced plans to show videos on Instagram? How times change…

Step 2 - I decided to just leave

I just left a post on Facebook about Zuckerberg’s new-found love of Tr*mp and asked “Who all’s OK with this?” then I signed off. It has been a week. People who matter to me and to whom I matter will know how to contact me. I suppose I will circle back and start deleting content so Meta has less to mine. But it’s too much trouble right now. When I eventually do that, I’ll leave a pinned post with my contact information and basically saying: “Hi, I’m not here any longer; if you ever want to reach me again, copy down this contact information.” After that, I’ll wait a few weeks then I’ll burn it down.

But you may want to do it differently and make a more graceful exit.

Step 3 - I blocked the hell out of social media

Here’s where it gets a little technical.

Blocked social media locally on macOS

I use Little Snitch to observe traffic in and out of my desktop macOS machines; and a few years ago I began building rules in a group called “Block Social Spies” that block outgoing traffic to Facebook and other social media sites. When I wanted to browse Facebook, I would have to manually switch groups to deactivate these rules. It created just a little barrier to prevent me from wasting too much time on these sites.

But the desktop solution only worked, well, on the desktop. What about the phone?

Deleted the phone apps

Facebook app? Gone. Instagram app? Deleted. Twitter/X app? Buh bye. All of them gone.

But what about the mobile web which on the phone can often still allow some limited access to these social media sites? Keep reading.

Spun up my own DNS server

A DNS server is like a phonebook for the internet. When you type in a website name like “google.com”, the DNS server translates that human-readable domain name into the numerical IP address (like 172.217.3.110) that computers use to find and connect to each other.

Here’s the basic process:

  1. You enter “google.com” in your browser
  2. Your computer asks a DNS server “What’s the IP address for google.com?”
  3. The DNS server looks up that information in its records
  4. It returns the correct IP address to your computer
  5. Your computer can then connect directly to that IP address

Without DNS servers, we’d have to memorize IP addresses for every website we want to visit. DNS servers make the internet much more user-friendly by handling these translations automatically.

Since DNS servers are critical to the functioning of the web, it gives us an opportunity to intervene on what gets looked up. What if we had a DNS server that “forgot” the IP addresses for social media sites, but remembered everything else? What if this DNS server was under our control and not Google’s; so that we could choose what lookups to block and which ones to pass on to the real DNS server? Well, that’s what PiHole does. By running PiHole on a server inside your network (almost any old spare computer will do), you can have your own DNS server to customize however you want. Typically PiHole is used to block tracking and ad delivery sites but you can use it to effectively block social media sites, too. After spinning up your instance of PiHole, you just have to give its IP address as the DNS address on all the devices on your network that you want protected, include, of course your phones.


Through a combination of blocks at both the DNS level and the local network stack on my macOS devices, I have successfully eliminated this surveillance and MAGA menace from my life. I’m not on Bluesky either because I don’t trust them (yet) to not sell-out to capitalist usurpers. We’ll see. Friendship and community should not be held hostage to private surveillance networks.

When will I get to 1,000,000 Anki reviews?

Recently I’ve been wondering how long it would take me to get to 1,000,000 reviews. Right now I’m sitting at between 800,000 and 900,000 reviews and for no other reason than pure ridiculous curiosity I was curious whether I could get SQLite to compute it directly for me. Turns out the answer is “yes, you can.”

CAUTION
Before you run anything that accesses the Anki database outside of the Anki application itself, you absolutely should backup your database first. You have been warned.

Here’s the query in its gory detail and then I’ll walk through how it works:

WITH 
daily_review_rate AS (
  SELECT AVG(reviews_per_day) as avg_daily_reviews
  FROM (
    SELECT 
      date(ROUND(id/1000), 'unixepoch') as review_date,
      COUNT(*) as reviews_per_day
    FROM revlog
    WHERE date(ROUND(id/1000), 'unixepoch') >= date('now', '-2 years')
    GROUP BY review_date
    HAVING reviews_per_day >= 25
  )
),
current_total AS (
  SELECT COUNT(*) as total_reviews
  FROM revlog
)
SELECT 
  ROUND((1000000 - total_reviews) / avg_daily_reviews) as days_to_million,
  date('now', '+' || ROUND((1000000 - total_reviews) / avg_daily_reviews) || ' days') as target_date
FROM current_total, daily_review_rate
WHERE total_reviews < 1000000;

Today when I run the query, I get: days_to_million = 698 and target_date - 2026-12-12.

Common Table Expressions

The first step in understanding the query is to grasp Common Table Expressions (CTEs) which are like functions that compute intermediate values for use in the main query. You can recognize CTEs by the use of WITH:

WITH 
  cte1 AS (...),
  cte2 AS (...),
  cte3 AS (...)
SELECT ... FROM ...

It’s basically telling SQLite, before you run the main query, compute these intermediate steps first.

Each CTE defined in the WITH clause:

  • Can be referenced by name in the main query
  • Can reference CTEs defined before it (like cte2 could use cte1)
  • Only exists for the duration of the query
  • Can be used multiple times in the main query if needed

We could get by without CTEs here if we were willing to compromise legibility by using more nested queries.


I’ll break down the query step by step:

  1. First, we have two Common Table Expressions (CTEs) that prepare our data:

    WITH 
    daily_review_rate AS (...)
    current_total AS (...)
    
  2. Let’s look at the inner workings of daily_review_rate:

    SELECT AVG(reviews_per_day) as avg_daily_reviews
    FROM (
      SELECT 
        date(ROUND(id/1000), 'unixepoch') as review_date,
        COUNT(*) as reviews_per_day
      FROM revlog
      WHERE date(ROUND(id/1000), 'unixepoch') >= date('now', '-2 years')
      GROUP BY review_date
      HAVING reviews_per_day >= 25
    )
    
    • The innermost subquery converts millisecond timestamps to dates (date(ROUND(id/1000), 'unixepoch'))
    • Groups these by date and counts reviews per day
    • Filters for days with 25+ reviews
    • The innter query only accepts reviews in the last 2 years so that we only consider these more recent data in the calculations.
    • The outer query then averages these daily counts
  3. The current_total CTE is simpler:

    SELECT COUNT(*) as total_reviews
    FROM revlog
    
    • Just counts all reviews ever done
  4. The main query brings it all together:

    SELECT 
      ROUND((1000000 - total_reviews) / avg_daily_reviews) as days_to_million,
      date('now', '+' || ROUND((1000000 - total_reviews) / avg_daily_reviews) || ' days') as target_date
    FROM current_total, daily_review_rate
    WHERE total_reviews < 1000000;
    
    • Calculates remaining reviews needed (1000000 - total_reviews)
    • Divides by average daily rate to get days needed
    • Uses SQLite’s date function to add these days to today
    • Only returns results if we haven’t hit 1 million yet

Each step builds on the previous ones, using the CTEs like building blocks to construct the final calculation. The query is structured this way to make each calculation clear and separate, rather than trying to do everything in one giant nested query.

Enjoy! You can run the query from the sqlite3 on the command line if you want, or if you’re on macOS you could use Base or another GUI SQLite application. Happy reviewing!

For any questions, you can get in touch at my contact page.

Why I'm quitting Facebook (again)

This isn’t the first time, but I hope it will be the last. Facebook, for me has long been a source of enjoyment and connection. But it also leaves me feeling cringey. So what changed? What changed is that Facebook has gone full-on MAGA and I’m not OK with that: “Meta CEO Mark Zuckerberg met with President-elect Donald Trump on Friday [January 10, 2025] at Mar-a-Lago, two sources familiar tell CNN.

Registering a custom collation to query Anki database

While working on a project that requires querying the Anki database directly outside of the Anki desktop application, I encountered an interesting issue with sqlite3 collations. This is is just a short post about how I went about registering a collation in order to execute SQL queries against the Anki db. CAUTION Before you run anything that accesses the Anki database, you absolutely should backup your database first.

Fix your Anki streak - the script edition

Like many Anki users, I keep track of my streaks because it motivates me to do my reviews each day. But since life gets in the way sometimes, I may miss my reviews in one or more decks. It has been years since I’ve neglected to do all of my reviews; but sometimes I will forget to come back later in the day to finish up some of my decks. Since I like to have a clean review heatmap, I will “fix” my streak in a skipped deck.

An API (sort of) for adding links to ArchiveBox

I use ArchiveBox extensively to save web content that might change or disappear. While a REST API is apparently coming eventually, it doesn’t appear to have been merged into the main fork. So I cobbled together a little application to archive links via a POST request. It takes advantage of the archivebox command line interface. If you are impatient, you can skip to the full source code. Otherwise I’ll describe my setup to provide some context.

A Keyboard Maestro action to save bookmarks to Espial

So this is a little esoteric, but it meets a need I encountered; and it may meet yours if you use Espial, Keyboard Maestro and are on macOS. For several years I’ve been using Espial a bookmark manager that looks and feels like Pinboard, but is both self-hosted and drama-free1. Espial is easy to setup, stores its data in a comprehensible sqlite database and has an API, which comes in handy when it came to solving the problem I encountered.

Louisiana and the Ten Commandments

Recently, the governor of Louisiana signed a bill requiring all public school classrooms in the state to display a poster-sized copy of the Ten Commandments. In the “Beforetimes” (before the current partisan Supreme Court took shape), this would have been struck down immediately as a violation of the Establishment Clause of the First Amendment. This bill is a clear violation of that clause. I imagine that the justices will dance around the cultural and historical significance of the document without stopping to consider the state’s motives in passing this law.

Improving vegetable seed germination with chemical pretreatment

Some vegetable seeds, particularly many exotic chilli pepper varieties and some Asian eggplants are tricky to germinate. After trying the obvious things - cold-induced forced dormancy (cold stratification), abundant moisture, high humidity, and temperatures over 80F, I’ve found that some seeds simply do not germinate with much success at all. But having read a number of articles on this problem, we decided to try an intensive chemical process to see if we could achieve better results.

A quick word on ATtiny 1-series interrupts

The Atmel AVR 8-bit microcontrollers have always been a favourite for tinkering; and the massive popularity of the Arduino based on the ATmega 168 and 328 MCUs introduced a lot of hobbyists to this series. The companion ATtiny series from Atmel were the poor stepchildren of the ATmega controllers to an extent - useful for small projects but often quite limited. However, the acquisition of Atmel by Microchip Technology in 2016 ushered in a new series of MCUs bearing the same moniker of ATtiny, but much more capable and innovative.