Following on from this post, for anyone who's interested in getting ActivityPub integrated on their static site, this is what I've needed to setup/build (some of this may indeed be redundant, but it 'evolved' over time, so easier just to leave alone than fix).

Some not-hugely-relevant background: I have a custom-built site generator, written in Python, pushing files via git to a Gitea instance and pulled via a cronjob to a dir where they're served by Apache2 (with Cloudflare in front). Everything is static, apart from (now) the inbox required for ActivityPub to work.

In terms of static ActivityPub content, there are a bunch of JSON files required (I'll probably butcher the descriptions of these, but whatever ¯\_(ツ)_/¯ ):

  • actor - effectively your ActivityPub profile information, but importantly this also has your public key.
  • followers - this is an OrderedCollection (an ActivityStreams term) of your followers. For some reason (I forget why now) I'm just pointing this to a separate JSON file: followers-list with the actual followers.
  • outbox - this is the actual content you post in another OrderedCollection.

Along with these, I've got another JSON "profile"...

[email protected]

...which is used for webfinger (more on that shortly).

My rewrite rules for the first set of files look like this:

RewriteRule ^actor$ /@me/actor [R=301,L]
RewriteRule ^@me$ /@me/actor [R=301,L]
RewriteRule ^@me/$ /@me/actor [R=301,L]
RewriteRule ^@jasonrbriggs$ /@me/actor [R=301,L]
RewriteRule ^@jasonrbriggs/$ /@me/actor [R=301,L]
RewriteRule ^followers$ /@me/followers

# force content type
RewriteRule @me/actor - [env=headerjs:1]
Header set Content-type application/activity+json env=headerjs
RewriteRule @me/outbox - [env=headerjs:1]
Header set Content-type application/activity+json env=headerjs

For the aforementioned webfinger, there's one more rewrite rule:

RewriteCond %{QUERY_STRING} (?:^|&)(resource=acct:[email protected]$) [NC]
RewriteRule ^\.well-known/webfinger /profile/[email protected] [L]

That takes care of the discovery aspects of the process (the self link in the webfinger profile points to the actor).

I do have a couple more rewrite rules for the @me directory which redirect browser requests (basically for text/html) to my local archive of "micro posts":

RewriteCond %{HTTP:Accept} text/html [NC]
RewriteRule ^@me$ /micro/ [R=301,L]
RewriteCond %{HTTP:Accept} text/html [NC]
RewriteRule ^@me/$ /micro/ [R=301,L]

(Why "@me"? Because I was originally planning @[email protected] as my ActivityPub address, before changing my mind and going with @[email protected], and I never got around to fixing).

As mentioned, the only part of my site which is (unfortunately) not static is the ActivityPub inbox, because you need to be able to accept POST follow requests and other notification activities from the fediverse. For the inbox, I've built a very simple Flask/WSGI app to do the very minimum (just file-based, no database, hopefully as minimal impact as possible).

I've also built a local (ie. not web-based) command-line Python app which can be used to send an Accept activity to a follow request (it should also add the follower to the follower's list, but I haven't gotten around to that yet); additionally, it retrieves all new posted content from the outbox and delivers that content to each follower's inbox. I'm not posting the code to github - at least not yet - because it's a bit of a mess. If anyone's enthusiastic to use unsupported Python code for this, feel free to get in contact.

The final piece of the puzzle is a separate Mastodon account (I've had one on for a while) where you send yourself a follow request, and then accept it - this not only tests that follow/accept works, but then you can see that your posts are correctly appearing at least somewhere in the fediverse.

So the nice part of all this, is that a micro post on my site is also added to the outbox, and shortly after it now appears on Mastodon (viewable here for example).