My Mastodon setup
Nov 13, 2022
It seems like Twitter may not be around much longer. Even if does stay around, it’s not likely to be a place I want to be anymore.
Like many folks, I decided to dip into the Fediverse. Using Mastodon seemed like a good first step. I set up an account on mastodon.social but quickly realized it was overwhelmed. There were other servers to join but I wanted to run my own.
My goals:
- have
danp@danp.net
be my handle - keep danp.net on GitHub pages
- run Mastodon elsewhere (not on danp.net, since it’s on GitHub Pages)
The main rub with this is WebFinger which is a big part of Fediverse discovery.
When a handle like user@example.com
is used, a WebFinger request is made to a URL like:
https://example.com/.well-known/webfinger?resource=acct:user@example.com
The response is meant to look something like:
{
"aliases": [
"https://example.com/@user",
"https://example.com/users/user"
],
"links": [
{
"href": "https://example.com/@user",
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html"
},
{
"href": "https://example.com/users/user",
"rel": "self",
"type": "application/activity+json"
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://example.com/authorize_interaction?uri={uri}"
}
],
"subject": "acct:user@example.com"
}
Usually this would change based on the resource
query parameter.
If I wanted a setup just for my lone user, could I return a static response?
Setting up Mastodon
First, I got Mastodon set up. This is not an exhaustive guide to setting up Mastodon, mostly just bits from my notes along the way.
I saw the Installing from source documentation page but it called for installing a bunch of stuff.
After doing some searching for mastodon docker
I found the project’s
docker-compose
file so I thought I’d start with that until it didn’t work.
That let me mostly skip to Generating a configuration with:
touch .env.production # so docker stuff runs
docker-compose run -i web bash # builds a bunch
RAILS_ENV=production bundle exec rake mastodon:setup
Since I wanted to have my handle be danp@danp.net
but my instance run at mastodon.danp.net
I needed to do the WEB_DOMAIN wrangling mentioned here.
The setup process didn’t give a way to do that so I nuked the database and redis data after running the setup process and fixed up the generated config with:
LOCAL_DOMAIN=danp.net
WEB_DOMAIN=mastodon.danp.net
Then ran rake db:setup
like the setup process does.
With that, I was able to start everything up:
docker-compose up -d
Next, to get traffic to the instance, I added this to my host’s Caddyfile:
mastodon.danp.net {
handle /api/v1/streaming* {
reverse_proxy localhost:4000
}
reverse_proxy localhost:3000
}
The special bit for the streaming endpoint came later, after I realized the websockets requests weren’t working out. Once I added it, notifications in the web UI happened instantly!
Then, I added my lone user:
RAILS_ENV=production bin/tootctl accounts create danp \
--email danp@danp.net --confirmed --role Owner
Everything seemed to be working!
But what about WebFinger?
Next, I knew I had to figure out WebFinger requests to danp.net.
I curl’d https://mastodon.danp.net/.well-known/webfinger?resource=acct:danp@danp.net
and got:
{
"aliases": [
"https://mastodon.danp.net/@danp",
"https://mastodon.danp.net/users/danp"
],
"links": [
{
"href": "https://mastodon.danp.net/@danp",
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html"
},
{
"href": "https://mastodon.danp.net/users/danp",
"rel": "self",
"type": "application/activity+json"
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://mastodon.danp.net/authorize_interaction?uri={uri}"
}
],
"subject": "acct:danp@danp.net"
}
So I just dumped that to a file on my static GitHub Pages repo.
This makes any request to https://danp.net/.well-known/webfinger
(regardless of query param) return that static response.
Should be fine, right? Everything seemed to be working!
The octodon.social mystery
I was able to find and follow many folks. But then I noticed none of my searches for folks on octodon.social worked.
This was where having the source came in super handy.
It took a bit of tracing from the search controller, to the search service, to the account search service, to the resolve account service, to the ActivityPub fetch remote account service and its base class, but I was able to boil it down to:
actor_url = "https://octodon.social/users/commaok"
x = ActivityPub::FetchRemoteAccountService.new
req = x.build_request(actor_url, Account.representative)
req.perform {|r| p r.body_with_limit }
Which let me see that the Public key not found for key https://mastodon.danp.net/actor#main-key
error from around
here was being returned.
This indicates octodon.social is running in “secure mode” which requires signatures which can be verified.
In the code above, Account.representative
is an instance-level account.
When some requests are made on behalf of the instance,
those requests are signed
using a key from that account.
The key URI mentioned in the error above is from that account.
When octodon.social saw this key URI, it:
- requested the data at
https://mastodon.danp.net/actor#main-key
- saw
preferredUsername: danp.net
in the body - made a WebFinger query for
danp.net@mastodon.danp.net
- got a response with
subject: acct:danp.net@danp.net
(nomastodon
in the hostname) - made another WebFinger query for
danp.net@danp.net
This hit my static WebFinger response and broke.
I fixed this with a little patch to the Account model:
diff --git a/app/models/account.rb b/app/models/account.rb
index 3647b8225..72a89dee9 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -180,7 +180,11 @@ class Account < ApplicationRecord
end
def local_username_and_domain
- "#{username}@#{Rails.configuration.x.local_domain}"
+ domain = Rails.configuration.x.local_domain
+ if instance_actor? && domain != Rails.configuration.x.web_domain
+ domain = Rails.configuration.x.web_domain
+ end
+ "#{username}@#{domain}"
end
def local_followers_count
Now the WebFinger query for danp.net@mastodon.danp.net
in step 3 above returns
a response with subject: acct:danp.net@mastodon.danp.net
and everything works out.
I wish WebFinger requests could be done in a way that didn’t require this. For example, if it requested paths like this:
/.well-known/webfinger/danp.net/danp
/.well-known/webfinger/danp.net/danp.net
Then I could place two static files and return the right thing.
Since it uses a query param this isn’t possible without some HTTP server intelligence.
Conclusion
It seems like I’ve achieved my goals.
My handle is danp@danp.net
,
danp.net is still on GitHub Pages,
and I have my own instance.
I learned quite a bit about WebFinger and the Mastodon source along the way, too.
If I were doing it again, I might go for s.danp.net (“s” for “social”) or similar instead of trying to use just danp.net.
I also might not have “mastodon” in any hostname. I realize now that Mastodon is just one way to access the Fediverse.
Still, it’s nice to have a setup that could very well outlast Twitter.