Skip to content

dimkr/tootik

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

          ..    .        ..                                        ..
 ...            .       ....                     ...
 ...   .     .   .   .   ... ..      .  .     .  . ...                 ...
 ..    .  .    ..   .         .    .. .       .. ..     .                   .
 .,     .               . . . ..             .  ..                  .        .
 .                .     . .. .. .... .       .  .  .               ..  ..
            .              ..   ...  .       .    .   .   .        ..  .   .
 .   .        .   ..        .    ..             ...' .. .          .   .    . .
 . .              . .    .  __     .     .__  _ __ ,; .'. .  .     ....
    . .          .         / /____  ___  /./_(_) /__  .'  ..         . .  . .
  ..      ... .    .  .   /.__/ _ \/ _ \/ __/./  '_/.   .       .. .   .    .
  .'   ...  .             \__/\___/\___/\__/_/_/\_\              .  .  . .
      .         .    .    . .    .    ...     ... .           .      ..
 ..  .. .   . .... ..  .  .         ..   .  .     .          .  .  ... ....' .
   ...  .      .   .  .. .  ... ...      . ..   ..         .,..    .....
 .   ..   ......             . .''.  .  ..          .         . .  ...
 ' .      .. ..  ..     . . ... ......::.   ..       .,.       .  .. ....    ..
 . ....  . .....     .  .. .  . ... . .,'.   .        ..          ,..  ..
 . .    .  .  . ..   .  .   .. .  .     ..     ..  .  . .       . . .        .'
   .  ....   '...                ...    . .  ..  .     ...     . '.   '     ...

# localhost.localdomain:8443

Welcome, fedinaut! localhost.localdomain:8443 is an instance of tootik, a federated nanoblogging service.

────

πŸ“» My radio
⚑️ Followed users
πŸ“‘ This planet
✨ FOMO from outer space
πŸ”­ Find user
πŸ”Ž Search posts
πŸ”₯ Hashtags
πŸ“Š Statistics
βš™οΈ Settings
πŸ”” New post
πŸ“£ New public post
πŸ›Ÿ Help

Latest release Build status Go Reference

Overview

tootik is a federated nanoblogging service for the small internet. With tootik, you can interact with your friends, including those on Mastodon, Lemmy and other ActivityPub-compatible servers, from the comfort of a minimalistic, text-based interface in the small internet:

                         Gemini           ActivityPub (HTTPS)
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β†’ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” ⇄ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  Bob's Gemini client  β”œβ”€β”¬β”€β”€ tootik instance β”œβ”€β”¬β”€β”€ Another tootik instance β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 β”‚2024-01-01 alice       β”‚ β”‚ β”‚$ ./tootik ...   β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚> Hi @bob and @carol!  β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”œβ”€β”€ Something else β”‚
 β”‚...                    β”‚ β”‚                     β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚                     β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         └── Mastodon instance β”œβ”€β”
               β”‚ Alice's Gemini client β”‚           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
               β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
               β”‚2024-01-01 bob         β”‚              β”‚  Carol's web browser  β”‚
               β”‚> Hi @alice!           β”‚              β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
               β”‚...                    β”‚              │╔═╗ alice              β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚β•šβ•β•             17h agoβ”‚
                                                      β”‚Hi @bob and @carol!    β”‚
                                                      β”‚                       β”‚
                                                      β”‚  ╔═╗ bob              β”‚
                                                      β”‚  β•šβ•β•           16h agoβ”‚
                                                      β”‚  Hi @alice!           β”‚
                                                      β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                                                      β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”β”β”β”β”β”β”β”β”“β”‚
                                                      β”‚β”‚ Hola       │┃Publish┃│
                                                      β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”—β”β”β”β”β”β”β”β”›β”‚
                                                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

