Mastodon API - reference social.
  • Ada 93.5%
  • Python 2.9%
  • JavaScript 1.8%
  • C 1%
  • CSS 0.5%
  • Other 0.2%
Find a file
LowEel 71236e2c95
All checks were successful
continuous-integration/drone/push Build is passing
Store the avatar and header chosen on the profile page
The profile settings page sends a chosen avatar and header in its two
file fields, avatar_file and header_file, and nothing on the server
read them: the page answered "Settings updated" and the old picture
stayed.  Only a Mastodon client could change it, through
update_credentials.

Save_Settings now stores them through the same Store_Profile_Image,
with the same checks -- an image name, at most 1 MiB -- and puts the
URL into the profile, which publishes the actor update as any other
change of it does.  A refused picture is a 400 that says why, and the
page shows that sentence instead of "Save failed (400)".

Smoke: a PNG avatar and header uploaded from the page become the
actor's icon and image, and both serve the bytes that were sent.
2026-09-30 08:08:59 +02:00
alire Let a job wait again: polls and scheduled posts could not be created 2026-09-20 11:03:08 +02:00
corpus Record how snac2, Honk and GoToSocial federate into corpus/; peer-stub quirks 2026-09-15 14:13:25 +02:00
doc One folder, one thread, and delta a function 2026-09-23 22:18:32 +02:00
docs Initial commit 2026-08-19 21:58:25 +02:00
etc Give a failing delivery days instead of hours 2026-09-16 15:45:20 +02:00
scripts refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
smoketest Store the avatar and header chosen on the profile page 2026-09-30 08:08:59 +02:00
spark Run one line of an imported account list as a spool job 2026-09-28 08:19:13 +02:00
src Store the avatar and header chosen on the profile page 2026-09-30 08:08:59 +02:00
static Store the avatar and header chosen on the profile page 2026-09-30 08:08:59 +02:00
tests Run one line of an imported account list as a spool job 2026-09-28 08:19:13 +02:00
tools Retire spool migration entirely; activation is self-healing only 2026-09-13 18:10:01 +02:00
.dockerignore Activate spool v2 and materialized local delivery 2026-09-12 15:04:06 +02:00
.drone.yml Build the spool layout first, and never die on it 2026-09-16 16:19:49 +02:00
.gitignore Add validated spool v2 envelopes and local delivery 2026-09-03 01:04:42 +02:00
alire.toml Replace XMLAda with a native libxml2 SAX bridge for feed parsing 2026-09-13 21:30:04 +02:00
application_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
DOCKER.md Say how to run the container, not how to build it 2026-09-23 14:34:54 +02:00
Dockerfile.amd64 Render a post's Markdown where the reader is, with GitHub's renderer 2026-09-24 16:07:51 +02:00
Dockerfile.arm64 Render a post's Markdown where the reader is, with GitHub's renderer 2026-09-24 16:07:51 +02:00
Dockerfile.builder.amd64 Replace XMLAda with a native libxml2 SAX bridge for feed parsing 2026-09-13 21:30:04 +02:00
Dockerfile.builder.arm64 Replace XMLAda with a native libxml2 SAX bridge for feed parsing 2026-09-13 21:30:04 +02:00
Dockerfile.builder.spark Initial commit 2026-08-19 21:58:25 +02:00
Dockerfile.spark Activate spool v2 and materialized local delivery 2026-09-12 15:04:06 +02:00
email_delivery_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
fs_atomic_replace_proof.gpr Activate spool v2 and materialized local delivery 2026-09-12 15:04:06 +02:00
fs_atomic_replace_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
fs_nfs_rename_tests.gpr Handle unsupported no-replace rename on single-writer NFS and diagnose spool publication 2026-09-12 15:30:39 +02:00
fs_probe_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
INSTALL.md Say how to run the container, not how to build it 2026-09-23 14:34:54 +02:00
instance_reputation_tests.gpr Rebuild reputation from the votes already cast 2026-09-22 12:42:51 +02:00
instance_sweep_tests.gpr Read both instance lists correctly, and say so when a sweep breaks 2026-09-20 23:10:03 +02:00
journal.md Store passwords with scrypt, and convert the old ones on sign-in 2026-09-17 23:12:23 +02:00
LICENSE.md Update LICENSE.md 2026-09-13 16:53:05 +02:00
local_delivery_state_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
local_timeline_migration_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
logs_loweel_xenomorph_246_1_2.log Let a job wait again: polls and scheduled posts could not be created 2026-09-20 11:03:08 +02:00
pending_status_update_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
purge_helpers_tests.gpr Activate spool v2 and materialized local delivery 2026-09-12 15:04:06 +02:00
queue_publisher_recovery_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
README.md Say how to run the container, not how to build it 2026-09-23 14:34:54 +02:00
rsa_verification_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
security_helpers_tests.gpr Prove the lines of an uploaded account list 2026-09-28 08:08:39 +02:00
smoke_queue_publish_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
spark_helpers.gpr Prove the lines of an uploaded account list 2026-09-28 08:08:39 +02:00
spool.md Let a job wait again: polls and scheduled posts could not be created 2026-09-20 11:03:08 +02:00
spool_v2_activation_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
spool_v2_admission_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
spool_v2_phase_five_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
spool_v2_phase_four_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
spool_v2_phase_three_tests.gpr Store passwords with scrypt, and convert the old ones on sign-in 2026-09-17 23:12:23 +02:00
spool_v2_phase_two_tests.gpr refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
spool_v2_policy_tests.gpr Add SPARK spool v2 policy model 2026-09-02 21:59:52 +02:00
status_projection_tests.gpr Replace XMLAda with a native libxml2 SAX bridge for feed parsing 2026-09-13 21:30:04 +02:00
TODO-resume.md refactor native boundaries and add four-peer federation gate 2026-09-13 13:54:06 +02:00
xenomorph.gpr Render a post's Markdown where the reader is, with GitHub's renderer 2026-09-24 16:07:51 +02:00

