Mastodon · reimplemented in Rust

eunha

A Rust re-implementation of Mastodon, aiming for 100% database schema compatibility — so it can be a drop-in replacement on top of the Mastodon database you already have.

Tracks Mastodon 4.7.0 Version 0.2.0+mastodon.4.7.0 Storage PostgreSQL 17+

What it is

Eunha speaks Mastodon's client API and federates over ActivityPub, and it stores everything in Mastodon's own schema. The tables are upstream's tables, down to constraint names and index columns; eunha's own tables live apart, in a eunha schema beside public.

That is the whole design constraint. An instance can move to eunha without exporting or reshaping anything, and can move back. Existing clients keep working, because the API they read is the same API.

It is not a goal to mimic Mastodon's entire feature set or its implementation. Where eunha behaves differently, the difference is written down rather than discovered.

Compatibility, checked rather than claimed

A compatibility claim decays the moment nobody tests it. Four harnesses keep this one honest, and they run in CI:

Schema check

Reads the live database back out of Postgres and diffs it against a reference built by Mastodon's own ActiveRecord from upstream's schema.rb — types, nullability, defaults, index columns, every constraint by name, sequences, views.

mise run schema:check

Entity parity

A client reads fields by name, so a missing one breaks it. Every REST entity Mastodon's serializers emit is recorded, and real responses from a running eunha are compared field by field.

mise run entities:build

Differential testing

The same request goes to eunha and to a real Mastodon in Docker, and the answers are compared: 31 reads, nine writes, and the interaction verbs. Nothing in it encodes what the answer should be.

mise run differential

Federation

Twenty-four checks in both directions against a live Mastodon: resolve, follow, deliver, favourite, boost, delete. Two servers sharing nothing but the specification.

mise run federation

Which Mastodon are we?

One file answers that. mastodon.toml names the release whose schema and API a build implements, the version repeats it as build metadata, and the build fails if the two disagree:

v0.2.0+mastodon.4.7.0
│      └── the Mastodon release this build implements
└── eunha's own version, which moves independently

Adopting a newer release is a procedure rather than a guess: mastodon:plan lists the Rails migrations upstream added since, one eunha migration reproduces them, and the reference schema is rebuilt so the diff is the delta being adopted.

mise run mastodon:status              # is there a newer release?
mise run mastodon:plan --to v4.8.0    # what would adopting it involve?
mise run schema:check                 # does this database match?

Divergences are data

A difference from Mastodon is either a bug or a decision. The decisions live in divergences.toml — what Mastodon does, what eunha does instead, why, and which test would fail if that stopped being true.

They are recorded as data rather than prose because prose is not checked and so stops being true. The test suite reads that file: an entry whose evidence has gone missing fails, and every entry carries the Mastodon release it was last judged against — so adopting a newer release fails the suite until each divergence has been re-examined.

Federation, and where it scales badly

Federation is built on feder, extended where necessary. Outgoing requests are signed with the draft-cavage signatures the network still runs on, and double-knocked with RFC 9421 when a peer refuses; inbound requests are verified either way.

PROTOCOL.md is a design record of the places ActivityPub scales badly — the dereference storm a boost sets off, the absence of backfill, identity that cannot outlive a hostname — and what eunha proposes for them. It says plainly which parts exist today and which do not.

Elsewhere