tootik's goal is to make the fediverse lighter, more private and more accessible:

  • Its frontend supports Gemini, Gopher, Finger and Guppy: there's a wide variety of clients to choose from and an old device incapable of running a modern web browser is good enough.
  • It converts rich content into plain text and links, reducing bandwidth requirements and making content more suitable for screen readers.
  • It puts the users in control of the content they see, there's no tracking or analytics and the post sorting algorithm imposes the slower pace of the small internet.
  • It's a single, easy-to-deploy executable that handles both the federation and frontend aspects, making it easy to set up your own instance instead of joining an existing one.
  • It stores data like registered users and posts in a single file, a sqlite database that is easy to backup and restore.
  • It should be lightweight and efficient enough to host a single-user instance and even a small community on one, cheap server.
  • It implements only a subset of ActivityPub, enough for its feature set but not more, to stay small, reliable and maintainable.
  • It's permissively-licensed and everything is implemented in one language (Go), without abstraction layers (like web or ORM frameworks), making the codebase suitable for educational purposes and easy to understand and hack on.

Using tootik

You can join an existing instance or set up your own.

Building

go generate ./migrations

Then:

go build ./cmd/tootik -tags fts5

or, to build a static executable:

go build -tags netgo,sqlite_omit_load_extension,fts5 -ldflags "-linkmode external -extldflags -static" ./cmd/tootik

Gemini Frontend

  • /local shows a compact list of local posts; each entry contains a link to /view.

  • / is the homepage: it shows an ASCII art logo, a short description of this server and a list of local posts.

  • /federated shows a compact list of federated posts.

  • /hashtag shows a compact list of posts with a given hashtag.

  • /search shows an input prompt and redirects to /hashtag.

  • /hashtags shows a list of popular hashtags.

  • /fts shows an input prompt and performs full-text search in posts.

  • /stats shows statistics and server health metrics.

  • /view shows a complete post with extra details like links in the post, a list mentioned users, a list of hashtags, a link to the author's outbox, a list of replies and a link to the parent post (if found).

  • /thread displays a tree of replies in a thread.

  • /outbox shows list of posts by a user.

Users are authenticated using TLS client certificates; see Gemini protocol specification for more details. The following pages require authentication:

  • /users shows the number of incoming posts by date.
  • /users/inbox shows a list of posts by followed users and posts sent to the authenticated user.
  • /users/firehose is like /users/inbox but sorted chronologically.
  • /users/register creates a new user.
  • /users/follows shows a list of followed users, ordered by activity.
  • /users/resolve looks up federated user user@domain or local user user.
  • /users/dm creates a post visible to a given user.
  • /users/whisper creates a post visible to followers.
  • /users/say creates a public post.
  • /users/reply replies to a post.
  • /users/edit edits a post.
  • /users/delete deletes a post.
  • /users/share shares a post.
  • /users/unshare removes a shared post.
  • /users/follow sends a follow request to a user.
  • /users/unfollow deletes a follow request.
  • /users/outbox is equivalent to /outbox but also includes a link to /users/follow or /users/unfollow.
  • /users/bio allows users to edit their bio.

Some clients generate a certificate for / (all pages of this capsule) when /foo requests a client certificate, while others use the certificate requested by /foo only for /foo and /foo/bar. Therefore, pages that don't require authentication are also mirrored under /users:

  • /users/local
  • /users/federated
  • /users/hashtag
  • /users/hashtags
  • /users/fts
  • /users/stats
  • /users/view
  • /users/thread

This way, users who prefer not to provide a client certificate when browsing to /x can reply to public posts by using /users/x instead.

To make the transition to authenticated pages more seamless, links in the user menu at the bottom of each page point to /users/x rather than /x, if the user is authenticated.

All pages follow the subscription convention, so users can "subscribe" to a user, a hashtag, posts by followed users or other activity. This way, tootik can act as a personal, curated and prioritized fediverse aggregator. In addition, /users/inbox always shows posts received within a given day, to interrupt the endless stream of incoming content, make the content consumption more intentional and prevent doomscrolling.

Authentication

If no client certificate is provided, all pages under /users redirect the client to /users.

/users asks the client to provide a certificate. Well-behaved clients should generate a certificate, re-request /users, then reuse this certificate in future requests of /users and pages under it.

