Permalink

Mastodon on Mac

I’ve been playing off and on with trying to get Mastodon running on my self-hosted Mac mini colocated at MacStadium (yes, it’s a referral link, but I’m just a really happy customer!) and with the release of Mastodon 4.2.0 I think I’ve finally got it up and working! I’ve heard of a few other people mention getting it to work on their Macs but I haven’t yet seen any real how-tos on, uh, “how-to” do it, so I figured I’d write up what I needed to do to get it working and document the pitfalls and caveats that I encountered along the way. While I could have hosted it on AWS or DigitalOcean or used one of the Mastodon hosting services, I was already paying for my Mac mini server and it had more than enough power to host a small Mastodon instance.

I’ve created a Github repository to keep track of any future changes and updates if necessary.

Important notes

Because I was already using my Mac mini to host my blog as well as some other applications, I was already running the following applications and services, so a few of these might need to be installed but I already had them in place, and the list of prerequsites might thus be incomplete:

  • Homebrew
  • Apache httpd
  • MariaDB
  • Redis
  • ffmpeg
  • ImageMagick
  • Supervisor

Almost all of the information I saw about configuring the webserver for Mastodon was using Nginx, and rather than run a second webserver I took the example Nginx configuration file and rewrote it for httpd as well as I could. I think I’ve got it all translated appropriately, but there might be some configuration commands I may have neglected or that may be entirely superfluous. Actually, now that I think of it, I guess that disclaimer applies to this whole writeup, heh. But, it seems to work!

Besides my Mac mini at MacStadium, I also use Amazon Web Services for some additional hosting features, and wanted to use S3 for my media uploads and host them through the CloudFront CDN; I didn’t try getting it set up to host my media files locally so I’m not sure what issues there may be with that configuration, and I’m not going to get into the S3 bucket creation and CloudFront configuration steps at this time.

I also use Fastmail (yes, another referral link, but again I’m just really happy with their service!) for my mail hosting, and am using their SMTP server to send emails from the Mastodon server. I couldn’t get it working using STARTTLS, so it’s using the SSL server instead.

Also, when I had once previously tried to get Mastodon working on my Mac I was having all sorts of issues with getting Ruby installed, and I eventually discovered Ruby on Mac which got it installed for me.

Install prerequisites

This is possibly an incomplete list of prerequisites:

brew install postgresql@14
brew install redis
brew install nodenv
brew install yarn
brew install imagemagick
brew install httpd
brew install libidn
brew install opensearch
brew install ffmpeg

Ruby

Because I already had mariadb installed as my MySQL-compatible database, I didn’t need ruby-on-mac to install the official mysql package as part of its web development tool installation, so I commented out brew 'mysql' from Brewfile-rom-webdev before I ran its installation command.

Once ruby-on-mac was installed, make sure we’re using an appropriate version of Ruby.

chruby 3.2.2

NodeJS

nodenv install 20.5.1

This might be able to go earlier in the process, but I wasn’t sure if ruby-on-mac installed anything that NodeJS might need.

Start and set up supporting services

brew services start opensearch
brew services start redis
brew services start postgresql@14

Create the mastodon database user.

psql postgres
CREATE USER mastodon WITH PASSWORD 'password' CREATEDB;
\q

Get Mastodon

cd ~/Sites
git clone https://github.com/mastodon/mastodon.git
cd mastodon
git checkout v4.2.0

Compile Mastodon

corepack enable
yarn set version classic
gem install bundler --no-document
bundle config deployment 'true'
bundle config without 'development test'
bundle install -j$(getconf _NPROCESSORS_ONLN)
yarn install --pure-lockfile

Configure Mastodon

DISABLE_DATABASE_ENVIRONMENT_CHECK=1 needed if rebuilding over an existing database.

NODE_OPTIONS=--openssl-legacy-provider is required to compile assets.

You may not be able to send a test email during the configuration process until some additional values are added to the .env.production file in the next section.

RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 NODE_OPTIONS=--openssl-legacy-provider bundle exec rake mastodon:setup

Configure .env.production file

You should now have an .env.production file that looks something like this, but showing the values you entered during the configuration phase:

LOCAL_DOMAIN=example.com
SINGLE_USER_MODE=false
SECRET_KEY_BASE=
OTP_SECRET=
VAPID_PRIVATE_KEY=
VAPID_PUBLIC_KEY=
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mastodon_production
DB_USER=mastodon
DB_PASS=
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
S3_ENABLED=true
S3_PROTOCOL=https
S3_BUCKET=
S3_REGION=us-east-1
S3_HOSTNAME=s3.us-east-1.amazonaws.com
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
S3_ALIAS_HOST=
SMTP_SERVER=
SMTP_PORT=465
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=never
SMTP_FROM_ADDRESS=