Xenomorph

Xenomorph is a usable ActivityPub server written in Ada.

At this stage it starts as a source-guided port of snac2, especially for the visible interfaces and filesystem behavior. The long-term goal is not to pretend that Ada is C, but to carry the same small-server idea into a codebase with stronger type safety, better modularity, and a maintenance model that fits exposed Internet software.

The short version is: Daddy is Snac.

Why

snac2 is fascinating because it proves that a single-person project can still implement a practical ActivityPub server without turning into a large platform. ActivityPub federation is difficult, and the protocol often feels much more complicated than it needs to be. snac2 approaches that problem with discipline: plain storage, small moving parts, and a bias toward direct, understandable behavior.

Xenomorph exists because that approach deserves an Ada implementation.

This is not a claim that snac2 has specific security problems. It is a different engineering bet: C can be used extremely well by expert programmers, but Internet-facing C services also live close to classes of memory and data handling bugs that Ada is designed to make harder. Xenomorph tries to keep the practical minimalism of snac2 while gaining Ada type and data safety.

Also, it is fun.

Goals

  • Keep the visible behavior close to snac2 while the port is young.
  • Use snac2 as the design map: simple filesystem storage, simple processes, clear HTTP behavior, and as little machinery as possible.
  • Avoid a database. The filesystem is the storage model.
  • Keep configuration transparent and inspectable.
  • Make the project suitable for small Docker deployments.
  • Fit homelab-scale servers, including very small machines and NFS-backed storage.
  • Stay aligned with snac2 development where that makes sense.
  • Eventually let Xenomorph grow its own features once the port has legs.

Current Status

Current version: Xenomorph/warrior.

Xenomorph runs live instances and is the software behind the author's own.

The project currently has working ActivityPub federation for the implemented S2S flows, filesystem storage, Web UI work, WebFinger and actor output, login/admin scaffolding, and the subset of the Mastodon client API implemented by snac2. Comparative smoke tests exercise Xenomorph and a real snac2 server side by side.

If this sounds interesting, follow the project and expect the feature list and release notes to become useful as the implementation matures.

Implemented So Far

