Mastodon API - reference social.
  • Ada 58.6%
  • Python 38.4%
  • JavaScript 2.3%
  • CSS 0.6%
Find a file
LowEel ee20f7ce22
All checks were successful
continuous-integration/drone/push Build is passing
Fix broken attachment preview via FileReader instead of object URLs
attachmentPicker() built the preview <img> from URL.createObjectURL(file)
synchronously inside the paste/change handler. A File's bytes aren't
always readable yet at that exact instant (most reliably reproduced via
clipboard paste, but also seen from the file picker), so the browser
would show a broken-image icon even though the same File uploads fine
moments later at post time -- confirmed live by the user, upload always
succeeded, only the preview thumbnail was affected.

Switched to FileReader.readAsDataURL(), an explicitly asynchronous read
that only assigns entry.preview once the data URI is actually ready,
with render() called again on completion. render() now decides
img-vs-placeholder from entry.preview instead of file.type, so the row
shows the MEDIA badge immediately and swaps to the real thumbnail when
it resolves, never an empty <img src="">. Dropped the now-unnecessary
URL.revokeObjectURL calls in removeEntry()/reset() along with it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 16:02:15 +02:00
alire Add keystoreada dependency for encrypted-at-rest secret storage 2026-08-17 23:51:23 +02:00
doc Define resettling as contact follow refresh 2026-08-02 15:45:19 +02:00
docs
etc Simplify runtime Docker builders 2026-08-03 18:34:58 +02:00
smoketest Fix dropped .webm attachments via one canonical mimetype classifier 2026-08-19 14:01:09 +02:00
spark Fix dropped .webm attachments via one canonical mimetype classifier 2026-08-19 14:01:09 +02:00
src Fix dropped .webm attachments via one canonical mimetype classifier 2026-08-19 14:01:09 +02:00
static Fix broken attachment preview via FileReader instead of object URLs 2026-08-19 16:02:15 +02:00
tests
tools Support AMD64 deployment only 2026-08-08 12:46:31 +02:00
.dockerignore
.drone.yml Publish the documented latest image 2026-08-08 15:38:34 +02:00
.gitignore Fix outbound federation signing and key management 2026-08-08 09:22:56 +02:00
alire.toml Add keystoreada dependency for encrypted-at-rest secret storage 2026-08-17 23:51:23 +02:00
DOCKER.md Support AMD64 deployment only 2026-08-08 12:46:31 +02:00
Dockerfile.amd64 Roll instance health windows monthly 2026-08-08 15:36:02 +02:00
Dockerfile.builder.amd64 Prebuild native Ada and SPARK toolchains 2026-08-08 11:00:42 +02:00
Dockerfile.builder.spark Prebuild native Ada and SPARK toolchains 2026-08-08 11:00:42 +02:00
Dockerfile.spark Publish builder images on Docker Hub 2026-08-08 11:45:41 +02:00
INSTALL.md
LICENSE.md
README.md Make resettling key-driven and promote facehugger 2026-08-08 15:10:07 +02:00
spark_helpers.gpr Add strict RFC2616 request validation and reject WebSocket upgrades 2026-08-18 17:30:17 +02:00
xenomorph.gpr Release xenomorph egg 2026-08-02 17:13:01 +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/facehugger.

Xenomorph is now usable for real small-instance testing and everyday dogfooding, but it is still hardening and should not yet be treated as a conservative production recommendation.

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.

Contact

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

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