Now I needed to add a few additional environment variables to the file. Remember what I said earlier about not being able to send emails during the configuration phase? It turned out I needed to add the following lines to the .env.production file in order to connect to the SMTP server:

SMTP_TLS=false
SMTP_SSL=true
SMTP_CA_FILE=/opt/homebrew/etc/openssl@3/cert.pem

I already had my site running on my main domain and wanted to host Mastodon on a subdomain:

WEB_DOMAIN=social.example.com

I had issues with images and other assets not loading properly, so I had to have Rails serve them:

RAILS_SERVE_STATIC_FILES=true

I also enabled Elasticsearch (well, OpenSearch):

ES_ENABLED=true
ES_HOST=localhost
ES_PORT=9200

Create the Elasticsearch indices

I had to first comment out the progress.total = indices.sum { |index| importers[index].estimate! } line due to a progress bar error (see #18625).

RAILS_ENV=production bin/tootctl search deploy

Deploy the mastodon_supervisor.ini configuration file

I used an example file from checkmyworking.com. You may need to create the /opt/homebrew/etc/supervisor.d directory, but this file goes there; replace foo with your username and update any paths to point to where you cloned the mastodon directory earlier if needed.

Note that PGGSSENCMODE=disable is needed due to a segmentation fault in gem pg on MacOS.

Update httpd configuration files

Because I wanted to host my server in a subdomain, but didn’t want that subdomain to be part of the name of my instance, I had to forward some URIs from the main domain to the Mastodon subdomain; these lines were added in my VirtualHost section for my main domain:

<VirtualHost xxx.xxx.xxx.xxx:443>
  ServerName example.com
  ...

  <Location "/.well-known">
    ProxyPreserveHost On
    RequestHeader set X-Real-IP %{REMOTE_ADDR}s
    RequestHeader set X-Forwarded-Proto "https"
    ProxyPass http://127.0.0.1:3000/.well-known
    ProxyPassReverse http://127.0.0.1:3000/.well-known
  </Location>

  <Location "/api">
    ProxyPreserveHost On
    RequestHeader set X-Real-IP %{REMOTE_ADDR}s
    RequestHeader set X-Forwarded-Proto "https"
    ProxyPass http://127.0.0.1:3000/api
    ProxyPassReverse http://127.0.0.1:3000/api
  </Location>

  <Location "/oauth/token">
    ProxyPreserveHost On
    RequestHeader set X-Real-IP %{REMOTE_ADDR}s
    RequestHeader set X-Forwarded-Proto "https"
    ProxyPass http://127.0.0.1:3000/oauth/token
    ProxyPassReverse http://127.0.0.1:3000/oauth/token
  </Location>

  <Location "/oauth/authorize">
    RewriteEngine On
    RewriteRule .* https://social.glennfitzpatrick.com%{REQUEST_URI} [R=301,L]
    Header append Access-Control-Allow-Origin '*'
  </Location>

  ...
</VirtualHost>

Meanwhile, the social.example.com subdomain that would actually host the web interface, its configuration file is here. I commented out configuration details that were in the official nginx configuration file that I either couldn’t find an corresponding configuration command for in httpd, or they were configuration details that didn’t appear to be needed. Update the domain name, certificate file locations, and Mastodon repository location and deploy it to wherever you keep your httpd config files; my Homebrew installation has them in /opt/homebrew/etc/httpd/sites.

Database tweaks

I updated the /opt/homebrew/var/postgresql@14/postgresql.conf file to tune the database server to better suit my hardware using PGTune (I could probably increase the total RAM, but I figured I’d start with this setup for now and see how it runs):

# DB Version: 14
# OS Type: mac
# DB Type: web
# Total Memory (RAM): 4 GB
# CPUs num: 8
# Data Storage: ssd

max_connections = 200
shared_buffers = 1GB
effective_cache_size = 3GB
maintenance_work_mem = 256MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
work_mem = 1310kB
huge_pages = off
min_wal_size = 1GB
max_wal_size = 4GB
max_worker_processes = 8
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
max_parallel_maintenance_workers = 4

The PgHero tool in the Mastodon server also prompted me to make a change to enable statistics, so these lines were also added to the postgresql.conf file:

shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all

Then enable the statistics for the mastodon database user:

psql postgres
\c mastodon;
CREATE extension pg_stat_statements;
exit

Start Mastodon!

Whew, I think that’s about everything that I encountered while getting it to run properly. Now you should be able to start Mastodon directly in two Terminal windows…

RAILS_ENV=production PORT=3000 MAX_THREADS=10 WEB_CONCURRENCY=4 PGGSSENCMODE=disable bundle exec puma -C config/puma.rb
RAILS_ENV=production DB_POOL=25 MALLOC_ARENA_MAX=2 LD_PRELOAD=libjemalloc.so PGGSSENCMODE=disable bundle exec sidekiq -c 25

Or, as I prefer, with supervisor:

brew services start supervisor

Now you should be able to use Ivory or your Mastodon client of choice to connect to your example.com instance, and access it on the web at social.example.com! The only issue I can seem to find at the moment is that when I list another account on my same instance on my profile, if I try to access that other account in my client I get a “User not found” error but I can’t figure out if it’s something with the API URLs or something with the client or what, as it seems to think the instance should be social.example.com. But other than that, things seem to work well! Hope this helps!

Permalink

nOStalgia

Recently I’d been investigating some retro computing resources; there was a retro computing swapmeet nearby a month or so ago that I went to check out, and (while not retro-computing per se) an amateur radio hamfest I explored. I’d also been reading some books on the development of the personal computer (The Apple II Age: How the Computer Became Personal, iWoz, Shareware Heroes, and The Secret History of Mac Gaming). I’d also discovered Infinite Mac, but ultimately I fished my old iMac DV out of storage as well as an old Titanium PowerBook G4 I had once bought on eBay to see what I could do about updating and upgrading them to run old games that I used to enjoy playing ages ago.

The iMac was one that my family had bought around senior year of high school (it might have been a graduation present? I also got a digital video camera around that time because my sisters and friends and I would make videos of ourselves using our old analog video camera but now we could import and edit our films with iMovie), but I had bought this particular PowerBook model a few years ago because it was the most powerful Mac I could get used that was self-contained that could also boot directly into OS 9. Though the PowerBook was much faster than the iMac – 867 MHz G4 vs 400 MHz G3 – and had a more advanced video card than the iMac, I had more of a soft spot for the iMac being as it had belonged to me and it felt more enjoyable to use; the PowerBook’s battery no longer held a charge, the keyboard was all mushy compared to what I’ve grown used to, the only place for it on my desk meant its screen was further away than the iMac’s screen was in the same footprint, and the fan on the PowerBook would loudly spin up after a few minutes of use.

I had apparently already maxed out the PowerBook with as much RAM it could hold as well as an 240 GB SSD drive split between OS 9 and Mac OS X and had previously also bought an external SSD with both Firewire 800 and USB 3 connections so I could use it with both old and new computers, but I saw the iMac still could use some upgrades and ended up maxing out its RAM to 1 GB and popping in a 120 GB SSD and a new PRAM battery. Despite the iMac being fanless it sure made quite a racket with its spinning hard drive, so once I got it all upgraded I thought it was the perfect thing for my retro computing needs, despite the occasional “bzzt”s and “zzap”s of old capacitors not having power for years and one or two sudden unexplained shutdowns.

I had made notes while reading The Secret History of Mac Gaming of old games I had forgotten about or wanted to try, and just managed to go through my collection of MacAddict cover disc ISOs to save all the game installers as well as other odds-and-ends from its premier issue’s CD through 1 year after OS 9’s “funeral”, but no more than a day after I finished saving what I wanted for easy access I discovered the iMac no longer powers up. Alas. And just days after the original iMac’s 25th anniversary, too! I think my cat Freddie is a likely suspect, as he was Not Thrilled that the iMac had taken up residence in his napping spot on my desk and stared at it for several minutes the other day.

A cat looking annoyed at an iMac on a computer desk

Anyway, now I’ve got my broken, upgraded iMac just sitting around. I’ve already removed the SSD I just recently put in, but otherwise it’s ready for electronic recycling. I had bought a new PRAM battery for my PowerBook as well but turns out I bought the wrong voltage, so that computer will keep forgetting things like the current day and time until I’m able to source a new one. I’m still not thrilled about the fan in the PowerBook, though; I may keep a lookout for an old Power Mac G4 cube and a 15″ Apple Studio Display for my end goal of having a quiet, fanless Mac that runs OS 9 that doesn’t need a giant CRT monitor. Emulation would be an option for some of my needs if only everything that I wanted to do worked in an emulator, but a few of the games that I tried didn’t work properly or at all.

Just using OS 9 again for the first time in a few years made me wish for something like OS 9 but with modern technologies. Give me an operating system with the design of OS 9 and the multithreaded capabilities and other modern affordances of current MacOS. Something about the whole retro experience just felt more fun.

Permalink

Boring ways to treat yourself

After work today I wandered Costco for a bit. I had made an appointment to get my tires rotated, but I also had recently received a member rewards rebate certificate for my spending over the past year. Since it had to be spent on something in the warehouse (as opposed to through the website) I figured I’d spend my time waiting for the tire techs to do their thing by window shopping and seeing what I wanted to buy to treat myself. I did go in with a few thoughts of what I’d probably want to buy but some are more boring than others. Coming in at #1 on the boring scale:

A new thermostat!

I had replaced my apartment’s thermostat with a Nest not that long after I first moved in 12 or so years ago; the previous tenant had smoked inside and when I’d come back home from being away for just a day or two my unit smelled like stale air with a whiff of cigarettes, so I bought the Nest for its automatic fan feature where it would run the HVAC fan for 10 minutes of every hour if it hadn’t run for at least that long already. I later added a remote sensor to it and put that in my bedroom, but I haven’t been too thrilled with the sensor setup. If I could say “at 11 PM use the bedroom sensor to determine the temperature, and at 5 PM switch to the thermostat, but if it’s a weekend then use the thermostat starting at 9 AM”, it’d be perfect, but I can’t. I can only tell it “use the thermostat in the living room to determine the temperature between 4 PM and 9 PM each day, and the bedroom sensor at all other times”. It’s not so bad when I’m working from home at my desk in my bedroom, or if I’m in the living room from 5 to 9 PM, but from 9 PM until I go to bed I start having to fight with the thermostat to get a comfortable temperature in my living room and having to strain to hear the TV over the HVAC fan running frequently is starting to get a bit old. You wouldn’t think there’d be much variation in such a small apartment but it’s amazing how much of a temperature differential there is between my hallway where the thermostat is and my bedroom! So, I considered changing the thermostat to an Ecobee. Apparently the Costco package comes with two sensors that detect motion as well as temperature, and it has a “follow-me” setting where it uses the room you’re in to determine which sensor to use for temperature sensing. Imagine that! I was eager to buy it and found it in stock at the store, but after walking a few laps of the warehouse and texting with the ladyfriend I decided to hold off on it for now at least, as it’s not something “for me” and I don’t own my unit, and I was wanting to find something that felt more like a treat for myself. Which brings me to #2 on the boring scale:

A new trashcan!

After doing some renovations to my apartment (new paint job, fixing some damaged walls, taking care of other odds and ends that have bugged me over the years), I thought it’d be nice to have a more aesthetically-pleasing trash can and had been eying the SimpleHuman dual trash-and-recycling bin. Unfortunately while I saw it online I didn’t see it in my warehouse, so no luck buying that today.

A few other things that I had considered were a new AppleTV for its Thread capabilities as my HomePod mini sometimes occasionally has issues with controlling my Thread-enabled outlets and I hoped that something with a hard-wired internet connection would be more responsive to Siri requests, or if not that then perhaps a pair of new AirPods Pro; though I’m happy with my existing pair, as I drove to the store I was listening to Accidental Tech Podcast, and I heard Casey say that’s how he spent his free money. Unfortunately neither was in stock at my warehouse, so oh well.

Since I’m in no rush I’ll just have to wait for another day to see if something catches my eye, but at least my tires are rotated and I got some grocery shopping done. Even though using my rewards to pay for groceries would be the most practical thing to do, I think doing that’s even more boring than a new thermostat!

Permalink

Venti vent

Today was A Day. Didn’t sleep well last night at all, discovered mid-commute that my transit card had become inactive for some reason and so I had to buy a new one (it may have been reported as missing/lost/stolen by someone who’s not me??), my mask broke, my coffee order was screwed up (or at the very least tasted horrible, as if they had dumped a ton of artificial sweetener in), and realized I forgot my headphones for plugging into my work computer for joining conference calls (I figured that I could just dial in to the meeting from the phone and then was incredibly confused why I was hearing myself through my laptop and hung up… turns out I could have just muted my computer, but my brain wasn’t firing on all cylinders at this point). Nothing catastrophic and all just little things that are easy to laugh off after the fact, but this morning was just one. thing. after. another. and having a bad coffee order after all that was just the final straw (or should that be “final wooden swizzle-stick”? “final plastic stopper that plugs up the coffee lid”?).

After I texted my ladyfriend to vent (did I tell you I have a new ladyfriend?? I’ll have to tell you more about us some other time), she offered to place a new Starbucks order for me for lunch to try to help make things better. When I went to pick it up I discovered she had added a cake pop to the order as a surprise for me because she figured I needed a pick-me-up. 🥰

After work I had my usual therapist visit. It’s quite nice having seen my therapist for ages since she knows quite a lot about me and what’s gone on in my life over the years, so as I was telling her about some things that I had been thinking recently she cautioned me against looking back with rose-colored glasses. I also mentioned to her about some old blog posts that I had discovered by chance when I was tweaking my site for returning to blogging and she recommended looking back on old entries occasionally to remind myself of how some things Used To Be. That’s a great idea, and ensuring I make myself a usable log of recent developments in my life is another good reason to try to update here regularly.

Anyway, I’ve zonked myself with ZzzQuil to hopefully help myself get a solid night’s rest so time to hit the Submit button before I fade away…