The implemented list is intentionally conservative. It should only describe what exists in the code today.

  • Ada project built with Alire.
  • Filesystem-based server and user storage.
  • User creation with local key material.
  • WebFinger responses for local users.
  • ActivityPub actor endpoint with local profile data.
  • Web login and a Semantic UI client for home, federated and hashtag timelines, notifications, conversations, profiles, people, follow requests, bookmarks, pinned posts, followed hashtags, lists, and search.
  • The Web UI uses locally vendored Atkinson Hyperlegible Mono by default.
  • Web composer and status controls for replies, media, content warnings, polls, scheduling, visibility, language, likes, reposts, bookmarks, pins, edits, deletion, and emoji reactions.
  • Remote media attachments and remote custom emoji caching for inbound S2S posts.
  • Settings UI for Mastodon-compatible profile fields and advanced local settings, including CSV contact-list import and asynchronous resettling.
  • Inbox and shared-inbox request handling.
  • Input, shared input, output, and local user queues.
  • Signed ActivityPub delivery for S2S output.
  • HTTP signature checking for queued inbound S2S messages.
  • Basic S2S handling for Follow, Accept Follow, Undo Follow, Create, Update, Delete, Like, Announce, EmojiReact, Ping/Pong, and Move behavior.
  • Filesystem indexes for timeline objects, replies, likes, announces, emoji reactions, public objects, collected inboxes, blocked instances, failed instances, and public hashtags.
  • snac-style handling for content rejection filters, blocked hashtags, followed hashtag distribution, dropped DMs from unknown actors, and blocked/muted actors.
  • The Mastodon client API subset implemented by snac2, including its OAuth authorization flow.
  • A Debian 12 multi-stage container image and a single-replica Docker Swarm deployment with separate data and configuration mounts.
  • Smoke tests that exercise the implemented HTTP and storage behavior and keep selected responses aligned with snac2.

What It Is Not Yet

  • It is not a complete ActivityPub server.
  • It is not yet a conservative production recommendation for unattended public instances.
  • It does not implement Mastodon's entire API, only the subset implemented by snac2.

Development installation is documented in INSTALL.md. Container and Docker Swarm deployment is documented in DOCKER.md. Container images use the loweel/xenomorph Docker Hub repository as their publication target.

Design Shape

Xenomorph is meant to stay small.

The intended deployment model is a tiny ActivityPub instance for one person or a few users, especially in a homelab. The concrete target environment is Docker on small machines, such as ODROID-class nodes, with storage that can live on NFS. Ada plus Docker should make the executable and runtime environment highly reproducible, while filesystem storage keeps data visible and easy to reason about.

Relationship With snac2

snac2 by grunfink is the reference, inspiration, and credit source for this project. Xenomorph initially follows it as a port for the externally visible interfaces and for many internal storage and queue decisions.

The goal is not to redesign the product while porting it. The goal is to understand what snac2 does, reproduce the behavior in Ada, and then only add new direction once there is a solid foundation.

One intentional divergence is the browser admin login flow. snac2 exposes the admin page through an HTTP Basic authentication challenge; Xenomorph redirects unauthenticated /user/admin browser requests to its OAuth-style Web UI login page and then uses the xenomorph_ui session cookie for the admin shell and Web UI actions. The ActivityPub and Mastodon-compatible API compatibility tests should not assume snac2-style Basic-auth behavior for Xenomorph's Web UI.

License

Xenomorph is intended to be licensed under the European Union Public Licence, using the current version or later: EUPL-1.2-or-later.

Acknowledgements

  • snac2 and grunfink, for the design path and the practical approach this project follows.
  • Braille Institute, for the Atkinson Hyperlegible font family used by the Web UI.
  • OpenAI Codex, for refreshing my memory about Ada. The last time I seriously programmed in Ada, it was still called Ada 95 and ran on SGI machines.
  • OpenMoji, Twemoji, and Noto Emoji, for the bundled custom-emoji packs (see static/xenomorph/emoji-packs/README.md for per-pack licenses and attribution).

Contact

This is a personal project, and feedback is welcome through the channels below.

  • Email: uriel.fanelli@keinpfusch.net
  • Matrix: @uriel:chat.keinpfusch.net