If a certificate is provided but does not belong to any user, the client is redirected to /users/register.

By default, the username associated with a client certificate is the common name specified in the certificate. If invalid or already in use by another user, /users/register asks the user to provide a different username. Once the user is registered, the client is redirected back to /users.

Once the client certificate is associated with a user, all pages under /users look up the authenticated user's data using the certificate hash.

Posts

tootik has three kinds of posts:

  • Messages: posts visible to their author and a single recipient
  • Posts: posts visible to followers of a user
  • Public posts: posts visible to anyone

User A is allowed to send a message to user B only if B follows A.

Post Visibility

Post type To CC
Message Receiving user -
Post Author's followers Mentions
Public post Public Mentions and author's followers

Reply Visibility

Post type To CC
Message Post author -
Post Post author Post recipients, mentions and followers of reply author
Public post Post author Post recipients, mentions, followers of reply author and Public

Post Editing

/users/edit cannot remove recipients from the post audience, only add more. If a post that mentions only @a is edited to mention only @b, both a and b will receive the updated post.

Polls

tootik supports Station-style polls. To publish a poll, publish a post in the form:

[POLL post content] option 1 | option 2 | ...

For example:

[POLL Does #tootik support polls now?] Yes | No | I don't know

Polls are multi-choice, allowed to have 2 to 5 options and end after a month.

Poll results are updated every 30m and distributed to other servers if needed.

Implementation Details

The "Nobody" User

Outgoing requests, like the WebFinger requests used to discover federated users, are usually associated with a user. For example, the key pair associated with local user A is used to digitally sign the Follow request sent to federated user B.

To protect user's privacy, requests not initiated by a particular user or requests not triggered during handling of user requests (like requests made during validation of incoming public posts) are associated with a special user named "nobody".

The Resolver

The resolver is responsible for resolving a user ID (local or federated) into an Actor object that contains the user's information, like the user's display name. Actor objects for federated users are cached in the database and updated once in a while.

This is an expensive but common operation that involves outgoing HTTPS requests. Therefore, to protect underpowered servers against heavy load and a big number of concurrent outgoing requests, the maximum number of outgoing requests is capped, concurrent attempts to resolve the same user are blocked and the resolver is a long-lived object that reuses connections.

Moved Accounts

If a user follows a federated user with the movedTo attribute set and the new account's alsoKnownAs attribute points back to the old account, follow requests are sent to the new user and old requests are cancelled.

Notes

The "notes" table holds posts and allows fast search of posts by author, replies to a post and so on.

Outbox

Once saved to the "notes" table, new posts can be viewed by local users. However, delivery to federated followers can take time and generate many outgoing requests.

Therefore, user actions are represented as an activity saved to the "outbox" table, accompanied by a delivery attempts counter, creation time and last attempt time. For example, a local post saved to the "notes" table is also accompanied with a Create activity in the "outbox" table. A single worker thread polls the table, prioritizes activities by the number of delivery attempts and the interval between attempts, then tries to deliver each activity to its federated recipients.

Inbox

The server verifies HTTP signatures of requests to /inbox/%s, using the sender's key. They key is cached to reduce the amount of outgoing requests.

The server must resolve unknown users to display their preferred username, summary text and so on, and a user may send an activity that cannot be displayed unless other users associated with it are resolved, too. Therefore, processing of incoming requests can generate outgoing requests. For example, the server must resolve C when A, who follows B and receives replies to B's posts, receives a reply by C, or a post by B which mentions C. B is guaranteed to be cached because A follow B, but C isn't. Therefore, incoming activities are saved to the "inbox" table and a worker thread processes these queued activities.

Migrations

To add a migration named x and add it to the list of migrations:

./migrations/add.sh x
go generate ./migrations

Credits and Legal Information

tootik is free and unencumbered software released under the terms of the Apache License Version 2.0; see LICENSE for the license text.

The ASCII art logo at the top was made using FIGlet.